Support new repeatable annotations in kotlin-reflect
- Unwrap Kotlin-repeatable annotations (with implicit container) - Introduce `KAnnotatedElement.findAnnotations` to find instances of repeated annotations #KT-12794
This commit is contained in:
@@ -7,7 +7,9 @@
|
||||
|
||||
package kotlin.reflect.full
|
||||
|
||||
import kotlin.reflect.*
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Returns an annotation of the given type on this element.
|
||||
@@ -25,3 +27,75 @@ inline fun <reified T : Annotation> KAnnotatedElement.findAnnotation(): T? =
|
||||
@WasExperimental(ExperimentalStdlibApi::class)
|
||||
inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
/**
|
||||
* Returns all annotations of the given type on this element, including individually applied annotations
|
||||
* as well as repeated annotations.
|
||||
*
|
||||
* In case the annotation is repeated, instances are extracted from the container annotation class similarly to how it happens
|
||||
* in Java reflection ([java.lang.reflect.AnnotatedElement.getAnnotationsByType]). This is supported both for Kotlin-repeatable
|
||||
* ([kotlin.annotation.Repeatable]) and Java-repeatable ([java.lang.annotation.Repeatable]) annotation classes.
|
||||
*/
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
inline fun <reified T : Annotation> KAnnotatedElement.findAnnotations(): List<T> =
|
||||
findAnnotations(T::class)
|
||||
|
||||
/**
|
||||
* Returns all annotations of the given type on this element, including individually applied annotations
|
||||
* as well as repeated annotations.
|
||||
*
|
||||
* In case the annotation is repeated, instances are extracted from the container annotation class similarly to how it happens
|
||||
* in Java reflection ([java.lang.reflect.AnnotatedElement.getAnnotationsByType]). This is supported both for Kotlin-repeatable
|
||||
* ([kotlin.annotation.Repeatable]) and Java-repeatable ([java.lang.annotation.Repeatable]) annotation classes.
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@SinceKotlin("1.5")
|
||||
@ExperimentalStdlibApi
|
||||
fun <T : Annotation> KAnnotatedElement.findAnnotations(klass: KClass<T>): List<T> {
|
||||
val filtered = annotations.filterIsInstance(klass.java)
|
||||
if (filtered.isNotEmpty()) return filtered
|
||||
|
||||
val containerClass = Java8RepeatableContainerLoader.loadRepeatableContainer(klass.java)
|
||||
if (containerClass != null) {
|
||||
val container = annotations.firstOrNull { it.annotationClass.java == containerClass }
|
||||
if (container != null) {
|
||||
// A repeatable annotation container must have a method "value" returning the array of repeated annotations.
|
||||
val valueMethod = container::class.java.getMethod("value")
|
||||
return (valueMethod(container) as Array<T>).asList()
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private object Java8RepeatableContainerLoader {
|
||||
class Cache(val repeatableClass: Class<out Annotation>?, val valueMethod: Method?)
|
||||
|
||||
var cache: Cache? = null
|
||||
|
||||
private fun buildCache(): Cache {
|
||||
val repeatableClass = try {
|
||||
Class.forName("java.lang.annotation.Repeatable") as Class<out Annotation>
|
||||
} catch (e: ClassNotFoundException) {
|
||||
return Cache(null, null)
|
||||
}
|
||||
|
||||
return Cache(repeatableClass, repeatableClass.getMethod("value"))
|
||||
}
|
||||
|
||||
fun loadRepeatableContainer(klass: Class<out Annotation>): Class<out Annotation>? {
|
||||
var cache = cache
|
||||
if (cache == null) {
|
||||
cache = buildCache()
|
||||
this.cache = cache
|
||||
}
|
||||
|
||||
val repeatableClass = cache.repeatableClass ?: return null
|
||||
val repeatable = klass.getAnnotation(repeatableClass) ?: return null
|
||||
val valueMethod = cache.valueMethod ?: return null
|
||||
|
||||
return valueMethod(repeatable) as Class<out Annotation>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.descriptors.runtime.components.tryLoadClass
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaAnnotation
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.ReflectJavaClass
|
||||
import org.jetbrains.kotlin.descriptors.runtime.structure.safeClassLoader
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
|
||||
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion
|
||||
@@ -49,6 +50,7 @@ import org.jetbrains.kotlin.serialization.deserialization.MemberDeserializer
|
||||
import java.lang.reflect.Type
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
import kotlin.jvm.internal.RepeatableContainer
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.IllegalCallableAccessException
|
||||
@@ -122,7 +124,22 @@ internal fun Annotated.computeAnnotations(): List<Annotation> =
|
||||
is RuntimeSourceElementFactory.RuntimeSourceElement -> (source.javaElement as? ReflectJavaAnnotation)?.annotation
|
||||
else -> it.toAnnotationInstance()
|
||||
}
|
||||
}
|
||||
}.unwrapRepeatableAnnotations()
|
||||
|
||||
private fun List<Annotation>.unwrapRepeatableAnnotations(): List<Annotation> =
|
||||
if (any { it.annotationClass.java.simpleName == JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME })
|
||||
flatMap {
|
||||
val klass = it.annotationClass.java
|
||||
if (klass.simpleName == JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME &&
|
||||
klass.getAnnotation(RepeatableContainer::class.java) != null
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(klass.getDeclaredMethod("value").invoke(it) as Array<out Annotation>).asList()
|
||||
else
|
||||
listOf(it)
|
||||
}
|
||||
else
|
||||
this
|
||||
|
||||
private fun AnnotationDescriptor.toAnnotationInstance(): Annotation? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
|
||||
Reference in New Issue
Block a user