5

Consider the following code:

public class SimpleTest {

    private Map<@JSON Integer,Map<@Frozen Integer,@Enumerated(value = Enumerated.Encoding.NAME, test = "123") String>> map;
}

With the latest JDK8 API for annotation processing, how can I access the list of annotations (@JSON, @Frozen & @Enumerated) and their corresponding attributes (value & test for @Enumerated) from the VariableElement ?

final VariableElement mapElm = els.stream().filter(x -> x.getSimpleName().contentEquals("map")).findFirst().get();
???
???

I've tried many tricks, like mapElm.getTypeArguments().get(0) for the @Json Integer but I never succeed to get my hand on the annotation @JSON...

Edit: By accessing internal classes of the JDK, I can have access to those annotations but it's so hacky and sensitive to impl change that I'm wondering whether there is a better way

public static class SimpleEntityCodecFactoryTest {

    private Map<@JSON Integer,Map<@Frozen Integer,@Enumerated(value = Enumerated.Encoding.NAME, test = "123") String>> map;
}

final TypeElement typeElement = elementUtils.getTypeElement(SimpleEntityCodecFactoryTest.class.getCanonicalName());
final List<VariableElement> els = ElementFilter.fieldsIn(typeElement.getEnclosedElements());

final VariableElement mapElt = els.stream().filter(x -> x.getSimpleName().contentEquals("map")).findFirst().get();
final com.sun.tools.javac.util.List<Attribute.TypeCompound> typeAttributes = ((Symbol.VarSymbol) mapElt).getMetadata().getTypeAttributes();
for (Attribute.TypeCompound typeAttribute : typeAttributes) {
    final DeclaredType annotationType = typeAttribute.getAnnotationType();
    System.out.println(format("Accessing annotation '%s' at location : %s",annotationType.toString(),typeAttribute.getPosition().location));
    for (Map.Entry<Symbol.MethodSymbol,Attribute> entry : typeAttribute.getElementValues().entrySet()) {
        final Symbol.MethodSymbol methodSymbol = entry.getKey();
        final Attribute attribute = entry.getValue();
        System.out.println(format("Attribute '%s' for annotation '%s' : %s", methodSymbol.name, annotationType.toString(), attribute.toString()));
    }
}    

The output display:

Accessing annotation 'info.archinnov.achilles.annotations.JSON' at location : TYPE_ARGUMENT(0)
Accessing annotation 'info.archinnov.achilles.annotations.Frozen' at location : TYPE_ARGUMENT(1),TYPE_ARGUMENT(0)
Accessing annotation 'info.archinnov.achilles.annotations.Enumerated' at location : TYPE_ARGUMENT(1),TYPE_ARGUMENT(1)
Attribute 'value' for annotation 'info.archinnov.achilles.annotations.Enumerated' : info.archinnov.achilles.annotations.Enumerated.Encoding.NAME
Attribute 'test' for annotation 'info.archinnov.achilles.annotations.Enumerated' : "123"

The above code is working fine in IntelliJ, but because of the dirty cast ((Symbol.VarSymbol) mapElt).getMetadata(), it is working with Oracle JDK but fails miserably with Eclipse compiler.

Right now, I don't find any other solution than the dirty cast to access annotations in generic types. Any idea is welcomed

Solution:

Thanks to Werner (wmdietl), I can access the nested annotations using the Tree API instead of Elements or TypeMirror

However I'm quite stuck because once I get there, it is not possible to convert any subclass of Tree back to Element or TypeMirror (my real target).

All of my annotation processing is using heavily JavaPoet (https://github.com/square/javapoet) to generate clean source code and this framework only handles TypeMirror, not Tree

In the https://github.com/typetools/checker-framework/blob/master/javacutil/src/org/checkerframework/javacutil/TreeUtils.java class, there are some methods to convert Tree back to Element but it is relying on InternalUtils, which I can't use because it won't be compatible with Eclipse ECJ compiler.

I guess I will have to wait for JDK 9 before having an usable Element API that will be compatible with ECJ compiler

Edit: To make the type annotation work for Eclipse Compiler, I had to cast to internal compiler classes like here: https://github.com/doanduyhai/Achilles/blob/master/achilles-core/src/main/java/info/archinnov/achilles/internals/parser/AnnotationTree.java#L83-L85. It's ugly but that is the only way for now until JDK9.

5
  • I dug into into the code of the checker-framework which is a good example of the annotation processor (it's the base of the JSR 308) and could not find a better way. See their implementation of the type parameter : here Commented Aug 7, 2015 at 20:33
  • Btw, did you manage to trigger the getElementsAnnotatedWith method on your parameter to get a VariableElement ? For me it seems an annotation with the target set to TYPE_PARAMETER is never passed to the processing round. I had to registered on everything and then go down to the method. Commented Aug 7, 2015 at 20:39
  • "Btw, did you manage to trigger the getElementsAnnotatedWith method on your parameter to get a VariableElement ?" --> I'm dealing with VariableElement as Class field, not method parameter. Commented Aug 9, 2015 at 9:05
  • The handling of Class field is done here: github.com/typetools/checker-framework/blob/master/framework/… And they're doing an ugly casting from VariableElement to Symbol. VarSymbol and then call the internal method getRawTypeAttributes() which is pretty equivalent to calling getMetadata().getTypeAttributes() Commented Aug 9, 2015 at 9:16
  • Cleanely getting type annotations from VariableElement does not seem to be possible in Java 8, but there is a promising series of changes in Java 9 javac repository, so the situation may get better eventually. Commented Nov 23, 2016 at 8:25

2 Answers 2

4

You shouldn't look at the Element (or Symbol), but at the TypeMirror (javax.lang.model.type.TypeMirror). The annotations you are interested in are type use annotations, so you can't access them (easily, there are hacky ways) through the Element.

Once you have the TypeMirror, you can use the methods in javax.lang.model.AnnotatedConstruct to query for all or particular annotations.

Another aspect to be aware of: usual annotation processing runs early in the compiler and not all types might have been set. See https://github.com/typetools/checker-framework/blob/master/javacutil/src/org/checkerframework/javacutil/AbstractTypeProcessor.java for a way to run your processor after code attribution. Alternatively, you can use the new "plugin" mechanism in com.sun.source.util.Plugin, but that is OpenJDK specific.

Sign up to request clarification or add additional context in comments.

2 Comments

I re-used the AbstractTypeProcessor from the checker framework. I put a break point at final VariableElement mapElt there, I did mapElt.asType().getAnnotationMirrors() but it only returns me: 1. @EmptyCollectionIfNull 2. @Frozen All other annotations like @Enumerated etc ... are not returned
Element.asType doesn't return the type you would want docs.oracle.com/javase/8/docs/api/javax/lang/model/element/… You should use the Tree and ask for the TypeMirror of the variable declaration. If you send a complete example, I can have a look at what's happening.
-3

@JSON must be decorated with @Retention(RetentionPolicy.RUNTIME) so that Reflections can be used.

AnnotatedParameterizedType mapField = (AnnotatedParameterizedType)SimpleTest.class.getDeclaredFields()[0].getAnnotatedType();
AnnotatedType integerType = mapField.getAnnotatedActualTypeArguments()[0];

System.out.println(integerType.getAnnotations()[0]);
// -> @JSON()

see AnnotatedParameterizedType

1 Comment

The reflection API is useful for runtime introspection. I want to use annotation processing API at compile time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.