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;
}
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.