Getting Class values from Annotations in an AnnotationProcessor

In annotation processors one common use case is to read the values contained within an Annotation. In most cases this works fine, however there are some caveats when it comes to accessing values within Annotations that are either Class or a Class array. It’s even worse if you are trying to access an annotation on a method within a super type of the class you are processing. Here I’ll go through how to access those Class values and how to get those values when they are in a super type – specifically when a method has been overridden.

First why?

Well inside retepTools we have an AnnotationProcessor that checks to ensure that certain annotations are used correctly, specifically the @ReadLock and @WriteLock annotations. It’s invalid for those annotations to be used together on the same method. It’s also invalid for a method to be annotated with one and then overridden in another class but annotated with the other, so the processor checks the overridden method and generates an error if the rules have been violated.

Now this caused be a big headache in reading the overridden annotations, because although the annotations were present, the values (and specific the Class ones) were null. Although @ReadLock and @WriteLock do not use values, another related annotation @Contract does, so here’s how I finally solved the problem.

The usual way of reading annotation values

In most cases, when you have an javax.lang.model.element.Element you can use the getAnnotation() method to return your annotation and then you have access to the values contained within it. For example say we have an annotation called Action which holds a single String value:

@Documented
@Retention( RetentionPolicy.RUNTIME )

@Target(ElementType.METHOD)

public @interface Action

{

    String value();

}

We annotate a class with that annotation against the run method:

public class A {

    @Action( “do something” )

    public void run() {

    }

}

Now within your AnnotationProcessor, you can obviously get the value easily by calling getAnnotation( Action.class ) and if it returns an instance call the value() method:

@SupportedAnnotationTypes( “*” )

@SupportedSourceVersion( SourceVersion.RELEASE_6 )

@ProhibitAnnotationProcessing

public class Processor

        extends AbstractProcessor

{

    @Override

    public boolean process( final Set<? extends TypeElement> annotations,

                            final RoundEnvironment env )

    {

        if( !env.processingOver() )

        {

            for( Element e : env.getRootElements() )

            {

                TypeElement te = findEnclosingTypeElement( e );

                System.out.printf( “\nScanning Type %s\n\n”,

                                   te.getQualifiedName() );

                for( ExecutableElement ee : ElementFilter.methodsIn(

                        te.getEnclosedElements() ) )

                {

                    Action action = ee.getAnnotation( Action.class );

                    

                    System.out.printf(

                            “%s Action value = %s\n”,

                            ee.getSimpleName(),

                            action == null ? null : action.value() );

                }

            }

        }

        return false;

    }

    public static TypeElement findEnclosingTypeElement( Element e )

    {

        while( e != null && !(e instanceof TypeElement) )

        {

            e = e.getEnclosingElement();

        }

        return TypeElement.class.cast( e );

    }

}

This generates the following output when run:

————————————————————————

Building scratch

   task-segment: [clean, install]

————————————————————————

[clean:clean]

Deleting directory /Users/peter/dev/retep/scratch/target

[compiler:compile {execution: compileAnnotations}]

Compiling 2 source files to /Users/peter/dev/retep/scratch/target/classes

[resources:resources]

Using default encoding to copy filtered resources.

[compiler:compile]

Compiling 2 source files to /Users/peter/dev/retep/scratch/target/classes

Scanning Type scratch.A

run Action value = do something

Reading annotation values from an overridden method

Now this is fine, but what happens if you are looking at an annotation thats in an overridden class?

Say we have class B which extends A and overrides run():

public class B extends A {

    @Override

    public void run() {

    }

}

Now when we run the processor, for each ExecutableElement we’ll first look for an annotation and then if not found we’ll look for an overridden method and check there. 

    @Override

    public boolean process( final Set<? extends TypeElement> annotations,

                            final RoundEnvironment env )

    {

        if( !env.processingOver() )

        {

            for( Element e : env.getRootElements() )

            {

                TypeElement te = findEnclosingTypeElement( e );

                System.out.printf( “\nScanning Type %s\n\n”,

                                   te.getQualifiedName() );

                for( ExecutableElement ee : ElementFilter.methodsIn(

                        te.getEnclosedElements() ) )

                {

                    Action action = ee.getAnnotation( Action.class );

                    if( action == null )

                    {

                        // Look for the overridden method

                        ExecutableElement oe = getExecutableElement( te,

                                                                     ee.getSimpleName() );

                        if( oe != null )

                        {

                            action = oe.getAnnotation( Action.class );

                        }

                    }

                    System.out.printf(

                            “%s Action value = %s\n”,

                            ee.getSimpleName(),

                            action == null ? null : action.value() );

                }

            }

        }

        return false;

    }

    public ExecutableElement getExecutableElement( final TypeElement typeElement,

                                                   final Name name )

    {

        TypeElement te = typeElement;

        do

        {

            te = (TypeElement) processingEnv.getTypeUtils().asElement(

                    te.getSuperclass() );

            if( te != null )

            {

                for( ExecutableElement ee : ElementFilter.methodsIn(

                        te.getEnclosedElements() ) )

                {

                    if( name.equals( ee.getSimpleName() ) && ee.getParameters().isEmpty() )

                    {

                        return ee;

                    }

                }

            }

        } while( te != null );

        return null;

    }

Now when we run we get the annotated value on A.run() when we are processing B.run():

[compiler:compile]

Compiling 2 source files to /Users/peter/dev/retep/scratch/target/classes

Scanning Type scratch.A

run Action value = do something

Scanning Type scratch.B

run Action value = do something

The problem with Class

Now this is fine, but what happens if the annotation’s value is Class instead of String? Well the problem here is that Javac does not load classes in the normal manner. In fact it doesn’t at all for classes that are in the source – it’s all contained within a model.

Now say our Action annotation had value defined as Class instead of String. In that case when we call action.value() it would fail:

[compiler:compile]

Compiling 2 source files to /Users/peter/dev/retep/scratch/target/classes

Scanning Type scratch.A

javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror java.lang.Runnable

So we have to find another way to get the value, and there are two available to us. The first is not to use getAnnotation() but getAnnotationMirrors(), and the second is a neat trick with MirroredTypeException.

Solution 1 use getAnnotationMirrors()

This solution is a little long winded but is the most reliable. When getAnnotationMirrors() is used, it returns a set of AnnotationMirror instances, one for each annotation on that Element, so the first step is to locate the correct AnnotationMirror for the annotation you require. The next step is to extract the AnnotationValue’s from that mirror which represents the values stored in the annotation. This is held in a map keyed by an ExecutableElement.

Why ExecutableElement? Well it’s because the annotation values are actually defined as methods – hence why in our Action value is defined as value().

So in the next example we run through the AnnotationMirrors on a method until we find the one for our annotation then run through until we find the required value.

Once we have the AnnotationValue we simply print it to System.out but normally you would use the getValue() method which returns the value as an Object. If the value is an array it returns a java.util.List containing the values. Oh and if the values are of type Class then it returns a TypeMirror or a List of TypeMirrors.

Element actionElement = processingEnv.getElementUtils().getTypeElement(

        Action.class.getName() );

TypeMirror actionType = actionElement.asType();

for( ExecutableElement ee : ElementFilter.methodsIn(

        te.getEnclosedElements() ) )

{

    ExecutableElement oe = ee;

    AnnotationValue action = null;

    while( action == null && oe != null )

    {

        for( AnnotationMirror am : oe.getAnnotationMirrors() )

        {

            if( am.getAnnotationType().equals( actionType ) )

            {

                for( Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet() )

                {

                    if( “value”.equals(

                            entry.getKey().getSimpleName().toString() ) )

                    {

                        action = entry.getValue();

                        break;

                    }

                }

            }

        }

        // Look for the overridden method

        oe = getExecutableElement(

                findEnclosingTypeElement( oe ),

                ee.getSimpleName() );

    }

    System.out.printf(

            “%s Action value = %s\n”,

            ee.getSimpleName(),

            action == null ? null : action );

}

Now at first that appears to work, and in most use cases it does – if we have a method thats overridden then we get the annotation values from the overridden method.

