Search code examples
javaannotation-processing

How to get the qualified class name of generic type in annotation processing?


I have an interface like this:

interface MyInterface<V>{

}

and all of my annotated classes with @MyAnnotation implement this interface in different ways, for example.

//first way
Class1 implement MyInterface<SomeClass>
//second way
AbstractClass<V> implement MyInterface<V>
Class2 extends  AbstractClass<SomeClass>
//third way    
ConcreteClass implement MyInterface<SomeClass>
Class3 extends ConcreteClass

Well, I have TypeElements of class 1,2 and 3 and I want to find qualified name of type variable V.

I tried this but it returns V instead of SomeClass.

TypeElement class1 = ...
while(reachToMyInterface){
for (TypeMirror m : ((DeclaredType) class1.asType()).getTypeArguments()) {
      print(m.toString()) // prints V
  }
  class1 = getItsSuperClass();
}

Edit: Also this approach has same problem:

 for (Element element : roundEnv.getElementsAnnotatedWith(Haha.class)) {
            if (element instanceof TypeElement) {
                TypeElement te = (TypeElement) element;
                TypeElement currentType = te;
                while(currentType!=null){
                for (TypeMirror typeMirror : currentType .getInterfaces()) {
                  if (typeMirror instanceof DeclaredType) {
                       DeclaredType dclt = (DeclaredType) typeMirror;
                      for (TypeMirror argument : dclt.getTypeArguments()) {
                        print(argument);
                      }
                   }
                }
                currentType = getSuperClass(currentType);                   
              }
}
private TypeElement getSuperClass(TypeElement typeElement) {
        if (!(typeElement.getSuperclass() instanceof DeclaredType)) return null;
        DeclaredType declaredAncestor = (DeclaredType) typeElement.getSuperclass();

        return (TypeElement) declaredAncestor.asElement();
    }

Solution

  • It's painful, but it can be done.

    With these types:

    @Retention(RUNTIME) @Target(TYPE) public @interface Haha {}
    
    interface MyInterface<V>{}
    
    @Haha public class StringImpl implements MyInterface<String> {}
    

    Here's an annotation processor that prints "java.lang.String" for the @Hahaannotation on StringImpl:

    public class Proc extends AbstractProcessor {
        @Override public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton("bar.Haha");
        }
    
        @Override public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.RELEASE_8;
        }
    
        @Override public boolean process(final Set<? extends TypeElement> annotations,
                                         final RoundEnvironment roundEnv) {
            for (Element element : roundEnv.getElementsAnnotatedWith(Haha.class)) {
                if (element instanceof TypeElement) {
                    TypeElement te = (TypeElement) element;
                    for (TypeMirror typeMirror : te.getInterfaces()) {
                        if (typeMirror instanceof DeclaredType) {
                            DeclaredType dclt = (DeclaredType) typeMirror;
                            for (TypeMirror argument : dclt.getTypeArguments()) {
                                System.out.println(argument);
                            }
                        }
                    }
                }
            }
            return false;
        }
    }
    

    Getting there is a lot of testing and fiddling, and I used this test to debug the process:

    public class ProcTest {
        @Test
        public void run() {
            String basePath = "/path/to/src/folder/";
            List<String> args = asList("Haha", "MyInterface", "StringImpl")
                    .stream()
                    .map(s -> basePath +"bar/" + s + ".java")
                    .collect(Collectors.toList());
            args.addAll(0, asList("-processor", Proc.class.getName()));
            String[] flags = args.toArray(new String[3]);
            ToolProvider.getSystemJavaCompiler()
                        .run(System.in, System.out, System.err,  flags);
        }
    }
    

    If you run this in debug mode, you can set breakpoint inside the annotation processor. That really helped me understand things better.