I have an annotation processor that generates an ID class for each annotated class. I would like classes to be able to refer to the generated ID types of other classes in the same compilation unit. Unfortunately, it seems like the annotation processor always gives the kind of a generated class as ERROR, even if that type was generated in a previous compilation round or by an entirely separate processor. Is there a way around this?
Here's a minimal example. Say I have the following class:
package tmp;
@MyAnnotation
public class Foo {
private Foo me;
private FooId myId;
}
It's processed first by this annotation processor to generate the IDs:
@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;
}
}
And then processed by this annotation processor to analyze the types:
@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;
}
}
The output of running the build looks like this:
Writing ID for Foo
<init> = ()void (EXECUTABLE)
me = tmp.Foo (DECLARED)
myId = FooId (ERROR)
Everything compiles fine, but the second processor sees the type of myId
as ERROR even though it was generated by the first processor (and the FooId class is indeed there in the output jar). This prevents the annotation processor from analyzing FooId
to, for example, find out the package it would belong to in order to import it. Are there any workarounds for this problem?
The Java Annotation Process performs its work in rounds. During each round it will run all applicable annotation processors, then compile the output. The Annotation Processor API also keeps track of what classes have been processed, and only processes them once. The problem you are running into is that your CreateIdProcessor
is generating classes during the same round that CheckIdProcessor
is looking for them. Because FooId
is not compiled before CheckIdProcessor
runs, the Element returned from calling getEnclosedElements()
is the ERROR
element.
In short, your second processor sees the FooId
field type as an error because FooId
's source was generated, but not compiled yet.
Here's an experiment you can run. This annotation processor will print out all new classes processed during the given round. FooId
shows up under Round: 1
whereas Foo
would show up under 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
Here's the annotation processor I used for this:
@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;
}
}