Search code examples
javaannotation-processing

How to get Actual Type from typeElement?


I'm writing custom annotation processor. Though I'm able to process class type without generic type but unable to get actual types for generic types.

MyBase Interface

interface MyBase<T, S> {
  void method1(S, T);
}

Example Class1

public class KpA{
 ...
}

Example Class2

public class KpB{
 ...
}

The Target Annotated class

@MyAnnotation
interface MyHandler extends MyBase<KpA, KpB>{

}

Processing code snippet

    TypeElement[] getActualTypes(ProcessingEnvironment pEnv, TypeElement element){
        List<TypeElement> myBaseElement = element.getInterfaces().stream().map(v-> (TypeElement) pEnv.getTypeUtils().asElement(v)).collect(Collectors.toList());
        myBaseElement.get(0).getTypeParameters().stream().forEach(System.out::println);
         return null;
    }

When I Sysout the getTypeParameters I get output as S and T; how can I get KpA and KpB.


Solution

  • When you deal with type system, it is important to understand what you precisely want. Do you want those type parameters because their type variables are used in some field/method of base type? E.g. your parametrized base type is something like Collection<SomeItem> and you want to know return type of add? Or is MyBase some kind of marker interface like Clonable and you want precisely it's parameter?

    Whatever; let's suppose, that your situation is exactly as described in your question: MyBase is some interface and you simply want it's arguments for unknown reasons. Here is an inflexible, dumb way to accomplish that:

    // instance of javax.lang.model.util.Types
    protected final Types types;
    
    // instance of javax.lang.model.util.Elements
    protected final Elements el;
    
    // TypeMirror for java.lang.Object
    protected final TypeMirror theObject = ...
    
    private static final TYPE_REFINER = new DeclaredTypeRefiner();
    
    private class DeclaredTypeRefiner extends TypeKindVisitor6<DeclaredType, TypeMirror> {
      @Override
      public DeclaredType visitDeclared(DeclaredType type, TypeMirror desirableParent) {
        if (types.isSameType(types.erasure(type), desirableParent)) {
          return type;
        }
    
        return null;
      }
    
      @Override
      public DeclaredType visitUnknown(TypeMirror typeMirror, TypeMirror typeMirror2) {
        return defaultAction(typeMirror, typeMirror2);
      }
    
      @Override
      protected DeclaredType defaultAction(TypeMirror type, TypeMirror desirableParent) {
        if (types.isSameType(type, theObject)) {
          return null;
        }
    
        final List<? extends TypeMirror> superTypes = types.directSupertypes(type);
    
        for (TypeMirror parent : superTypes) {
            final DeclaredType discovered = visit(parent, desirableParent);
    
          if (discovered != null) {
             return discovered;
          }
        }
    
        return null;
      }
    }
    
    public TypeMirror getTypeArg(TypeMirror actual, TypeMirror base, int idx) {
      DeclaredType found = TYPE_REFINER.visit(actual, types.erasure(base));
    
      return found.getTypeArguments().get(idx);
    }
    

    To obtain the type of 2nd parameter (e.g. KpB):

    // type mirror of MyHandler goes here
    TypeMirror concrete = ...
    
    // your base type goes here. Real code should cache it
    TypeMirror base = types.getDeclaredType(el.getTypeElement("com.example.MyBase"));
    
    // boom
    TypeMirror arg = typeHelper.getTypeArg(concrete, base, 1);
    

    Here is what goes on here:

    1. Iterate over parent types looking for DeclaredTypes (e.g. classes and interfaces)
    2. Check whether a declared type is our base interface
    3. If we found it, return it to caller, so it can check the type args

    The trick lies in call to directSupertypes: as explained in it's documentation, that method automagically resolved type arguments for you.

    We compare type erasures (aka raw types) because they are expected to be comparable in meaningful way (it is more or less the same as comparing TypeElements). Please do not try to compare generic types with isSameType — this is guaranteed to produce surprising results due to the way type system works.


    Note, that the code above might not always provide correct results due to multiple inheritance: MyHandler may inherit MyBase from multiple parents, leading to confusion about real identities of T and S. If those variables aren't used anywhere (e.g. we are speaking about a pure marker interface), it is going to remain a mystery: the compiler does not care, so nobody knows (I presume, that you don't want to manually compute upper/lower bounds, do you?) If those variables are used in some methods/fields, you should be able to get their types by examining those methods/fields on MyHandler (how to do that is a slightly tricky matter worth another question).