Support Java annotation constructors in reflection

#KT-13106 In Progress
This commit is contained in:
Alexander Udalov
2016-09-15 16:12:28 +03:00
parent aeba124bdb
commit 525937252d
7 changed files with 244 additions and 9 deletions
@@ -0,0 +1,78 @@
// WITH_REFLECT
// FILE: J.java
public interface J {
@interface NoParams {}
@interface OneDefault {
String s() default "OK";
}
@interface OneNonDefault {
String s();
}
@interface TwoParamsOneDefault {
String s();
int x() default 42;
}
@interface TwoNonDefaults {
String string();
Class<?> clazz();
}
@interface ManyDefaultParams {
int i() default 0;
String s() default "";
double d() default 3.14;
}
}
// FILE: K.kt
import J.*
import kotlin.reflect.KClass
import kotlin.reflect.primaryConstructor
import kotlin.test.assertEquals
import kotlin.test.assertFails
inline fun <reified T : Annotation> create(args: Map<String, Any?>): T {
val ctor = T::class.constructors.single()
return ctor.callBy(args.mapKeys { entry -> ctor.parameters.single { it.name == entry.key } })
}
inline fun <reified T : Annotation> create(): T = create(emptyMap())
fun box(): String {
create<NoParams>()
val t1 = create<OneDefault>()
assertEquals("OK", t1.s)
assertFails { create<OneDefault>(mapOf("s" to 42)) }
val t2 = create<OneNonDefault>(mapOf("s" to "OK"))
assertEquals("OK", t2.s)
assertFails { create<OneNonDefault>() }
val t3 = create<TwoParamsOneDefault>(mapOf("s" to "OK"))
assertEquals("OK", t3.s)
assertEquals(42, t3.x)
val t4 = create<TwoParamsOneDefault>(mapOf("s" to "OK", "x" to 239))
assertEquals(239, t4.x)
assertFails { create<TwoParamsOneDefault>(mapOf("s" to "Fail", "x" to "Fail")) }
assertFails("KClass (not Class) instances should be passed as arguments") {
create<TwoNonDefaults>(mapOf("clazz" to String::class.java, "string" to "Fail"))
}
val t5 = create<TwoNonDefaults>(mapOf("clazz" to String::class, "string" to "OK"))
assertEquals("OK", t5.string)
val t6 = create<ManyDefaultParams>()
assertEquals(0, t6.i)
assertEquals("", t6.s)
assertEquals(3.14, t6.d)
return "OK"
}
@@ -0,0 +1,90 @@
// WITH_REFLECT
// FILE: J.java
public interface J {
@interface NoParams {}
@interface OneDefault {
String foo() default "foo";
}
@interface OneDefaultValue {
String value() default "value";
}
@interface OneNonDefault {
String foo();
}
@interface OneNonDefaultValue {
String value();
}
@interface TwoParamsOneDefault {
String string();
Class<?> clazz() default Object.class;
}
@interface TwoParamsOneValueOneDefault {
String value();
Class<?> clazz() default Object.class;
}
@interface TwoNonDefaults {
String string();
Class<?> clazz();
}
@interface ManyDefaults {
int i() default 0;
String s() default "";
double d() default 3.14;
}
}
// FILE: K.kt
import J.*
import kotlin.reflect.KClass
import kotlin.reflect.primaryConstructor
import kotlin.test.assertEquals
import kotlin.test.assertFails
inline fun <reified T : Annotation> create(vararg args: Any?): T =
T::class.constructors.single().call(*args)
fun box(): String {
create<NoParams>()
assertFails { create<OneDefault>() }
assertFails { create<OneDefault>("") }
assertFails { create<OneDefault>("", "") }
assertFails { create<OneDefaultValue>() }
create<OneDefaultValue>("")
assertFails { create<OneDefaultValue>("", "") }
assertFails { create<OneNonDefault>() }
assertFails { create<OneNonDefault>("") }
assertFails { create<OneNonDefaultValue>() }
create<OneNonDefaultValue>("")
assertFails { create<TwoParamsOneDefault>() }
assertFails { create<TwoParamsOneDefault>("") }
assertFails { create<TwoParamsOneDefault>("", Any::class) }
assertFails { create<TwoParamsOneDefault>(Any::class, "") }
assertFails { create<TwoParamsOneValueOneDefault>() }
assertFails { create<TwoParamsOneValueOneDefault>("") }
assertFails { create<TwoParamsOneValueOneDefault>("", Any::class) }
assertFails { create<TwoParamsOneValueOneDefault>(Any::class, "") }
assertFails { create<TwoNonDefaults>("", Any::class) }
assertFails { create<TwoNonDefaults>(Any::class, "") }
assertFails { create<ManyDefaults>() }
assertFails { create<ManyDefaults>(42, "Fail", 2.72) }
return "OK"
}
@@ -0,0 +1,15 @@
// WITH_REFLECT
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import kotlin.test.assertEquals
fun box(): String {
val ctor = Retention::class.constructors.single()
val r = ctor.callBy(mapOf(
ctor.parameters.single { it.name == "value" } to RetentionPolicy.RUNTIME
))
assertEquals(RetentionPolicy.RUNTIME, r.value as RetentionPolicy)
assertEquals(Retention::class.java.classLoader, r.javaClass.classLoader)
return "OK"
}
@@ -12156,17 +12156,35 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("callByJava.kt")
public void testCallByJava() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByJava.kt");
doTest(fileName);
}
@TestMetadata("callByKotlin.kt")
public void testCallByKotlin() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callByKotlin.kt");
doTest(fileName);
}
@TestMetadata("callJava.kt")
public void testCallJava() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callJava.kt");
doTest(fileName);
}
@TestMetadata("callKotlin.kt")
public void testCallKotlin() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/callKotlin.kt");
doTest(fileName);
}
@TestMetadata("createJdkAnnotationInstance.kt")
public void testCreateJdkAnnotationInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/reflection/createAnnotation/createJdkAnnotationInstance.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/reflection/enclosing")
@@ -26,17 +26,31 @@ internal class AnnotationConstructorCaller(
private val jClass: Class<*>,
private val parameterNames: List<String>,
private val callMode: CallMode,
origin: Origin,
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 }
enum class Origin { JAVA, KOTLIN }
// 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 }
init {
// TODO: consider lifting this restriction once KT-8957 is implemented
if (callMode == CallMode.POSITIONAL_CALL && origin == Origin.JAVA && (parameterNames - "value").isNotEmpty()) {
throw UnsupportedOperationException(
"Positional call of a Java annotation constructor is allowed only if there are no parameters " +
"or one parameter named \"value\". This restriction exists because Java annotations (in contrast to Kotlin)" +
"do not impose any order on their arguments. Use KCallable#callBy instead."
)
}
}
override fun call(args: Array<*>): Any? {
checkArguments(args)
@@ -28,6 +28,8 @@ 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.AnnotationConstructorCaller.Origin.JAVA
import kotlin.reflect.jvm.internal.AnnotationConstructorCaller.Origin.KOTLIN
import kotlin.reflect.jvm.internal.JvmFunctionSignature.*
internal class KFunctionImpl private constructor(
@@ -55,12 +57,16 @@ internal class KFunctionImpl private constructor(
val member: Member? = when (jvmSignature) {
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL)
return@caller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, POSITIONAL_CALL, KOTLIN)
container.findConstructorBySignature(jvmSignature.constructorDesc, isDeclared())
}
is KotlinFunction -> container.findMethodBySignature(jvmSignature.methodName, jvmSignature.methodDesc, isDeclared())
is JavaMethod -> jvmSignature.method
is JavaConstructor -> jvmSignature.constructor
is FakeJavaAnnotationConstructor -> {
val methods = jvmSignature.methods
return@caller AnnotationConstructorCaller(container.jClass, methods.map { it.name }, POSITIONAL_CALL, JAVA, methods)
}
is BuiltInFunction -> jvmSignature.getMember(container)
}
@@ -86,9 +92,13 @@ internal class KFunctionImpl private constructor(
}
is KotlinConstructor -> {
if (isAnnotationConstructor)
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME)
return@defaultCaller AnnotationConstructorCaller(container.jClass, parameters.map { it.name!! }, CALL_BY_NAME, KOTLIN)
container.findDefaultConstructor(jvmSignature.constructorDesc, isDeclared())
}
is FakeJavaAnnotationConstructor -> {
val methods = jvmSignature.methods
return@defaultCaller AnnotationConstructorCaller(container.jClass, methods.map { it.name }, CALL_BY_NAME, JAVA, methods)
}
else -> {
// Java methods, Java constructors and built-ins don't have $default methods
null
@@ -71,9 +71,15 @@ internal sealed class JvmFunctionSignature {
class JavaConstructor(val constructor: Constructor<*>) : JvmFunctionSignature() {
override fun asString(): String =
"<init>" +
constructor.parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")") { it.desc } +
"V"
constructor.parameterTypes.joinToString(separator = "", prefix = "<init>(", postfix = ")V") { it.desc }
}
class FakeJavaAnnotationConstructor(val jClass: Class<*>) : JvmFunctionSignature() {
// Java annotations do not impose any order of methods inside them, so we consider them lexicographic here for stability
val methods = jClass.declaredMethods.sortedBy { it.name }
override fun asString(): String =
methods.joinToString(separator = "", prefix = "<init>(", postfix = ")V") { it.returnType.desc }
}
open class BuiltInFunction(private val signature: String) : JvmFunctionSignature() {
@@ -189,10 +195,14 @@ internal object RuntimeTypeMapper {
return JvmFunctionSignature.JavaMethod(method)
}
is JavaClassConstructorDescriptor -> {
val constructor = ((function.source as? JavaSourceElement)?.javaElement as? ReflectJavaConstructor)?.member ?:
throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function")
return JvmFunctionSignature.JavaConstructor(constructor)
val element = (function.source as? JavaSourceElement)?.javaElement
when {
element is ReflectJavaConstructor ->
return JvmFunctionSignature.JavaConstructor(element.member)
element is ReflectJavaClass && element.isAnnotationType ->
return JvmFunctionSignature.FakeJavaAnnotationConstructor(element.element)
else -> throw KotlinReflectionInternalError("Incorrect resolution sequence for Java constructor $function ($element)")
}
}
else -> throw KotlinReflectionInternalError("Unknown origin of $function (${function.javaClass})")
}