@Target(ElementType.TYPE_USE)
annotation via an annotation processor?Links to related documentation I missed are highly appreciated.
The annotation:
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface TypeUseAnno {}
An example class:
public class SomeClass extends HashMap<@TypeUseAnno String, String> {}
The processor:
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("base.annotations.TypeUseAnno")
public class Processor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Initialized.");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Invoked.");
for (TypeElement annotation : annotations) {
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "" + roundEnv.getElementsAnnotatedWith(annotation));
}
return true;
}
}
Compiling the above SomeClass
with Processor
on the classpath will show the "Intialized"
message but the process(...)
method is never invoked.
Adding another annotation to the processor with @Target(ElementType.PARAMETER)
works fine when the annotation is present on a method parameter. If the method parameter is annotated with @TypeUseAnno
the process will again ignore the element.
The TYPE_USE
annotations are a bit tricky, because the compiler treats them differently, than the "old usage" annotations.
So as you correctly observed, they are not passed to annotation processor, and your process()
method will never receive them.
So how to use them at compilation time?
In Java 8, where these annotations got introduced, there was also introduced new way to attach to java compilation. You can now attach listener to compilation tasks, and trigger your own traversal of the source code. So your task to access the annotation splits into two.
Ad 1. There are 2 options to hook on the compiler in Java 8:
I haven't used option #1 much, because it needs to be explicitely specified as javac parameter. So I'll describe option #1:
You have to attach TaskListener
to the propper compilation phase. There are various phases. Following one is the only one, during which you have accessible syntax tree representing full source code including method bodies (remember, that TYPE_USE
annotations can be used even on local variable declarations.
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EndProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
Trees trees = Trees.instance(env);
JavacTask.instance(env).addTaskListener(new TaskListener() {
@Override
public void started(TaskEvent taskEvent) {
// Nothing to do on task started event.
}
@Override
public void finished(TaskEvent taskEvent) {
if(taskEvent.getKind() == ANALYZE) {
new MyTreeScanner(trees).scan(taskEvent.getCompilationUnit(), null);
}
}
});
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// We don't care about this method, as it will never be invoked for our annotation.
return false;
}
}
Ad 2.
Now the MyTreeScanner
can scan the full source code, and find the annotations. That applies no matter if you used the Plugin
or AnnotationProcessor
approach. This is still tricky. You have to implement the TreeScanner
, or typically extend the TreePathScanner
.
This represents a visitor pattern, where you have to properly analyze, which elements are of your interest to be visited.
Let's give simple example, that can somehow react on local variable declaration (give me 5 minutes):
class MyTreeScanner extends TreePathScanner<Void, Void> {
private final Trees trees;
public MyTreeScanner(Trees trees) {
this.trees = trees;
}
@Override
public Void visitVariable(VariableTree tree, Void aVoid) {
super.visitVariable(variableTree, aVoid);
// This method might be invoked in case of
// 1. method field definition
// 2. method parameter
// 3. local variable declaration
// Therefore you have to filter out somehow what you don't need.
if(tree.getKind() == Tree.Kind.VARIABLE) {
Element variable = trees.getElement(trees.getPath(getCurrentPath().getCompilationUnit(), tree));
MyUseAnnotation annotation = variable.getAnnotation(MyUseAnnotation.class);
// Here you have your annotation.
// You can process it now.
}
return aVoid;
}
}
This is very brief introduction. For real examples you can have a look at following project source code: https://github.com/c0stra/fluent-api-end-check/tree/master/src/main/java/fluent/api/processors
It's also very important to have good tests while developing such features, so you can debug, reverse engineer and solve all the tricky issues you'll face in this area ;) For that you can also get inspired here: https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java
Maybe my last remark, as the annotations are really used differently by the javac, there are some limitations. E.g. it's not suitable for triggering java code generation, because the compiler doesn't pick files created during this phase for further compilation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With