Reputation: 385
I have a VariableElement
field that is annotated with a generated Annotation (which is why I can't use field.getAnnotation(annotationClass)
). I need to get all parameters passed to this annotation.
Note that by "a generated Annotation" I mean that literally the Annotation class itself (not the annotated one) has been generated by an Annotation Processor. The field/class that is being annotated is in the handwritten source code.
It didn't look like it'd be that hard, so far I've come up with this:
for (AnnotationMirror annotation : field.getAnnotationMirrors()) {
Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap = annotation.getElementValues();
messager.printMessage(Diagnostic.Kind.WARNING, annotation.toString() + ":" + annotationValueMap.toString());
}
I thought this would do it, but the output for the field is the following:
@MyAnnotation:{}
So, the processor does recognize that the field is annotated, but I'm unable to access the passed parameters. Even though the field is definetely annotated and does pass parameters with the annotation (it has to, since the annotation defines required parameters and no defaults):
@MyAnnotation(max = 387, min = 66876, ...)
private Integer myField;
Here's the generated annotation code:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface MyAnnotation {
int max();
boolean allowAuto();
int min();
}
I've clean-compiled the project multiple times, the processor never sees the values. What am I overlooking here? The processor can obviously see the annotation itself, yet the parameters passed to it are hidden.
Upvotes: 1
Views: 1521
Reputation: 1781
Yes, you will not be able to instantiate a Class
object for a type which is not available in your annotation processor's classloader, and may not even have been compiled into a class file yet at all. A similar problem exists for retrieving enum constants.
There are a few wrinkles to dealing with this sort of thing:
Foo.class
you need to get the erasure of that type, so you don't generate Foo<Bar>.class
into your generated sources.FWIW, I wrote a library to solve this and related problems, which can be found on Maven central at the coordinates com.mastfrog:annotations-tools:2.8.3.4
(check for newer versions). The usage pattern is simple:
AnnotationUtils
in an override of the init()
method of your annotation processor and store it in a fieldClass<?>[]
into a list of string class names that you can work with inside javac, and similarIt makes it pretty straightforward to write annotation processors that do not directly depending on the classes they processes at all - which means the annotation processors (and their dependency graphs!) be completely independent of what they process, and can depend on whatever libraries they like without forcing those dependencies into the dependency graph of any project that uses them - the most common pattern is someone writes some annotations and then puts the annotation processor in the same project, or even package, and so anything the annotation processor uses becomes a dependency of every consumer of the annotations, even though those dependencies will probably never be used at runtime at all. That, it seems to me, is an antipattern worth avoiding.
Upvotes: 0
Reputation: 18331
Recall that annotation processors run as part of the compiler, in steps called "rounds". This process runs iteratively until there is no new code to compile, and then processors get one last chance to run (not necessary for this answer, but helpful for more context). Each round only the newly created types are directly given to the processor to examine.
What seems to be happening here is that during a round you are emitting a new annotation type, which should allow the processor to observe certain features about some code submitted to be compiled. However, any types created during a given round are not yet compiled until the next round begins.
For this question, we run into a conflict here - some Java sources are compiled which use an annotation that doesn't exist yet. The processor first creates the annotation, and then tries to read the newly-created annotation out of those partly-compiled sources. Unfortunately, until the annotation has been compiled, we can't actually read the annotation. Instead, we need to wait until the subsequent round (once the annotation itself has compiled), then go back to that class which has finished being compiled and examine it.
This can be implemented yourself without too much trouble, but the easiest way is often to rely on the google/auto project (specifically the auto-common library, see https://github.com/google/auto/tree/master/common), and extend their BasicAnnotationProcessor
class. One of the nice features it supports is to automatically examine types and check if there are any compilation issues - if so, they are deferred until a later round so you can handle them without any type resolution issues.
Upvotes: 2
Reputation: 1054
Use getAnnotation(MyAnnotation.class)
available from VariableElement
in your example code you can do this to get the min
and max
parameters
MyAnnotation myAnnotation= field.getAnnotation(MyAnnotation.class);
int max = myAnnotation.max();
int min = myAnnotation.min();
this will work unless the annotation members returns class/class[]
value, in which you will get an exception if you try to get the value using this method.
more about how to get class literal values can be found on this answer
How to read a Class[] values from a nested annotation in an annotation processor
Or using annotation mirrors
for (AnnotationMirror annotation : field.getAnnotationMirrors()) {
Map<? extends ExecutableElement, ? extends AnnotationValue> annotationValueMap = annotation.getElementValues();
annotationValueMap.forEach((element, annotationValue) -> {
messager.printMessage(Diagnostic.Kind.WARNING, element.getSimpleName().toString() + ":" + annotationValue.getValue());
});
}
In case you have more than one annotation on the field then you can iterate over the annotation mirrors and use the check types.isSameType(annotationMirror.getAnnotationType(), elements.getTypeElement(MyAnnotation.class.getName()).asType())
to find the annotation you are interested in
Upvotes: 1