[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");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("javaRecordsViaKotlinReflection.kt")
|
||||
public void testJavaRecordsViaKotlinReflection() throws Exception {
|
||||
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/javaRecordsViaKotlinReflection.kt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestMetadata("javaSealedClass.kt")
|
||||
public void testJavaSealedClass() throws Exception {
|
||||
|
||||
+6
@@ -129,6 +129,12 @@ public class IrBlackBoxModernJdkCodegenTestGenerated extends AbstractIrBlackBoxC
|
||||
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
|
||||
@TestMetadata("javaSealedClass.kt")
|
||||
public void testJavaSealedClass() throws Exception {
|
||||
|
||||
+22
-6
@@ -97,9 +97,6 @@ class ReflectJavaClass(
|
||||
.map(::ReflectJavaConstructor)
|
||||
.toList()
|
||||
|
||||
override val recordComponents: Collection<JavaRecordComponent>
|
||||
get() = emptyList()
|
||||
|
||||
override fun hasDefaultConstructor() = false // any default constructor is returned by constructors
|
||||
|
||||
override val lightClassOriginKind: LightClassOriginKind?
|
||||
@@ -118,9 +115,11 @@ class ReflectJavaClass(
|
||||
override val isEnum: Boolean
|
||||
get() = klass.isEnum
|
||||
|
||||
// TODO: Support reflect
|
||||
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
|
||||
get() = Java16SealedRecordLoader.loadIsSealed(klass) ?: false
|
||||
@@ -141,6 +140,8 @@ private object Java16SealedRecordLoader {
|
||||
class Cache(
|
||||
val isSealed: Method?,
|
||||
val getPermittedSubclasses: Method?,
|
||||
val isRecord: Method?,
|
||||
val getRecordComponents: Method?
|
||||
)
|
||||
|
||||
private var _cache: Cache? = null
|
||||
@@ -152,9 +153,11 @@ private object Java16SealedRecordLoader {
|
||||
Cache(
|
||||
clazz.getMethod("isSealed"),
|
||||
clazz.getMethod("getPermittedSubclasses"),
|
||||
clazz.getMethod("isRecord"),
|
||||
clazz.getMethod("getRecordComponents")
|
||||
)
|
||||
} catch (e: NoSuchMethodException) {
|
||||
Cache(null, null)
|
||||
Cache(null, null, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,4 +182,17 @@ private object Java16SealedRecordLoader {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
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