简体   繁体   中英

How to get a List type with type arguments in Java annotation processor

Let's say I have a property

@MyAnnotation
class Foo {
    var bar: List<String> = emptyList()
}

And I'm writing an annotation processor for the code above:

class AnnotationProcessor : AbstractProcessor() {


    override fun getSupportedAnnotationTypes(): MutableSet<String> = mutableSetOf(...)

    @KotlinPoetMetadataPreview
    override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?
): Boolean {

        val barPropType: TypeMirror = (roundEnv!!.getElementsAnnotatedWith(Command::class.java) as TypeElement)
            .kotlinProperties
            .first()
            .type


        return true
    } 
}

I want to now verify that the type of the property is assignable from List<String>

A solution I was able to find is to use the isAssignable method avaliable in TypeUtils , but in order to do that I would need an instance of TypeMirror for List, which I was not able to find a way to do. I can get an instance of List and perform an erasure to obtain a TypeMirror of raw type, which will not help me though as it is assignable to any List<> , a List<Integer> for example:

val listType = processingEnv.elementUtils.getTypeElement(List::class.java.name).asType()

println((listType as DeclaredType).typeArguments) // E

println(processingEnv.typeUtils.isAssignable(barPropType, listType)) // false

        println(processingEnv.typeUtils.isAssignable( // true, but also true when barPropType is List<Integer>
            processingEnv.typeUtils.erasure(barPropType),
            listType,
        ))


This talk proposes a following solution:

  /**
   * Get the type parameter for a {@link Collection}.
   * @param type a parameterized collection type
   * @return the type of elements in the collection
   */
  private DeclaredType getCollectionType(TypeMirror type) {
    if (type != null && typeUtils().isAssignable(type, collectionType.type)) {
      // This is a bit of a hack; to work properly, we should walk up the inheritance hierarchy, tracking type
      // parameters as we go. For example, if the type in question is StringList, which implements List<String>,
      // then this code would not work.
      List<? extends TypeMirror> typeArguments = ((DeclaredType) type).getTypeArguments();
      return (DeclaredType) typeArguments.get(0);
    }
    return null;
  }

full code

For my project, I would need that "proper" solution, but I don't know how to implement it. I've done something like this:

fun TypeElement.allSuperInterfaces(env: ProcessingEnvironment): List<TypeMirror> {
    val result: MutableList<TypeMirror> = mutableListOf(this.asType())
    result.addAll(interfaces)

    var toCheckFurther = interfaces
    while(toCheckFurther.isNotEmpty()) {

        toCheckFurther = toCheckFurther
            .mapNotNull { env.typeUtils.asElement(it) as? TypeElement }
            .flatMap { it.interfaces }
            .toMutableList()

        result.addAll(toCheckFurther)

    }

    return result;
}

The problem with this is that during conversion from the TypeMirror to TypeElement information about the actual type argument is lost:

((barPropType as DeclaredType).asElement() as TypeElement)
            .allSuperInterfaces(processingEnv)
            .forEach(::println)

outputs:

java.util.List<E>
java.util.Collection<E>
java.lang.Iterable<E>

But at the same time it seems like this conversion needs to take place as there is no way of obtaining the implemented interfaces from the TypeMirror (at least that I am aware of).

What is the proper way to implement this solution involving walking up the inheritance hierarchy?

You need to override AbstractProcessor init-Method . There you will get "processingEnv" parameter which you can use to initilaize TypeMirror for List-class, which you can use later with isAssignable .

In java it will looks like that:

public final class MyProcessor extends AbstractProcessor {

    private TypeMirror listTM;

    @Override public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        listTM = processingEnv.getElementUtils().getTypeElement(List.class.getName()).asType();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM