[Reflection] Support java sealed classes in kotlin reflection

^KT-46778
This commit is contained in:
Dmitriy Novozhilov
2021-07-15 18:36:59 +03:00
committed by teamcityserver
parent fa1d09c778
commit 8dad8fa813
5 changed files with 114 additions and 2 deletions
@@ -0,0 +1,21 @@
// WITH_REFLECT
// FILE: Base.java
public sealed class Base permits O, K {}
// FILE: O.java
public final class O extends Base {}
// FILE: K.java
public non-sealed class K extends Base {}
// FILE: main.kt
fun box(): String {
val klass = Base::class
if (!klass.isSealed) return "Error: Base is not sealed"
if (klass.isAbstract) return "Error: Base is not abstract"
return klass.sealedSubclasses
.joinToString("") { it.simpleName ?: "_No name provided_" }
.takeIf { it.isNotBlank() }
?: "_No sealed subclasses found_"
}
@@ -0,0 +1,21 @@
// WITH_REFLECT
// FILE: Base.java
public sealed interface Base permits O, K {}
// FILE: O.java
public non-sealed interface O extends Base {}
// FILE: K.java
public non-sealed interface K extends Base {}
// FILE: main.kt
fun box(): String {
val klass = Base::class
if (!klass.isSealed) return "Error: Base is not sealed"
if (klass.isAbstract) return "Error: Base is abstract"
return klass.sealedSubclasses
.joinToString("") { it.simpleName ?: "_No name provided_" }
.takeIf { it.isNotBlank() }
?: "_No sealed subclasses found_"
}
@@ -146,6 +146,18 @@ public class BlackBoxModernJdkCodegenTestGenerated extends AbstractBlackBoxCodeg
public void testSealedJavaClassViaJavaReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaJavaReflection.kt");
}
@Test
@TestMetadata("sealedJavaClassViaKotlinReflection.kt")
public void testSealedJavaClassViaKotlinReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt");
}
@Test
@TestMetadata("sealedJavaInterfaceViaKotlinReflection.kt")
public void testSealedJavaInterfaceViaKotlinReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt");
}
}
@Nested
@@ -146,6 +146,18 @@ public class IrBlackBoxModernJdkCodegenTestGenerated extends AbstractIrBlackBoxC
public void testSealedJavaClassViaJavaReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaJavaReflection.kt");
}
@Test
@TestMetadata("sealedJavaClassViaKotlinReflection.kt")
public void testSealedJavaClassViaKotlinReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaClassViaKotlinReflection.kt");
}
@Test
@TestMetadata("sealedJavaInterfaceViaKotlinReflection.kt")
public void testSealedJavaInterfaceViaKotlinReflection() throws Exception {
runTest("compiler/testData/codegen/boxModernJdk/testsWithJava17/sealedJavaInterfaceViaKotlinReflection.kt");
}
}
@Nested
@@ -123,10 +123,12 @@ class ReflectJavaClass(
get() = false
override val isSealed: Boolean
get() = false
get() = Java16SealedRecordLoader.loadIsSealed(klass) ?: false
override val permittedTypes: Collection<JavaClassifierType>
get() = emptyList()
get() = Java16SealedRecordLoader.loadGetPermittedSubclasses(klass)
?.map(::ReflectJavaClassifierType)
?: emptyList()
override fun equals(other: Any?) = other is ReflectJavaClass && klass == other.klass
@@ -134,3 +136,47 @@ class ReflectJavaClass(
override fun toString() = this::class.java.name + ": " + klass
}
private object Java16SealedRecordLoader {
class Cache(
val isSealed: Method?,
val getPermittedSubclasses: Method?,
)
private var _cache: Cache? = null
private fun buildCache(): Cache {
val clazz = Class::class.java
return try {
Cache(
clazz.getMethod("isSealed"),
clazz.getMethod("getPermittedSubclasses"),
)
} catch (e: NoSuchMethodException) {
Cache(null, null)
}
}
private fun initCache(): Cache {
var cache = this._cache
if (cache == null) {
cache = buildCache()
this._cache = cache
}
return cache
}
fun loadIsSealed(clazz: Class<*>): Boolean? {
val cache = initCache()
val isSealed = cache.isSealed ?: return null
return isSealed.invoke(clazz) as Boolean
}
fun loadGetPermittedSubclasses(clazz: Class<*>): Array<Class<*>>? {
val cache = initCache()
val getPermittedSubclasses = cache.getPermittedSubclasses ?: return null
@Suppress("UNCHECKED_CAST")
return getPermittedSubclasses.invoke(clazz) as Array<Class<*>>
}
}