Как получить доступ к аннотации TypeUse через AnnotationProcessor - PullRequest
10 голосов
/ 18 марта 2019

Вопрос:

  1. Можно ли получить доступ к элементам, аннотированным аннотацией @Target(ElementType.TYPE_USE), через процессор аннотаций?
  2. Можно ли получить доступ к границам аннотированных типов через процессор аннотаций?

Ссылки на соответствующую документацию, которую я пропустил, высоко ценятся.

Контекст:

Аннотация:

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface TypeUseAnno {}

Пример класса:

public class SomeClass extends HashMap<@TypeUseAnno String, String> {}

Процессор:

@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;
    }
}

Компиляция вышеуказанного SomeClass с Processor на пути к классам покажет сообщение "Intialized", но метод process(...) никогда не вызывается. Добавление другой аннотации к процессору с @Target(ElementType.PARAMETER) работает нормально, когда аннотация присутствует в параметре метода. Если параметр метода помечен @TypeUseAnno, процесс снова проигнорирует элемент.

1 Ответ

5 голосов
/ 21 марта 2019

Аннотации TYPE_USE немного хитры, потому что компилятор обрабатывает их иначе, чем аннотации "старого использования".

Итак, как вы правильно заметили, они не передаются процессору аннотаций, и ваш process() метод никогда их не получит.

Так как их использовать во время компиляции?

В Java 8, где были представлены эти аннотации, также был представлен новый способ присоединения к компиляции Java. Теперь вы можете присоединить слушателя к задачам компиляции и инициировать собственный обход исходного кода. Таким образом, ваша задача доступа к аннотации делится на две части.

  1. Крюк к компилятору
  2. Реализуйте свой анализатор

Объявление 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, поскольку компилятор не выбирает файлы, созданные на этом этапе, для дальнейшей компиляции.

...