1

I've struggled with this for a while now, relying on various arcane casts and manipulation of toString to get it working, but there has to be a proper way of doing this.

I've got an annotation processor that looks like this:

@SupportedAnnotationTypes("processor.EchoFields")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class TestProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        roundEnvironment.getElementsAnnotatedWith(EchoFields.class)
                .stream()
                .filter(e -> e.getKind() == ElementKind.CLASS)
                .map(TypeElement.class::cast)
                .forEach(this::echoFields);
        return false;
    }

    private void echoFields(TypeElement element) {
        log("Fields of class %s", element);
        element.getEnclosedElements().stream()
                .filter(e -> e.getKind() == ElementKind.FIELD)
                .map(VariableElement.class::cast)
                .forEach(this::echoField);
    }

    private void echoField(VariableElement element) {
        log("\tField %s of type %s", element.getSimpleName().toString(), element.asType().toString());
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            log("\t\t%s", annotationMirror);
        }
    }

    private void log(String format, Object... parameters) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, String.format(format, parameters));
    }

}

When run on my test class that looks like this:

@EchoFields
public class Pojo {

    @TypeAnnotation
    @FieldAnnotation
    private List<String> aListOfStrings;
}

(@TypeAnnotation, and @FieldAnnotation have targets TYPE(_USE) and FIELD respectively)

I get the following output:

Note: Fields of class consumer.Pojo
Note:   Field aListOfStrings of type @processor.TypeAnnotation java.util.List<java.lang.String>
Note:           @processor.FieldAnnotation

Whereas what I want is this:

Note: Fields of class consumer.Pojo
Note:   Field aListOfStrings of type java.util.List<java.lang.String>
Note:           @processor.TypeAnnotation
Note:           @processor.FieldAnnotation

With the important part being java.util.List<java.lang.String> without @processor.TypeAnnotation - Listing @processor.TypeAnnotation with the annotations is just a bonus and not something I need.

I can't seem to find any way of doing this though. Every time I find something that removes the annotation I also lose the type parameters (java.lang.String). I've poked around in the classes implementing the annotation processing API and there is definitely methods there that do what I want - but I can't find any way of invoking them through the public API and I don't want to rely on implementation details (especially since I got vastly different implementations when I tried this on JDK 8).

There's a runnable Gradle project at https://github.com/Raniz85/processor-test, just clone and run ./gradlew build

Raniz
  • 10,882
  • 1
  • 32
  • 64

1 Answers1

2

Since TypeAnnotation is marked as TYPE+TYPE_USE (and note that TYPE isn't relevant for this issue) and not FIELD, it isn't an annotation on the field at all, but is actually on the declared type of the field. You can put TYPE_USE annotations on other things as well, like local variables - but you cannot read them from a normal annotation processor. From How to access TypeUse annotation via AnnotationProcessor:

The TYPE_USE annotations are a bit tricky, because the compiler treats them differently, than the "old usage" annotations.

So as you correctly observed, they are not passed to annotation processor, and your process() method will never receive them.

Without knowing specifically what problem you are trying to solve, it is hard to say if you actually want TYPE_USE in the first place, or if you are just experimenting with the different possible targets that an annotation may have. Probably you just want to use FIELD instead here, since you are writing an annotation processor (which traverses your API, and not the source code itself, so you'll miss any TYPE_USE on local vars, etc).

--

Also, be very careful of using TypeMirror.toString() or Element.toString(), in some environments these will not output what you are expecting. Either use the Name type, or something like javapoet's TypeName hierarchy.

Colin Alworth
  • 17,801
  • 2
  • 26
  • 39
  • As stated in the question I'm not really interested in working with the TYPE_USE annotation, it's merely causing me issues. How would I go about getting a Name for my type? – Raniz Jan 21 '20 at 05:39
  • For clarification, I'm encountering _other_ TYPE_USE annotations in my actual processor and they are causing this issue. – Raniz Jan 21 '20 at 05:47
  • `TypeName`from JavaPoet fixed this, though it bugs me that I need to pull in a library to deal with it – Raniz Jan 21 '20 at 06:05
  • 2
    Getting a `Name` requires an `Element`, not a `TypeMirror` - any TypeMirror which is an instanceof DeclaredType can then be cast to DeclaredType, and you can call `asElement()` on it then. Element then has `getSimpleName()`, and also `getEnclosingElement()` to get the parent class or package (if any). These tools are what JavaPoet uses, but you could of course implement it yourself. Handling generics is a bit harder, but still not magic - you will take the TypeMirror and write a visitor which will examine each type in the object, again, as JavaPoet internally does. – Colin Alworth Jan 21 '20 at 16:56
  • You can absolutely get TYPE_USE annotations from the annotation processor. You just can't claim or query for all of them. https://docs.oracle.com/en/java/javase/16/docs/api/java.compiler/javax/lang/model/type/TypeMirror.html#getAnnotationMirrors() **Note that any annotations returned by this method are type annotations.** – Adam Gent Aug 02 '21 at 20:48
  • Good point - though you wouldn't access it as the question was doing, but would access it from the mirror of the method's return type (or the field's type). – Colin Alworth Aug 03 '21 at 15:19