However, although its not obvious, if the super type is not part of the same CompilationUnit – i.e. it’s in a third party jar or from a previous call to javac then it will not find anything outside of that CompilationUnit.

The trouble with TypeMirrors

Now the cause on why the solution above fails isn’t obvious. The problem here is actually down to the TypeMirror’s. In the above example we get a TypeMirror for the annotation called actionType then search the AnnotationMirror set of each element using that TypeMirror.

Now TypeMirror acts in a similar way to how Class works at runtime. At run time Class is equal if it’s in the same ClassLoader, so here TypeMirror is equal if it’s in the same CompilationUnit. So the example above fails because they are different instances.


So the solution here is not to use TypeMirror.equals() but to convert the TypeMirror into a String representing the fully qualified class name and use equals() on that String. Now, no matter what  source the super type comes from, it will always match.

Here’s the new version:

final String actionName = Contract.class.getName();

for( ExecutableElement ee : ElementFilter.methodsIn(

        te.getEnclosedElements() ) )

{

    ExecutableElement oe = ee;

    AnnotationValue action = null;

    while( action == null && oe != null )

    {

        for( AnnotationMirror am : oe.getAnnotationMirrors() )

        {

            if( actionName.equals(

                    am.getAnnotationType().toString() ) )

            {

                for( Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet() )

                {

                    if( “value”.equals(

                            entry.getKey().getSimpleName().toString() ) )

                    {

                        action = entry.getValue();

                        break;

                    }

                }

            }

        }

        // Look for the overridden method

        oe = getExecutableElement(

                findEnclosingTypeElement( oe ),

                ee.getSimpleName() );

    }

    System.out.printf(

            “%s Action value = %s\n”,

            ee.getSimpleName(),

            action == null ? null : action );

}

Now that one works. The lesson here is to use the String version of TypeElement when searching as two TypeElement’s representing the same class are not always equal.

Solution 2 – Single Class values

Now if your value contains just one Class (i.e. not Class[] ) then there’s a much simpler solution. This one isn’t that obvious, but I found that someone had a similar problem in the sun forums[1]. There the trick is to actually use getAnnotation() and catch the MirroredTypeException. Surprisingly the exception then provides the TypeMirror of the required class:

for( ExecutableElement ee : ElementFilter.methodsIn(

        te.getEnclosedElements() ) )

{

    Action action = ee.getAnnotation( Action.class );

    if( action == null )

    {

        // Look for the overridden method

        ExecutableElement oe = getExecutableElement( te,

                                                     ee.getSimpleName() );

        if( oe != null )

        {

            action = oe.getAnnotation( Action.class );

        }

    }

    TypeMirror value = null;

    if( action != null )

    {

        try

        {

            action.value();

        }

        catch( MirroredTypeException mte )

        {

            value = mte.getTypeMirror();

        }

    }

    System.out.printf(

            “%s Action value = %s\n”,

            ee.getSimpleName(),

            value );

}

Notice getTypeMirror() method call? Here’s the output of the above loop:

[compiler:compile]

Compiling 2 source files to /Users/peter/dev/retep/scratch/target/classes

Scanning Type scratch.A

run Action value = java.lang.Runnable

Scanning Type scratch.B

run Action value = java.lang.Runnable

This trick works fine for individual classes, but sadly it does not work for Class[] arrays. According to the javadocs it should work for Class[] as it should then throw the MirroredTypesException instead and that exception contains an alternate method that returns a List of TypeMirrors.

However it doesn’t – it simply throws MirroredTypeException for the first element in the array. I think it’s where it’s running through the array to populate it and the first element is then throwing MirroredTypeException before it gets chance to throw MirroredTypesException.

Conclusion

Well, hopefully this article will save someone hours of trouble when they hit the same problem. I’ve spent about 20 hours of time searching the net and dabbling for these solutions – and at least these solutions use just the documented public APIs.

References

  1. http://forums.sun.com/thread.jspa?threadID=791053

Author: petermount1

