From 64fdb18ad3f5b310af291952cceae9aba0ea7734 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 19 Feb 2015 17:27:58 +0300 Subject: [PATCH] Use descriptors for reflection on class properties - use ConcurrentHashMap as a cache of class loaders to module descriptors - KClassImpl now has a lazy class descriptor and it manages property creation by looking (also lazily) for the property descriptor in the corresponding scope - since deserialized descriptors have full information about where a JVM symbol is located and what signature it has, new tests will begin to pass where Kotlin model and Java reflection model differ, see classObjectVar.kt --- .../property/classObjectVar.kt | 15 ++++ ...lackBoxWithStdlibCodegenTestGenerated.java | 6 ++ .../structure/reflect/reflectStructureUtil.kt | 18 ++++- .../src/kotlin/reflect/exceptions.kt | 12 ++- .../jvm/internal/DescriptorBasedProperty.kt | 67 ++++++++++++++++ .../jvm/internal/KCallableContainerImpl.kt | 40 ++++++++++ .../kotlin/reflect/jvm/internal/KClassImpl.kt | 56 ++++++++++---- .../jvm/internal/KMemberPropertyImpl.kt | 72 +++++------------ .../jvm/internal/ReflectionObjectRenderer.kt | 51 ++++++++++++ .../jvm/internal/moduleByClassLoader.kt | 68 ++++++++++++++++ .../src/kotlin/reflect/jvm/internal/util.kt | 77 +++++++++++++++++++ 11 files changed, 409 insertions(+), 73 deletions(-) create mode 100644 compiler/testData/codegen/boxWithStdlib/callableReference/property/classObjectVar.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/DescriptorBasedProperty.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionObjectRenderer.kt create mode 100644 core/reflection.jvm/src/kotlin/reflect/jvm/internal/moduleByClassLoader.kt diff --git a/compiler/testData/codegen/boxWithStdlib/callableReference/property/classObjectVar.kt b/compiler/testData/codegen/boxWithStdlib/callableReference/property/classObjectVar.kt new file mode 100644 index 00000000000..e8cf75aa2a4 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/callableReference/property/classObjectVar.kt @@ -0,0 +1,15 @@ +class A { + default object B { + var state: String = "12345" + } +} + +fun box(): String { + val p = A.B::state + + if (p.name != "state") return "Fail state: ${p.name}" + if (p.get(A.B) != "12345") return "Fail value: ${p.get(A.B)}" + p.set(A.B, "OK") + + return p[A.B] +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index 0312ccbc5ce..a9d74f2c840 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -741,6 +741,12 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/callableReference/property"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("classObjectVar.kt") + public void testClassObjectVar() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/callableReference/property/classObjectVar.kt"); + doTestWithStdlib(fileName); + } + @TestMetadata("delegated.kt") public void testDelegated() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/callableReference/property/delegated.kt"); diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt index 6766f8aa41a..985a0449155 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/reflectStructureUtil.kt @@ -21,7 +21,9 @@ import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.load.java.JavaVisibilities import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.Name +import java.lang.reflect.Array import java.lang.reflect.Modifier private fun calculateVisibility(modifiers: Int): Visibility { @@ -34,6 +36,9 @@ private fun calculateVisibility(modifiers: Int): Visibility { } } +public val Class<*>.classLoader: ClassLoader + get() = getClassLoader() ?: ClassLoader.getSystemClassLoader() + public fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean = javaClass>().isAssignableFrom(this) @@ -41,4 +46,15 @@ public val Class<*>.fqName: FqName get() = classId.asSingleFqName().toSafe() public val Class<*>.classId: ClassId - get() = getDeclaringClass()?.classId?.createNestedClassId(Name.identifier(getSimpleName())) ?: ClassId.topLevel(FqName(getName())) + get() = when { + isPrimitive() -> throw IllegalArgumentException("Can't compute ClassId for primitive type: $this") + isArray() -> throw IllegalArgumentException("Can't compute ClassId for array type: $this") + getEnclosingMethod() != null, getEnclosingConstructor() != null, getSimpleName().isEmpty() -> { + val fqName = FqName(getName()) + ClassId(fqName.parent(), FqNameUnsafe.topLevel(fqName.shortName()), /* local = */ true) + } + else -> getDeclaringClass()?.classId?.createNestedClassId(Name.identifier(getSimpleName())) ?: ClassId.topLevel(FqName(getName())) + } + +public fun Class<*>.createArrayType(): Class<*> = + Array.newInstance(this, 0).javaClass diff --git a/core/reflection.jvm/src/kotlin/reflect/exceptions.kt b/core/reflection.jvm/src/kotlin/reflect/exceptions.kt index c8632479a8e..7201f1efe04 100644 --- a/core/reflection.jvm/src/kotlin/reflect/exceptions.kt +++ b/core/reflection.jvm/src/kotlin/reflect/exceptions.kt @@ -16,16 +16,20 @@ package kotlin.reflect -suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") public class IllegalPropertyAccessException(cause: IllegalAccessException) : Exception(cause.getMessage()) { { + [suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")] (this as java.lang.Throwable).initCause(cause) } } -suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") -public class NoSuchPropertyException(cause: Exception) : Exception() { +public class NoSuchPropertyException(cause: Exception? = null) : Exception() { { - (this as java.lang.Throwable).initCause(cause) + [suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")] + if (cause != null) { + (this as java.lang.Throwable).initCause(cause) + } } } + +public class KotlinReflectionInternalError(message: String) : Error(message) diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/DescriptorBasedProperty.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/DescriptorBasedProperty.kt new file mode 100644 index 00000000000..b3ddba51a7a --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/DescriptorBasedProperty.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.reflect.jvm.internal + +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.serialization.ProtoBuf +import org.jetbrains.kotlin.serialization.deserialization.NameResolver +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor +import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf +import java.lang.reflect.Field +import java.lang.reflect.Method +import kotlin.reflect.KotlinReflectionInternalError + +abstract class DescriptorBasedProperty(computeDescriptor: () -> PropertyDescriptor) { + protected abstract val container: KCallableContainerImpl + + data class PropertyProtoData( + val proto: ProtoBuf.Callable, + val nameResolver: NameResolver, + val signature: JvmProtoBuf.JvmPropertySignature + ) + + protected val descriptor: PropertyDescriptor by ReflectProperties.lazySoft { computeDescriptor() } + + protected val protoData: PropertyProtoData by ReflectProperties.lazyWeak { + val property = DescriptorUtils.unwrapFakeOverride(descriptor) as? DeserializedPropertyDescriptor + ?: throw KotlinReflectionInternalError("Member property resolved incorrectly: $descriptor") + val proto = property.proto + if (!proto.hasExtension(JvmProtoBuf.propertySignature)) { + throw KotlinReflectionInternalError("Member property lacks JVM signature: $descriptor") + } + PropertyProtoData(proto, property.nameResolver, proto.getExtension(JvmProtoBuf.propertySignature)) + } + + val field: Field? by ReflectProperties.lazySoft { + val proto = protoData + if (!proto.signature.hasField()) null + else container.findFieldBySignature(proto.signature.getField(), proto.nameResolver) + } + + val getter: Method? by ReflectProperties.lazySoft { + val proto = protoData + if (!proto.signature.hasGetter()) null + else container.findMethodBySignature(proto.signature.getGetter(), proto.nameResolver) + } + + val setter: Method? by ReflectProperties.lazySoft { + val proto = protoData + if (!proto.signature.hasSetter()) null + else container.findMethodBySignature(proto.signature.getSetter(), proto.nameResolver) + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt new file mode 100644 index 00000000000..00cc920b40d --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KCallableContainerImpl.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.reflect.jvm.internal + +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.scopes.JetScope +import kotlin.reflect.KotlinReflectionInternalError + +abstract class KCallableContainerImpl { + abstract val jClass: Class<*> + + abstract val scope: JetScope + + protected fun findPropertyDescriptor(name: String): () -> PropertyDescriptor = { + val properties = scope.getProperties(Name.identifier(name)) + if (properties.size() != 1) { + throw KotlinReflectionInternalError( + if (properties.isEmpty()) "Property $name not resolved in $this" + else "${properties.size()} properties with name $name resolved in $this" + ) + } + + properties.single() as PropertyDescriptor + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt index 7ea41948d7a..ba3b9153d49 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KClassImpl.kt @@ -16,8 +16,15 @@ package kotlin.reflect.jvm.internal -import kotlin.reflect.* +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.load.java.structure.reflect.classId +import org.jetbrains.kotlin.resolve.scopes.JetScope +import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies import kotlin.jvm.internal.KotlinClass +import kotlin.reflect.KClass +import kotlin.reflect.KMemberProperty +import kotlin.reflect.KMutableMemberProperty +import kotlin.reflect.KotlinReflectionInternalError enum class KClassOrigin { BUILT_IN @@ -25,10 +32,21 @@ enum class KClassOrigin { FOREIGN } -class KClassImpl(val jClass: Class) : KClass { +class KClassImpl(override val jClass: Class) : KCallableContainerImpl(), KClass { // Don't use kotlin.properties.Delegates here because it's a Kotlin class which will invoke KClassImpl() in , // resulting in infinite recursion + val descriptor by ReflectProperties.lazySoft {(): ClassDescriptor -> + val moduleData = jClass.getOrCreateModule() + + val found = moduleData.module.findClassAcrossModuleDependencies(jClass.classId) + if (found != null) return@lazySoft found + + throw KotlinReflectionInternalError("Class not resolved: $jClass") + } + + override val scope: JetScope get() = descriptor.getDefaultType().getMemberScope() + private val origin by ReflectProperties.lazy {(): KClassOrigin -> if (jClass.isAnnotationPresent(javaClass())) { KClassOrigin.KOTLIN @@ -39,21 +57,27 @@ class KClassImpl(val jClass: Class) : KClass { } } - fun memberProperty(name: String): KMemberProperty = - if (origin === KClassOrigin.KOTLIN) { - KMemberPropertyImpl(name, this) - } - else { - KForeignMemberProperty(name, this) - } + fun memberProperty(name: String): KMemberProperty { + val computeDescriptor = findPropertyDescriptor(name) - fun mutableMemberProperty(name: String): KMutableMemberProperty = - if (origin === KClassOrigin.KOTLIN) { - KMutableMemberPropertyImpl(name, this) - } - else { - KMutableForeignMemberProperty(name, this) - } + if (origin === KClassOrigin.KOTLIN) { + return KMemberPropertyImpl(this, computeDescriptor) + } + else { + return KForeignMemberProperty(name, this) + } + } + + fun mutableMemberProperty(name: String): KMutableMemberProperty { + val computeDescriptor = findPropertyDescriptor(name) + + if (origin === KClassOrigin.KOTLIN) { + return KMutableMemberPropertyImpl(this, computeDescriptor) + } + else { + return KMutableForeignMemberProperty(name, this) + } + } override fun equals(other: Any?): Boolean = other is KClassImpl<*> && jClass == other.jClass diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KMemberPropertyImpl.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KMemberPropertyImpl.kt index a952bc6d715..627fd382b74 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KMemberPropertyImpl.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/KMemberPropertyImpl.kt @@ -16,39 +16,22 @@ package kotlin.reflect.jvm.internal -import java.lang.reflect.* -import kotlin.reflect.* - -// TODO: properties of built-in classes +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import kotlin.reflect.IllegalPropertyAccessException +import kotlin.reflect.KMemberProperty +import kotlin.reflect.KMutableMemberProperty open class KMemberPropertyImpl( - override val name: String, - protected val owner: KClassImpl -) : KMemberProperty, KPropertyImpl { - // TODO: extract, make lazy (weak?), use our descriptors knowledge - override val field: Field? - override val getter: Method? - - { - try { - field = owner.jClass.getDeclaredField(name) - } - catch (e: NoSuchFieldException) { - field = null - } - - try { - getter = owner.jClass.getMaybeDeclaredMethod(getterName(name)) - } - catch (e: NoSuchMethodException) { - if (field == null) throw NoSuchPropertyException(e) - getter = null - } - } + override val container: KClassImpl, + computeDescriptor: () -> PropertyDescriptor +) : DescriptorBasedProperty(computeDescriptor), KMemberProperty, KPropertyImpl { + override val name: String get() = descriptor.getName().asString() override fun get(receiver: T): R { try { - return (if (getter != null) getter!!(receiver) else field!!.get(receiver)) as R + val getter = getter + [suppress("UNCHECKED_CAST")] + return if (getter != null) getter(receiver) as R else field!!.get(receiver) as R } catch (e: IllegalAccessException) { throw IllegalPropertyAccessException(e) @@ -56,42 +39,27 @@ open class KMemberPropertyImpl( } override fun equals(other: Any?): Boolean = - other is KMemberPropertyImpl<*, *> && name == other.name && owner == other.owner + other is KMemberPropertyImpl<*, *> && descriptor == other.descriptor override fun hashCode(): Int = - name.hashCode() * 31 + owner.hashCode() + descriptor.hashCode() - // TODO: include visibility, return type override fun toString(): String = - "val ${owner.jClass.getName()}.$name" + ReflectionObjectRenderer.renderProperty(descriptor) } + class KMutableMemberPropertyImpl( - name: String, - owner: KClassImpl -) : KMutableMemberProperty, KMutablePropertyImpl, KMemberPropertyImpl(name, owner) { - override val setter: Method? - - { - try { - val returnType = if (getter != null) getter.getReturnType() else field!!.getType() - setter = owner.jClass.getMaybeDeclaredMethod(setterName(name), returnType) - } - catch (e: NoSuchMethodException) { - if (field == null) throw NoSuchPropertyException(e) - setter = null - } - } - + container: KClassImpl, + computeDescriptor: () -> PropertyDescriptor +) : KMemberPropertyImpl(container, computeDescriptor), KMutableMemberProperty, KMutablePropertyImpl { override fun set(receiver: T, value: R) { try { - if (setter != null) setter!!(receiver, value) else field!!.set(receiver, value) + val setter = setter + if (setter != null) setter(receiver, value) else field!!.set(receiver, value) } catch (e: IllegalAccessException) { throw IllegalPropertyAccessException(e) } } - - override fun toString(): String = - "var ${owner.jClass.getName()}.$name" } diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionObjectRenderer.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionObjectRenderer.kt new file mode 100644 index 00000000000..f4eda03bdba --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/ReflectionObjectRenderer.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.reflect.jvm.internal + +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor +import org.jetbrains.kotlin.renderer.DescriptorRenderer + +object ReflectionObjectRenderer { + private val renderer = DescriptorRenderer.FQ_NAMES_IN_TYPES + + // TODO: include visibility, return type + fun renderProperty(descriptor: PropertyDescriptor): String { + fun StringBuilder.appendReceiverType(receiver: ReceiverParameterDescriptor?) { + if (receiver != null) { + append(renderer.renderType(receiver.getType())) + append(".") + } + } + + return StringBuilder { + append(if (descriptor.isVar()) "var " else "val ") + + val dispatchReceiver = descriptor.getDispatchReceiverParameter() + val extensionReceiver = descriptor.getExtensionReceiverParameter() + + appendReceiverType(dispatchReceiver) + + val addParentheses = dispatchReceiver != null && extensionReceiver != null + if (addParentheses) append("(") + appendReceiverType(extensionReceiver) + if (addParentheses) append(")") + + append(renderer.renderName(descriptor.getName())) + }.toString() + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/moduleByClassLoader.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/moduleByClassLoader.kt new file mode 100644 index 00000000000..de9e272a899 --- /dev/null +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/moduleByClassLoader.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.reflect.jvm.internal + +import org.jetbrains.kotlin.load.java.structure.reflect.classLoader +import org.jetbrains.kotlin.load.kotlin.reflect.RuntimeModuleData +import java.lang.ref.WeakReference +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +// TODO: collect nulls periodically +private val moduleByClassLoader: ConcurrentMap> = ConcurrentHashMap() + +private class WeakClassLoaderBox(classLoader: ClassLoader) { + val ref: WeakReference = WeakReference(classLoader) + + // Identity hash code is saved because otherwise once the weak reference is GC'd we cannot compute it anymore + val identityHashCode: Int = System.identityHashCode(classLoader) + + // Temporary strong reference to the class loader to ensure it won't get GC'd when we're inserting this box into the map + var temporaryStrongRef: ClassLoader? = classLoader + + override fun equals(other: Any?) = + other is WeakClassLoaderBox && ref.get() === other.ref.get() + + override fun hashCode() = + identityHashCode +} + +private fun Class<*>.getOrCreateModule(): RuntimeModuleData { + val classLoader = this.classLoader + val key = WeakClassLoaderBox(classLoader) + + val cached = moduleByClassLoader[key] + if (cached != null) { + cached.get()?.let { return it } + moduleByClassLoader.remove(key, cached) + } + + val module = RuntimeModuleData.create(classLoader) + try { + while (true) { + val ref = moduleByClassLoader.putIfAbsent(key, WeakReference(module)) + if (ref == null) return module + + val result = ref.get() + if (result != null) return result + moduleByClassLoader.remove(key, ref) + } + } + finally { + key.temporaryStrongRef = null + } +} diff --git a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt index ffa445f2a92..d297d8e8a62 100644 --- a/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt +++ b/core/reflection.jvm/src/kotlin/reflect/jvm/internal/util.kt @@ -16,7 +16,14 @@ package kotlin.reflect.jvm.internal +import org.jetbrains.kotlin.load.java.structure.reflect.classLoader +import org.jetbrains.kotlin.load.java.structure.reflect.createArrayType +import org.jetbrains.kotlin.serialization.deserialization.NameResolver +import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf +import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf.JvmType.PrimitiveType.* +import java.lang.reflect.Field import java.lang.reflect.Method +import kotlin.reflect.KotlinReflectionInternalError private fun String.capitalizeWithJavaBeanConvention(): String { if (length() > 1 && Character.isUpperCase(this[1])) return this @@ -36,3 +43,73 @@ private fun Class<*>.getMaybeDeclaredMethod(name: String, vararg parameterTypes: return getDeclaredMethod(name, *parameterTypes) } } + + +// TODO: check resulting method's return type +private fun KCallableContainerImpl.findMethodBySignature(signature: JvmProtoBuf.JvmMethodSignature, nameResolver: NameResolver): Method? { + val name = nameResolver.getString(signature.getName()) + val classLoader = jClass.classLoader + val parameterTypes = signature.getParameterTypeList().map { jvmType -> + loadJvmType(jvmType, nameResolver, classLoader) + }.copyToArray() + return try { + jClass.getMaybeDeclaredMethod(name, *parameterTypes) + } + catch (e: NoSuchMethodException) { + null + } +} + +// TODO: check resulting field's type +private fun KCallableContainerImpl.findFieldBySignature(signature: JvmProtoBuf.JvmFieldSignature, nameResolver: NameResolver): Field? { + val name = nameResolver.getString(signature.getName()) + val owner = + if (signature.getIsStaticInOuter()) + jClass.getEnclosingClass() + ?: throw KotlinReflectionInternalError("Inconsistent metadata for field $name in $jClass") + else jClass + + return try { + owner.getDeclaredField(name) + } + catch (e: NoSuchFieldException) { + null + } +} + + +private val PRIMITIVE_TYPES = mapOf( + VOID to Void.TYPE, + BOOLEAN to java.lang.Boolean.TYPE, + CHAR to java.lang.Character.TYPE, + BYTE to java.lang.Byte.TYPE, + SHORT to java.lang.Short.TYPE, + INT to java.lang.Integer.TYPE, + FLOAT to java.lang.Float.TYPE, + LONG to java.lang.Long.TYPE, + DOUBLE to java.lang.Double.TYPE +) + +private fun loadJvmType( + type: JvmProtoBuf.JvmType, + nameResolver: NameResolver, + classLoader: ClassLoader, + arrayDimension: Int = type.getArrayDimension() +): Class<*> { + if (arrayDimension > 0) { + // TODO: test multi-dimensional arrays + return loadJvmType(type, nameResolver, classLoader, arrayDimension - 1).createArrayType() + } + + if (type.hasPrimitiveType()) { + return PRIMITIVE_TYPES[type.getPrimitiveType()] + ?: throw KotlinReflectionInternalError("Unknown primitive type: ${type.getPrimitiveType()}") + } + + if (type.hasClassFqName()) { + val fqName = nameResolver.getFqName(type.getClassFqName()) + return classLoader.loadClass(fqName.asString()) + } + + throw KotlinReflectionInternalError("Inconsistent metadata for JVM type") +}