简体   繁体   中英

Kotlin - How to get KClass<*> annotation parameter from annotation processor

I have the following annotation:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Model(
    val owner: KClass<*>,
    val consumer: KClass<*>
)

@Model(DataOwner::class, DataConsumer::class)
interface Student {
    val name: String
    val group: Group
}

I need to get the value of owner and consumer in my annotation processor.

I've tried this approach:

private inline fun <reified T> findAnnotationValue(
    element: Element,
    annotationClass: KClass<*>,
    valueName: String
): T? {
    return element.annotationMirrors.map {
        it to it.annotationType.asElement() as TypeElement
    }.firstOrNull { (_, element) ->
        element.qualifiedName.contentEquals(annotationClass.qualifiedName)
    }?.let { (mirror, _) ->
        extractValue(mirror, valueName)
    }
}

private inline fun <reified T> extractValue(
    annotationMirror: AnnotationMirror,
    valueName: String
): T? {
    return annotationMirror.elementValues.toList()
        .firstOrNull { (key, _) ->
            key.simpleName.contentEquals(valueName)
        }?.let { (_, value) ->
            value.value as T
        }
}


val ownerClass: KClass<*> = findAnnotationValue(
    element,
    Model::class,
    "owner"
)

But it gave me this error:

e: [kapt] An exception occurred: java.lang.ClassCastException: com.sun.tools.javac.code.Type$ClassType cannot be cast to kotlin.reflect.KClass

I also tried this:

val ownerClass: KClass<*> = element.getAnnotation(Model::class.java).owner

But it gave me this error:

e: [kapt] An exception occurred: javax.lang.model.type.MirroredTypeException: Attempt to access Class object for TypeMirror inc.ahmedmourad.systems.tutors.domain.model.DataOwner

inc.ahmedmourad.systems.tutors.domain.model.DataOwner is the owner value passed to the annotation.

So this's where i'm stuck right now, any help is appreciated. Thank you!

Let's start with the reason the second approach doesn't work:

The annotation returned by this method could contain an element whose value is of type Class. This value cannot be returned directly: information necessary to locate and load a class (such as the class loader to use) is not available, and the class might not be loadable at all. Attempting to read a Class object by invoking the relevant method on the returned annotation will result in a MirroredTypeException, from which the corresponding TypeMirror may be extracted. Similarly, attempting to read a Class[]-valued element will result in a MirroredTypesException.

which obviously applies to KClass as well.

So for classes in annotations you can only get a TypeMirror (implemented in this case by Type.ClassType , but that's an internal detail you shouldn't rely on) and not a KClass . Either by the first approach or

inline fun <reified T : Annotation> Element.getAnnotationClassValue(f: T.() -> KClass<*>) = try { 
    getAnnotation(T::class.java).f() 
    throw Exception("Expected to get a MirroredTypeException")
} catch (e: MirroredTypeException) {
    e.typeMirror
}

which can be used as

element.getAnnotationClassValue<Model> { owner }

and returns the TypeMirror for DataOwner .

If you are using KSP then the solution can look something like this:

Using the experimental APIs and assuming the KClass you reference in the annotation is available:

resolver.getSymbolsWithAnnotation("com.example.Model")
    .map {
        val consumerType = try {
            it.getAnnotationsByType(Model::class).single().consumer
            null
        } catch (e: KSTypeNotPresentException) {
            e.ksType
        }
        val consumerDeclaration = consumerType!!.declaration as KSClassDeclaration // etc
    }

If you can't do this, then maintaining a KSP view of the annotation is more flexible:

resolver.getSymbolsWithAnnotation("com.example.Model")
    .map { ksAnnotated ->
        val args = ksAnnotated.annotations.single {
            it.shortName.asString() == "Model" && it.annotationType.resolve().declaration.qualifiedName?.asString() == "com.example.Model"
        }.arguments

        val consumerType = args.single { it.name?.asString() == "owner" }.value as KSType
        val consumerDeclaration = consumerType.declaration as KSClassDeclaration // etc 
    }

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