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.

doanduyhai
  • 8,712
  • 27
  • 26
  • I dug into into the code of the [checker-framework](https://github.com/typetools/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](https://github.com/typetools/checker-framework/blob/master/framework/src/org/checkerframework/framework/util/element/MethodTypeParamApplier.java#L60) – Raphaël Brugier Aug 07 '15 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. – Raphaël Brugier Aug 07 '15 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. – doanduyhai Aug 09 '15 at 09:05
  • The handling of Class field is done here: https://github.com/typetools/checker-framework/blob/master/framework/src/org/checkerframework/framework/util/element/TypeVarUseApplier.java#L184-L187 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()_ – doanduyhai Aug 09 '15 at 09:16
  • Cleanely getting type annotations from VariableElement does not seem to be possible in Java 8, but there is a [promising series of changes](http://mail.openjdk.java.net/pipermail/anno-pipeline-dev/2015-April.txt) in Java 9 javac repository, so the situation may get better eventually. – user1643723 Nov 23 '16 at 08:25

2 Answers2

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.

wmdietl
  • 41
  • 4
  • 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 – doanduyhai Nov 02 '15 at 13:01
  • Element.asType doesn't return the type you would want https://docs.oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html#asType-- 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. – wmdietl Nov 02 '15 at 16:34
-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

synthomat
  • 774
  • 5
  • 11
  • 2
    The reflection API is useful for runtime introspection. I want to use **annotation processing** API at compile time. – doanduyhai Aug 06 '15 at 07:00