[Reflection] Support java records in kotlin reflection

^KT-47760
This commit is contained in:
Dmitriy Novozhilov
2021-07-16 10:55:43 +03:00
committed by teamcityserver
parent 8dad8fa813
commit 17fc1da719
5 changed files with 138 additions and 6 deletions
@@ -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"
}
@@ -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 {
@@ -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 {
@@ -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>?
}
}
@@ -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
}
}