Может ли обработчик аннотаций получить доступ к типам, созданным в предыдущем раунде?

У меня есть процессор аннотаций, который генерирует класс идентификатора для каждого аннотированного класса. Я хотел бы, чтобы классы могли ссылаться на сгенерированные типы идентификаторов других классов в той же единице компиляции. К сожалению, похоже, что обработчик аннотаций всегда выдает тип сгенерированного класса как ERROR, даже если этот тип был сгенерирован в предыдущем раунде компиляции или полностью отдельным процессором. Это можно обойти?

Вот минимальный пример. Скажем, у меня есть следующий класс:

package tmp;

@MyAnnotation
public class Foo {
  private Foo me;
  private FooId myId;
}

Сначала он обрабатывается этим процессором аннотаций для генерации идентификаторов:

@SupportedAnnotationTypes("tmp.proc.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CreateIdProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for (Element elem : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
      System.out.println("Writing ID for " + elem.getSimpleName());
      try {
        Writer file = processingEnv.getFiler().createSourceFile(elem + "Id").openWriter();
        file.write(String.format("package tmp; public class %sId {}", elem.getSimpleName()));
        file.close();
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
    return false;
  }
}

И затем обрабатываются этим процессором аннотаций для анализа типов:

@SupportedAnnotationTypes("tmp.proc.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CheckIdProcessor extends AbstractProcessor {
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    for (Element elem : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {
      for (Element element : elem.getEnclosedElements()) {
        System.out.printf("%s = %s (%s)\n", element.getSimpleName(), element.asType(), element.asType().getKind());
      }
    }
    return false;
  }
}

Результат выполнения сборки выглядит так:

Writing ID for Foo
<init> = ()void (EXECUTABLE)
me = tmp.Foo (DECLARED)
myId = FooId (ERROR)

Все компилируется нормально, но второй процессор видит тип myId как ОШИБКУ, даже если он был сгенерирован первым процессором (а класс FooId действительно присутствует в выходном банке). Это не позволяет обработчику аннотаций анализировать FooId, например, чтобы выяснить, к какому пакету он будет принадлежать, чтобы импортировать его. Есть ли обходные пути для этой проблемы?


person Erik Kuefler    schedule 08.05.2015    source источник


Ответы (1)


Процесс аннотации Java выполняет свою работу по очереди. Во время каждого раунда он будет запускать все применимые обработчики аннотаций, а затем компилировать вывод. API обработчика аннотаций также отслеживает, какие классы были обработаны, и обрабатывает их только один раз. Проблема, с которой вы столкнулись, заключается в том, что ваш CreateIdProcessor генерирует классы в том же раунде, в котором CheckIdProcessor их ищет. Поскольку FooId не компилируется перед запуском CheckIdProcessor, элемент, возвращаемый при вызове getEnclosedElements(), является элементом ERROR.

Короче говоря, ваш второй процессор видит тип поля FooId как ошибку, потому что исходный код FooId был сгенерирован, но еще не скомпилирован.

Вот эксперимент, который вы можете провести. Этот обработчик аннотаций распечатает все новые классы, обработанные в течение данного раунда. FooId отображается под Round: 1, тогда как Foo отображается под Round: 0:

Round: 0
Foo = tmp.Foo (DECLARED)
<init> = ()void (EXECUTABLE)
me = tmp.Foo (DECLARED)
myId = FooId (ERROR)
Writing ID for Foo
Round: 1
FooId = tmp.FooId (DECLARED)
Round: 2
Round: 3

Вот процессор аннотаций, который я использовал для этого:

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class OutputRoundProcessor extends AbstractProcessor {
  private int round = 0;
  @Override
  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    System.out.println("Round: " + round);
    for (Element element : roundEnv.getRootElements()) {
      System.out.printf("%s = %s (%s)\n", element.getSimpleName(), element.asType(), element.asType().getKind());
    }
    round++;
    return false;
  }
}
person John Ericksen    schedule 12.05.2015
comment
Спасибо, этого было достаточно, чтобы указать мне правильное направление. Обычно мне нужно дважды запустить мой процессор на одном и том же объекте, чтобы он мог генерировать идентификаторы в первом раунде, а затем ссылаться на них во втором. Как вы сказали, каждый тип обрабатывается только один раз, но я могу обойти это, если в первом раунде будет храниться список имен классов, с которыми он сталкивается, а затем второй раунд будет считываться из этих классов вместо ввода раунда. Кажется, это помогает. Спасибо! - person Erik Kuefler; 15.05.2015