Аннотации TYPE_USE
немного хитры, потому что компилятор обрабатывает их иначе, чем аннотации "старого использования".
Итак, как вы правильно заметили, они не передаются процессору аннотаций, и ваш process()
метод никогда их не получит.
Так как их использовать во время компиляции?
В Java 8, где были представлены эти аннотации, также был представлен новый способ присоединения к компиляции Java. Теперь вы можете присоединить слушателя к задачам компиляции и инициировать собственный обход исходного кода. Таким образом, ваша задача доступа к аннотации делится на две части.
- Крюк к компилятору
- Реализуйте свой анализатор
Объявление 1.
Есть 2 варианта зацепить компилятор в Java 8
1. Использование нового API плагина компилятора (https://docs.oracle.com/javase/8/docs/jdk/api/javac/tree/com/sun/source/util/Plugin.html)
2. Использование процессора аннотаций
Я не использовал опцию № 1, потому что она должна быть явно указана в качестве параметра javac. Поэтому я опишу вариант № 1:
Вы должны присоединить TaskListener
к фазе компиляции пропппера. Есть разные фазы. Следующий - единственный, в течение которого у вас есть доступное синтаксическое дерево, представляющее полный исходный код, включая тела методов (помните, что аннотации TYPE_USE
могут использоваться даже в объявлениях локальных переменных.
@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;
}
}
Объявление 2.
Теперь MyTreeScanner
может сканировать полный исходный код и находить аннотации. Это применимо независимо от того, использовали ли вы подход Plugin
или AnnotationProcessor
. Это все еще сложно. Вы должны реализовать TreeScanner
или, как правило, расширить TreePathScanner
.
Это представляет собой шаблон посетителей, где вы должны правильно проанализировать, какие элементы вас интересуют для посещения.
Давайте приведем простой пример, который может как-то реагировать на объявление локальной переменной (дайте мне 5 минут):
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;
}
}
Это очень краткое введение. Для реальных примеров вы можете взглянуть на следующий исходный код проекта:
https://github.com/c0stra/fluent-api-end-check/tree/master/src/main/java/fluent/api/processors
Также очень важно иметь хорошие тесты при разработке таких функций, чтобы вы могли отлаживать, перепроектировать и решать все сложные проблемы, с которыми вы столкнетесь в этой области;)
Для этого вы также можете получить вдохновение здесь:
https://github.com/c0stra/fluent-api-end-check/blob/master/src/test/java/fluent/api/EndProcessorTest.java
Может быть, мое последнее замечание, поскольку аннотации действительно используются по-разному в javac, есть некоторые ограничения. Например. он не подходит для запуска генерации кода Java, поскольку компилятор не выбирает файлы, созданные на этом этапе, для дальнейшей компиляции.