Support Kotlin annotation constructors in reflection
#KT-13106 In Progress
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package kotlin.reflect.jvm.internal
|
||||
|
||||
import org.jetbrains.kotlin.load.java.structure.reflect.wrapperByPrimitive
|
||||
import java.lang.reflect.Proxy
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KotlinReflectionInternalError
|
||||
import java.lang.reflect.Method as ReflectMethod
|
||||
|
||||
internal class AnnotationConstructorCaller(
|
||||
private val jClass: Class<*>,
|
||||
private val parameterNames: List<String>,
|
||||
private val callMode: CallMode,
|
||||
methods: List<ReflectMethod> = parameterNames.map { name -> jClass.getDeclaredMethod(name) }
|
||||
) : FunctionCaller<Nothing?>(
|
||||
null, jClass, null, methods.map { it.genericReturnType }.toTypedArray()
|
||||
) {
|
||||
enum class CallMode { CALL_BY_NAME, POSITIONAL_CALL }
|
||||
|
||||
// Transform primitive int to java.lang.Integer because actual arguments passed here will be boxed and Class#isInstance should succeed
|
||||
private val erasedParameterTypes: List<Class<*>> = methods.map { method -> method.returnType.let { it.wrapperByPrimitive ?: it } }
|
||||
|
||||
private val defaultValues: List<Any?> = methods.map { method -> method.defaultValue }
|
||||
|
||||
override fun call(args: Array<*>): Any? {
|
||||
checkArguments(args)
|
||||
|
||||
val values = args.mapIndexed { index, arg ->
|
||||
val value =
|
||||
if (arg == null && callMode == CallMode.CALL_BY_NAME) defaultValues[index]
|
||||
else arg.transformKotlinToJvm(erasedParameterTypes[index])
|
||||
value ?: throwIllegalArgumentType(index, parameterNames[index], erasedParameterTypes[index])
|
||||
}
|
||||
|
||||
return createAnnotationInstance(jClass, parameterNames.zip(values).toMap())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a Kotlin value to the one required by the JVM, e.g. KClass<*> -> Class<*> or Array<KClass<*>> -> Array<Class<*>>.
|
||||
* Returns `null` in case when no transformation is possible (an argument of an incorrect type was passed).
|
||||
*/
|
||||
private fun Any?.transformKotlinToJvm(expectedType: Class<*>): Any? {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val result = when (this) {
|
||||
is Class<*> -> return null
|
||||
is KClass<*> -> this.java
|
||||
is Array<*> -> when {
|
||||
this.isArrayOf<Class<*>>() -> return null
|
||||
this.isArrayOf<KClass<*>>() -> (this as Array<KClass<*>>).map(KClass<*>::java).toTypedArray()
|
||||
else -> this
|
||||
}
|
||||
else -> this
|
||||
}
|
||||
|
||||
return if (expectedType.isInstance(result)) result else null
|
||||
}
|
||||
|
||||
private fun throwIllegalArgumentType(index: Int, name: String, expectedJvmType: Class<*>): Nothing {
|
||||
val kotlinClass = when {
|
||||
expectedJvmType == Class::class.java -> KClass::class
|
||||
expectedJvmType.isArray && expectedJvmType.componentType == Class::class.java ->
|
||||
@Suppress("CLASS_LITERAL_LHS_NOT_A_CLASS") Array<KClass<*>>::class // Workaround KT-13924
|
||||
else -> expectedJvmType.kotlin
|
||||
}
|
||||
// For arrays, also render the type argument in the message, e.g. "... not of the required type kotlin.Array<kotlin.reflect.KClass>"
|
||||
val typeString = when {
|
||||
kotlinClass.qualifiedName == Array<Any>::class.qualifiedName ->
|
||||
"${kotlinClass.qualifiedName}<${kotlinClass.java.componentType.kotlin.qualifiedName}>"
|
||||
else -> kotlinClass.qualifiedName
|
||||
}
|
||||
throw IllegalArgumentException("Argument #$index $name is not of the required type $typeString")
|
||||
}
|
||||
|
||||
private fun createAnnotationInstance(annotationClass: Class<*>, values: Map<String, Any>): Any {
|
||||
return Proxy.newProxyInstance(annotationClass.classLoader, arrayOf(annotationClass)) { proxy, method, args ->
|
||||
// TODO: support equals, hashCode, toString, annotationType
|
||||
values[method.name] ?: throw KotlinReflectionInternalError("Method is not supported: $method (args: ${args.orEmpty().toList()})")
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import java.lang.reflect.Constructor as ReflectConstructor
|
||||
import java.lang.reflect.Field as ReflectField
|
||||
import java.lang.reflect.Method as ReflectMethod
|
||||
|
||||
internal abstract class FunctionCaller<out M : Member>(
|
||||
internal abstract class FunctionCaller<out M : Member?>(
|
||||
internal val member: M,
|
||||
internal val returnType: Type,
|
||||
internal val instanceClass: Class<*>?,
|
||||
@@ -45,7 +45,7 @@ internal abstract class FunctionCaller<out M : Member>(
|
||||
}
|
||||
|
||||
protected fun checkObjectInstance(obj: Any?) {
|
||||
if (obj == null || !member.declaringClass.isInstance(obj)) {
|
||||
if (obj == null || !member!!.declaringClass.isInstance(obj)) {
|
||||
throw IllegalArgumentException("An object member requires the object instance passed as the first argument.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ import kotlin.reflect.jvm.javaType
|
||||
internal abstract class KCallableImpl<out R> : KCallable<R> {
|
||||
abstract val descriptor: CallableMemberDescriptor
|
||||
|
||||
// The instance which is used to perform a positional call, i.e. `call`
|
||||
abstract val caller: FunctionCaller<*>
|
||||
|
||||
// The instance which is used to perform a call "by name", i.e. `callBy`
|
||||
abstract val defaultCaller: FunctionCaller<*>?
|
||||
|
||||
abstract val container: KDeclarationContainerImpl
|
||||
@@ -94,7 +96,7 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
|
||||
override val isAbstract: Boolean
|
||||
get() = descriptor.modality == Modality.ABSTRACT
|
||||
|
||||
private val isAnnotationConstructor: Boolean
|
||||
protected val isAnnotationConstructor: Boolean
|
||||
get() = name == "<init>" && container.jClass.isAnnotation
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@@ -102,8 +104,12 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
|
||||
return caller.call(args) as R
|
||||
}
|
||||
|
||||
// See ArgumentGenerator#generate
|
||||
override fun callBy(args: Map<KParameter, Any?>): R {
|
||||
return if (isAnnotationConstructor) callAnnotationConstructor(args) else callDefaultMethod(args)
|
||||
}
|
||||
|
||||
// See ArgumentGenerator#generate
|
||||
private fun callDefaultMethod(args: Map<KParameter, Any?>): R {
|
||||
val parameters = parameters
|
||||
val arguments = ArrayList<Any?>(parameters.size)
|
||||
var mask = 0
|
||||
@@ -153,6 +159,25 @@ internal abstract class KCallableImpl<out R> : KCallable<R> {
|
||||
}
|
||||
}
|
||||
|
||||
private fun callAnnotationConstructor(args: Map<KParameter, Any?>): R {
|
||||
val arguments = parameters.map { parameter ->
|
||||
when {
|
||||
args.containsKey(parameter) -> {
|
||||
args[parameter] ?: throw IllegalArgumentException("Annotation argument value cannot be null ($parameter)")
|
||||
}
|
||||
parameter.isOptional -> null
|
||||
else -> throw IllegalArgumentException("No argument provided for a required parameter: $parameter")
|
||||
}
|
||||
}
|
||||
|
||||
val caller = defaultCaller ?: throw KotlinReflectionInternalError("This callable does not support a default call: $descriptor")
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return reflectionCall {
|
||||
caller.call(arguments.toTypedArray()) as R
|
||||
}
|
||||
}
|
||||
|
||||
private fun defaultPrimitiveValue(type: Type): Any? =
|
||||
if (type is Class<*> && type.isPrimitive) {
|
||||
when (type) {
|
||||
|
||||
@@ -170,10 +170,10 @@ internal class KClassImpl<T : Any>(override val jClass: Class<T>) : KDeclaration
|
||||
override val constructorDescriptors: Collection<ConstructorDescriptor>
|
||||
get() {
|
||||
val descriptor = descriptor
|
||||
if (descriptor.kind == ClassKind.CLASS || descriptor.kind == ClassKind.ENUM_CLASS) {
|
||||
return descriptor.constructors
|
||||
if (descriptor.kind == ClassKind.INTERFACE || descriptor.kind == ClassKind.OBJECT) {
|
||||
return emptyList()
|
||||
}
|
||||
return emptyList()
|
||||
return descriptor.constructors
|
||||
}
|
||||
|
||||
override fun getProperties(name: Name): Collection<PropertyDescriptor> =
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.lang.reflect.Modifier
|
||||
import kotlin.jvm.internal.FunctionImpl
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KotlinReflectionInternalError
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.CallMode.CALL_BY_NAME
|
||||
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.CallMode.POSITIONAL_CALL
|
||||
import kotlin.reflect.jvm.internal.JvmFunctionSignature.*
|
||||
|
||||
internal class KFunctionImpl private constructor(
|
||||
@@ -48,10 +50,14 @@ internal class KFunctionImpl private constructor(
|
||||
|
||||
private fun isDeclared(): Boolean = Visibilities.isPrivate(descriptor.visibility)
|
||||
|
||||
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft {
|
||||
override val caller: FunctionCaller<*> by ReflectProperties.lazySoft caller@ {
|
||||
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
||||
val member: Member? = when (jvmSignature) {
|
||||
is KotlinConstructor -> container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
|
||||
is KotlinConstructor -> {
|
||||
if (isAnnotationConstructor)
|
||||
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL)
|
||||
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
|
||||
}
|
||||
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
|
||||
is JavaMethod -> jvmSignature.method
|
||||
is JavaConstructor -> jvmSignature.constructor
|
||||
@@ -71,14 +77,16 @@ internal class KFunctionImpl private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override val defaultCaller: FunctionCaller<*>? by ReflectProperties.lazySoft {
|
||||
override val defaultCaller: FunctionCaller<*>? by ReflectProperties.lazySoft defaultCaller@ {
|
||||
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
||||
val member: Member? = when (jvmSignature) {
|
||||
is KotlinFunction -> {
|
||||
container.findDefaultMethod(jvmSignature.methodName, jvmSignature.methodDesc,
|
||||
!Modifier.isStatic(caller.member.modifiers), isDeclared())
|
||||
!Modifier.isStatic(caller.member!!.modifiers), isDeclared())
|
||||
}
|
||||
is KotlinConstructor -> {
|
||||
if (isAnnotationConstructor)
|
||||
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME)
|
||||
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
|
||||
}
|
||||
else -> {
|
||||
|
||||
Reference in New Issue
Block a user