[Reflection] Support java records in kotlin reflection
^KT-47760
This commit is contained in:
committed by
teamcityserver
parent
8dad8fa813
commit
17fc1da719
+37
@@ -0,0 +1,37 @@
|
|||||||
|
// WITH_REFLECT
|
||||||
|
// ATTACH_DEBUGGERp
|
||||||
|
// ISSUE: KT-47760
|
||||||
|
|
||||||
|
// FILE: MyRecord.java
|
||||||
|
public record MyRecord(String stringField) {}
|
||||||
|
|
||||||
|
// FILE: main.kt
|
||||||
|
import kotlin.reflect.full.*
|
||||||
|
import kotlin.reflect.KVisibility
|
||||||
|
import kotlin.reflect.jvm.isAccessible
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val expectedValue = "Hello"
|
||||||
|
val obj = MyRecord(expectedValue)
|
||||||
|
|
||||||
|
// stringField() function
|
||||||
|
val function = MyRecord::class.functions.single { it.name == "stringField" }
|
||||||
|
val functionValue = function.call(obj)
|
||||||
|
if (functionValue != expectedValue) {
|
||||||
|
return "Fail: stringField() call returned $functionValue, expected $expectedValue"
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringField field
|
||||||
|
val property = MyRecord::class.memberProperties.single { it.name == "stringField" }
|
||||||
|
if (property.visibility != KVisibility.PRIVATE) {
|
||||||
|
return "Fail: field stringField is not private"
|
||||||
|
}
|
||||||
|
val getter = property.getter
|
||||||
|
getter.isAccessible = true
|
||||||
|
val propertyValue = getter.call(obj)
|
||||||
|
if (propertyValue != expectedValue) {
|
||||||
|
return "Fail: stringField field returned $propertyValue, expected $expectedValue"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
+6
@@ -129,6 +129,12 @@ public class BlackBoxModernJdkCodegenTestGenerated extends AbstractBlackBoxCodeg
|
|||||||
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/compiledJavaSealedInterface.kt");
|
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/compiledJavaSealedInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("javaRecordsViaKotlinReflection.kt")
|
||||||
|
public void testJavaRecordsViaKotlinReflection() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/javaRecordsViaKotlinReflection.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("javaSealedClass.kt")
|
@TestMetadata("javaSealedClass.kt")
|
||||||
public void testJavaSealedClass() throws Exception {
|
public void testJavaSealedClass() throws Exception {
|
||||||
|
|||||||
+6
@@ -129,6 +129,12 @@ public class IrBlackBoxModernJdkCodegenTestGenerated extends AbstractIrBlackBoxC
|
|||||||
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/compiledJavaSealedInterface.kt");
|
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/compiledJavaSealedInterface.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("javaRecordsViaKotlinReflection.kt")
|
||||||
|
public void testJavaRecordsViaKotlinReflection() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/javaRecordsViaKotlinReflection.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("javaSealedClass.kt")
|
@TestMetadata("javaSealedClass.kt")
|
||||||
public void testJavaSealedClass() throws Exception {
|
public void testJavaSealedClass() throws Exception {
|
||||||
|
|||||||
+22
-6
@@ -97,9 +97,6 @@ class ReflectJavaClass(
|
|||||||
.map(::ReflectJavaConstructor)
|
.map(::ReflectJavaConstructor)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
override val recordComponents: Collection<JavaRecordComponent>
|
|
||||||
get() = emptyList()
|
|
||||||
|
|
||||||
override fun hasDefaultConstructor() = false // any default constructor is returned by constructors
|
override fun hasDefaultConstructor() = false // any default constructor is returned by constructors
|
||||||
|
|
||||||
override val lightClassOriginKind: LightClassOriginKind?
|
override val lightClassOriginKind: LightClassOriginKind?
|
||||||
@@ -118,9 +115,11 @@ class ReflectJavaClass(
|
|||||||
override val isEnum: Boolean
|
override val isEnum: Boolean
|
||||||
get() = klass.isEnum
|
get() = klass.isEnum
|
||||||
|
|
||||||
// TODO: Support reflect
|
|
||||||
override val isRecord: Boolean
|
override val isRecord: Boolean
|
||||||
get() = false
|
get() = Java16SealedRecordLoader.loadIsRecord(klass) ?: false
|
||||||
|
|
||||||
|
override val recordComponents: Collection<JavaRecordComponent>
|
||||||
|
get() = (Java16SealedRecordLoader.loadGetRecordComponents(klass) ?: emptyArray()).map(::ReflectJavaRecordComponent)
|
||||||
|
|
||||||
override val isSealed: Boolean
|
override val isSealed: Boolean
|
||||||
get() = Java16SealedRecordLoader.loadIsSealed(klass) ?: false
|
get() = Java16SealedRecordLoader.loadIsSealed(klass) ?: false
|
||||||
@@ -141,6 +140,8 @@ private object Java16SealedRecordLoader {
|
|||||||
class Cache(
|
class Cache(
|
||||||
val isSealed: Method?,
|
val isSealed: Method?,
|
||||||
val getPermittedSubclasses: Method?,
|
val getPermittedSubclasses: Method?,
|
||||||
|
val isRecord: Method?,
|
||||||
|
val getRecordComponents: Method?
|
||||||
)
|
)
|
||||||
|
|
||||||
private var _cache: Cache? = null
|
private var _cache: Cache? = null
|
||||||
@@ -152,9 +153,11 @@ private object Java16SealedRecordLoader {
|
|||||||
Cache(
|
Cache(
|
||||||
clazz.getMethod("isSealed"),
|
clazz.getMethod("isSealed"),
|
||||||
clazz.getMethod("getPermittedSubclasses"),
|
clazz.getMethod("getPermittedSubclasses"),
|
||||||
|
clazz.getMethod("isRecord"),
|
||||||
|
clazz.getMethod("getRecordComponents")
|
||||||
)
|
)
|
||||||
} catch (e: NoSuchMethodException) {
|
} catch (e: NoSuchMethodException) {
|
||||||
Cache(null, null)
|
Cache(null, null, null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,4 +182,17 @@ private object Java16SealedRecordLoader {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
return getPermittedSubclasses.invoke(clazz) as Array<Class<*>>
|
return getPermittedSubclasses.invoke(clazz) as Array<Class<*>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadIsRecord(clazz: Class<*>): Boolean? {
|
||||||
|
val cache = initCache()
|
||||||
|
val isRecord = cache.isRecord ?: return null
|
||||||
|
return isRecord.invoke(clazz) as Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadGetRecordComponents(clazz: Class<*>): Array<Any>? {
|
||||||
|
val cache = initCache()
|
||||||
|
val getRecordComponents = cache.getRecordComponents ?: return null
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return getRecordComponents.invoke(clazz) as Array<Any>?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.descriptors.runtime.structure
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.load.java.structure.JavaRecordComponent
|
||||||
|
import org.jetbrains.kotlin.load.java.structure.JavaType
|
||||||
|
import java.lang.reflect.Member
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
class ReflectJavaRecordComponent(val recordComponent: Any) : ReflectJavaMember(), JavaRecordComponent {
|
||||||
|
override val type: JavaType
|
||||||
|
get() = Java16RecordComponentsLoader.loadGetType(recordComponent)?.let { ReflectJavaClassifierType(it) }
|
||||||
|
?: throw NoSuchMethodError("Can't find `getType` method")
|
||||||
|
override val isVararg: Boolean
|
||||||
|
get() = false
|
||||||
|
override val member: Member
|
||||||
|
get() = Java16RecordComponentsLoader.loadGetAccessor(recordComponent)
|
||||||
|
?: throw NoSuchMethodError("Can't find `getAccessor` method")
|
||||||
|
}
|
||||||
|
|
||||||
|
private object Java16RecordComponentsLoader {
|
||||||
|
class Cache(
|
||||||
|
val getType: Method?,
|
||||||
|
val getAccessor: Method?,
|
||||||
|
)
|
||||||
|
|
||||||
|
private var _cache: Cache? = null
|
||||||
|
|
||||||
|
private fun buildCache(recordComponent: Any): Cache {
|
||||||
|
// Should be Class<RecordComponent>
|
||||||
|
val classOfComponent = recordComponent::class.java
|
||||||
|
|
||||||
|
return try {
|
||||||
|
Cache(
|
||||||
|
classOfComponent.getMethod("getType"),
|
||||||
|
classOfComponent.getMethod("getAccessor"),
|
||||||
|
)
|
||||||
|
} catch (e: NoSuchMethodException) {
|
||||||
|
Cache(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCache(recordComponent: Any): Cache {
|
||||||
|
var cache = this._cache
|
||||||
|
if (cache == null) {
|
||||||
|
cache = buildCache(recordComponent)
|
||||||
|
this._cache = cache
|
||||||
|
}
|
||||||
|
return cache
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadGetType(recordComponent: Any): Class<*>? {
|
||||||
|
val cache = initCache(recordComponent)
|
||||||
|
val getType = cache.getType ?: return null
|
||||||
|
return getType.invoke(recordComponent) as Class<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadGetAccessor(recordComponent: Any): Method? {
|
||||||
|
val cache = initCache(recordComponent)
|
||||||
|
val getType = cache.getAccessor ?: return null
|
||||||
|
return getType.invoke(recordComponent) as Method
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user