Prolific Open Source developer who also works in the online gaming industry during the day. Develops in Java, Go, Python & any other language as necessary. Still develops on retro machines like BBC Micro & Amiga A1200

13 thoughts on “Getting Class values from Annotations in an AnnotationProcessor”

  1. Nice post, but this works only for retrieving type metamodel (TypeMirror) from annotation elements whose value is of type Class.

    Is it actually possible to load the Class somehow?

    1. The problem is that when the annotation processor runs it runs from within Javac and as such you cannot load the class as all you have is the source classpath.

      During compilation the classes are not actually loaded in the normal sense, only the meta data is loaded, so if you try to load those classes normally and then use reflection then it will fail.

  2. Hi Peter, your post is great, thank you!

    I’ve more than one annotation for my classes. One annotation registers a Swing Action in a registry and two other annotations put this action in different GUI components (like menu, toolbar …).

    To register a special action to a component I prefer the class value. It’s more type save, but I’ve a problem to get the AnnotationMirror in nested Annotations.

    here an example:

    @Action (id=”my-super-action”, icon=”org/mycomp/icon.png”)
    @ActionReferences ({
    @ActionReference (component=MyPopupMenu.class, position = 100),
    @ActionReference (component=MyToolbar.class, position = 10),
    })
    public class MySuperAction implements ActionListener {
    }

    with

    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.TYPE)
    public @interface Action {…}

    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
    public @interface ActionReference {…}

    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PACKAGE})
    public @interface ActionReferences {
    ActionReference[] value();
    }

    With you sample code it’s easy to evaluate a class value in the @Action annotation. But if I’m in the process method to parse the Action annotation, I’m unable to go into the ActionReference:

    public final boolean process(Set annotations, RoundEnvironment env) {

    for (Element e : env.getElementsAnnotatedWith(Action.class)) {

    TypeElement clazz = (TypeElement) e;
    Action action = clazz.getAnnotation(Action.class);
    String teName = elements.getBinaryName(clazz).toString();
    // some useful things…

    ActionReference aref = e.getAnnotation(ActionReference.class);
    if (aref != null) {
    processReferences(e, teName, aref, category);
    }
    ActionReferences refs = e.getAnnotation(ActionReferences.class);
    if (refs != null) {
    for (ActionReference actionReference : refs.value()) {
    processReferences(e, teName, actionReference, category);
    }
    }
    }

    In processReference it’s no problem to call the annotation properties. But I’ve no idea how to get the AnnotationValues

    This does not work:

    private void processReferences(
    Element e, String teName, ActionReference ref, String category)
    {
    String clazz = null;

    AnnotationValue componentValue = null;
    for (AnnotationMirror am : e.getAnnotationMirrors()) {
    for (Map.Entry entry : am.getElementValues().entrySet()) {
    // Looking for the class value from component
    if (“component”.equals(entry.getKey().getSimpleName().toString())) {
    componentValue = entry.getValue();
    break;
    }
    }
    clazz = componentValue .getValue().toString();

    I’ve only the base Element provided by the getElementsAnnotatedWith method.

    Do you have an idea? 😀

    br, josh.

    1. Looking at the javadoc for AnnotationValue:

      A value is of one of the following types:
      * a wrapper class (such as Integer) for a primitive type
      * String
      * TypeMirror
      * VariableElement (representing an enum constant)
      * AnnotationMirror
      * List (representing the elements, in declared order, if the value is an array)

      So in theory going by your code, the “value” entry inside ActionReferences should be a List and then each one of those an AnnotationMirror for each ActionReference.

      I’ll have a play to see if that’s the case.

  3. Hi Peter,

    Thanks for the awesome article. I have been searching a lot to access the class values that are stored in the annotation.

    Here in your example “Action” annotation has the Class variable stored in value() method right. I am using your second solution and trying to get a hold of Class variable stored in value().

    I am able to get a TypeMirror object, But unable to extract the class object which is stored in it. It would be great if you can help me understand or guide me about how to do it.

    Thanks

  4. To whomever who wrote this, Thanks a lot! (I had solved my own question, but you just make me feel quiet by showing me that I chose the right solution)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: