Reputation: 8812
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.
Upvotes: 5
Views: 1916
Reputation: 41
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.
Upvotes: 4
Reputation: 774
@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
Upvotes: -3