The issue was getSupportedAnnotationTypes() needed to return "*" because it needed to be able to operate on the empty set of annotations. It also needed to always run the writeAnnotationFile
, not just when roundEnv.getElementsAnnotatedWith
returned a result.
Now it properly updates the file when an annotation is removed. This means that it'll consume all classes when you do a full rebuild, but a slow processor is better than one that doesn't work at all. It also has a problem where it creates empty text files instead of removing it, but it's not that big of a deal.
Edit
The previous posted solution worked within Eclipse, but did not when built with Gradle. This solution works in both Eclipse and Gradle.
@SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class AnnotationTextDump extends AbstractProcessor {
private List<AnnotationTextDumpWriter> writers = null;
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (writers == null) {
writers =
Arrays.asList(
new AnnotationTextDumpWriter("your.Annotation1"),
new AnnotationTextDumpWriter("your.Annotation2"));
}
if (!roundEnv.processingOver()) {
for (var i : writers) {
try {
i.appendElements(annotations, roundEnv, processingEnv);
} catch (IOException e) {
// You should properly log the exception here, but I'm not sure how to get it to appear
// in Eclipse's Problem's view.
e.printStackTrace();
throw new IllegalStateException(e);
}
}
} else {
for (var i : writers) {
i.close();
}
}
return false;
}
}
class AnnotationTextDumpWriter{
private String annotationName;
private boolean initialized;
private Set<String> classes;
private PrintWriter fileOutput;
XmImmutableListWriter(String annotationName) {
this.annotationName = annotationName;
initialized = false;
classes = null;
fileOutput = null;
}
void appendElements(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv,
ProcessingEnvironment processingEnv)
throws IOException {
initialize(processingEnv);
for (var i : annotations) {
if (i.getQualifiedName().toString().equals(annotationName)) {
var elements = roundEnv.getElementsAnnotatedWith(i);
convertElementsToList(elements, processingEnv);
}
}
}
private void initialize(ProcessingEnvironment processingEnv) throws IOException {
if (initialized) {
return;
}
var fileName = "META-INF/annotations/" + annotationName;
initialized = true;
classes = new HashSet<>();
classes.addAll(getPreviousListedClasses(fileName, annotationName, processingEnv));
fileOutput =
new PrintWriter(
processingEnv
.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", fileName)
.openWriter());
}
private void convertElementsToList(
Set<? extends Element> elements, ProcessingEnvironment processingEnv) {
for (var elem : elements) {
var packag = processingEnv.getElementUtils().getPackageOf(elem).getQualifiedName().toString();
var fullyQualifiedName = packag + "." + elem.getSimpleName();
classes.add(fullyQualifiedName);
}
}
void close() {
if (classes != null) {
var sortedClasses = new ArrayList<>(classes);
Collections.sort(sortedClasses);
for (var i : sortedClasses) {
fileOutput.append(i);
fileOutput.append('\n');
}
}
fileOutput.close();
}
private static List<String> getPreviousListedClasses(
String fileName, String annotationName, ProcessingEnvironment processingEnv)
throws IOException {
try {
var previousFile =
processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", fileName);
var classSplit = openPreviousFile(previousFile);
var ret = new ArrayList<String>();
for (var clazz : classSplit) {
if (classIsStillAnnotated(clazz, annotationName, processingEnv)) {
ret.add(clazz);
}
}
return ret;
} catch (FilerException e) {
throw e;
} catch (IOException e) {
return Collections.emptyList();
}
}
private static List<String> openPreviousFile(FileObject previousFile) throws IOException {
try (var in = previousFile.openInputStream()) {
var fileContents = ByteStreams.toByteArray(in);
var fileString = new String(fileContents, StandardCharsets.UTF_8);
if (fileString.isBlank()) {
return Collections.emptyList();
}
var classSplit = fileString.split("\n");
return Arrays.asList(classSplit);
}
}
private static boolean classIsStillAnnotated(
String clazz, String annotationName, ProcessingEnvironment processingEnv) {
var prevTypeElement = processingEnv.getElementUtils().getTypeElement(clazz);
if (prevTypeElement == null) {
return false;
}
var annot = prevTypeElement.getAnnotationMirrors();
for (var i : annot) {
var annotElem = i.getAnnotationType().asElement();
var packag =
processingEnv.getElementUtils().getPackageOf(annotElem).getQualifiedName().toString();
var qualifiedAnnotName = packag + "." + annotElem.getSimpleName();
if (qualifiedAnnotName.equals(annotationName)) {
return true;
}
}
return false;
}
}
Old solution that works in Eclipse, but not in Gradle
@SupportedAnnotationTypes({"*"})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class AnnotationTextDump extends AbstractProcessor {
public static final String YOUR_ANNOTATION1 = "your.Annotation1";
public static final String YOUR_ANNOTATION2 = "your.Annotation2";
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("*");
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
try {
generateAnnotationFiles(annotations, roundEnv);
} catch (Exception e) {
// You should properly log the exception here, but I'm not sure how to get it to appear
// in Eclipse's Problem's view.
throw new IllegalStateException(e);
}
}
return false;
}
private void generateAnnotationFiles(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) throws IOException {
Set<? extends Element> elements1 = Collections.<Element>emptySet();
Set<? extends Element> elements2 = Collections.<Element>emptySet();
for (var i : annotations) {
if (i.getQualifiedName().toString().equals(YOUR_ANNOTATION1)) {
elements1 = roundEnv.getElementsAnnotatedWith(i);
}
if (i.getQualifiedName().toString().equals(YOUR_ANNOTATION2)) {
elements2 = roundEnv.getElementsAnnotatedWith(i);
}
}
writeAnnotationFile(YOUR_ANNOTATION1, elements1);
writeAnnotationFile(YOUR_ANNOTATION2, elements2);
}
private void writeAnnotationFile(String annotationName, Set<? extends Element> elements)
throws IOException {
var fileName = "META-INF/annotations/" + annotationName;
var classBuilder = new HashSet<>(convertElementsToList(elements));
classBuilder.addAll(getPreviousListedClasses(fileName, annotationName));
var classes = new ArrayList<>(classBuilder);
Collections.sort(classes);
var fileContents = new StringBuilder();
for (var i : classes) {
fileContents.append(i);
fileContents.append('\n');
}
try (var out =
new PrintWriter(
processingEnv
.getFiler()
.createResource(StandardLocation.SOURCE_OUTPUT, "", fileName)
.openWriter())) {
out.append(fileContents.toString());
}
}
private List<String> getPreviousListedClasses(String fileName, String annotationName)
throws IOException {
try {
var previousFile =
processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, "", fileName);
var classSplit = openPreviousFile(previousFile);
var ret = new ArrayList<String>();
for (var clazz : classSplit) {
if (classIsStillAnnotated(clazz, annotationName)) {
ret.add(clazz);
}
}
return ret;
} catch (FilerException e) {
throw e;
} catch (IOException e) {
return Collections.emptyList();
}
}
private List<String> openPreviousFile(FileObject previousFile) throws IOException {
try (var in = previousFile.openInputStream()) {
var fileContents = ByteStreams.toByteArray(in);
var fileString = new String(fileContents, StandardCharsets.UTF_8);
if (fileString.isBlank()) {
return Collections.emptyList();
}
var classSplit = fileString.split("\n");
return Arrays.asList(classSplit);
}
}
private boolean classIsStillAnnotated(String clazz, String annotationName) {
var prevTypeElement = processingEnv.getElementUtils().getTypeElement(clazz);
if (prevTypeElement == null) {
return false;
}
var annot = prevTypeElement.getAnnotationMirrors();
for (var i : annot) {
var annotElem = i.getAnnotationType().asElement();
var packag =
processingEnv.getElementUtils().getPackageOf(annotElem).getQualifiedName().toString();
var qualifiedAnnotName = packag + "." + annotElem.getSimpleName();
if (qualifiedAnnotName.equals(annotationName)) {
return true;
}
}
return false;
}
private List<String> convertElementsToList(Set<? extends Element> elements) {
var ret = new ArrayList<String>();
for (var elem : elements) {
var packag = processingEnv.getElementUtils().getPackageOf(elem).getQualifiedName().toString();
var fullyQualifiedName = packag + "." + elem.getSimpleName();
ret.add(fullyQualifiedName);
}
return ret;
}
}