From 73e4287aee71da994a946659966c69d321106f9e Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Tue, 26 Aug 2014 16:18:56 +0400 Subject: [PATCH] Initial support of annotation loading at runtime In order to locate an annotated entity, we need to implement almost the whole Java element model (which will be used anyway for Java descriptor loading) --- .../annotations/StringConstantInParam.java | 2 + .../compiledJava/static/StaticFinal.java | 9 +- .../compiledJava/static/StaticFinal.txt | 7 +- .../AbstractJvmRuntimeDescriptorLoaderTest.kt | 19 +- .../load/java/structure/JavaMethod.java | 2 + .../structure/reflect/ReflectJavaArrayType.kt | 24 ++ .../structure/reflect/ReflectJavaClass.kt | 20 +- .../reflect/ReflectJavaClassifierType.kt | 7 +- .../structure/reflect/ReflectJavaField.kt | 45 ++++ .../structure/reflect/ReflectJavaMember.kt | 35 +++ .../structure/reflect/ReflectJavaMethod.kt | 61 ++++++ .../reflect/ReflectJavaPrimitiveType.kt | 23 ++ .../java/structure/reflect/ReflectJavaType.kt | 21 +- .../reflect/ReflectJavaTypeParameter.kt | 9 +- .../reflect/ReflectJavaValueParameter.kt | 41 ++++ .../reflect/ReflectJavaWildcardType.kt | 39 ++++ .../load/kotlin/reflect/ReflectKotlinClass.kt | 205 ++++++++++++++++-- .../reflect/ReflectKotlinClassFinder.kt | 7 +- 18 files changed, 524 insertions(+), 52 deletions(-) create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaArrayType.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaField.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMethod.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaPrimitiveType.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt create mode 100644 core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaWildcardType.kt diff --git a/compiler/testData/loadJava/compiledJava/annotations/StringConstantInParam.java b/compiler/testData/loadJava/compiledJava/annotations/StringConstantInParam.java index 9462a7d97f7..8ddf3917063 100644 --- a/compiler/testData/loadJava/compiledJava/annotations/StringConstantInParam.java +++ b/compiler/testData/loadJava/compiledJava/annotations/StringConstantInParam.java @@ -1,3 +1,5 @@ +// SKIP_IN_RUNTIME_TEST because there's no stable way to determine if a field is initialized with a non-null value in runtime + package test; public interface StringConstantInParam { diff --git a/compiler/testData/loadJava/compiledJava/static/StaticFinal.java b/compiler/testData/loadJava/compiledJava/static/StaticFinal.java index 77321894522..629fb32fd8d 100644 --- a/compiler/testData/loadJava/compiledJava/static/StaticFinal.java +++ b/compiler/testData/loadJava/compiledJava/static/StaticFinal.java @@ -1,5 +1,12 @@ +// SKIP_IN_RUNTIME_TEST because there's no stable way to determine if a field is initialized with a non-null value in runtime + package test; public class StaticFinal { - public static final String foo = "aaa"; + public static final String publicNonNull = "aaa"; + public static final String publicNull = null; + static final String packageNonNull = "bbb"; + static final String packageNull = null; + private static final String privateNonNull = "bbb"; + private static final String privateNull = null; } diff --git a/compiler/testData/loadJava/compiledJava/static/StaticFinal.txt b/compiler/testData/loadJava/compiledJava/static/StaticFinal.txt index 5e53daafac6..28485dcc39b 100644 --- a/compiler/testData/loadJava/compiledJava/static/StaticFinal.txt +++ b/compiler/testData/loadJava/compiledJava/static/StaticFinal.txt @@ -4,5 +4,10 @@ public open class StaticFinal { public constructor StaticFinal() // Static members - public final val foo: kotlin.String = "aaa" + public/*package*/ final val packageNonNull: kotlin.String = "bbb" + public/*package*/ final val packageNull: kotlin.String! + private final val privateNonNull: kotlin.String = "bbb" + private final val privateNull: kotlin.String! + public final val publicNonNull: kotlin.String = "aaa" + public final val publicNull: kotlin.String! } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt index 9e45e82ea28..7efd66013d6 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/jvm/runtime/AbstractJvmRuntimeDescriptorLoaderTest.kt @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.jvm.runtime -import com.intellij.openapi.util.io.FileUtil import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAllTo import org.jetbrains.kotlin.codegen.GeneratedClassLoader import org.jetbrains.kotlin.codegen.GenerationUtils @@ -64,11 +63,13 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi .build() } + // NOTE: this test does a dirty hack of text substitution to make all annotations defined in source code retain at runtime. + // Specifically each "annotation class" is replaced by "Retention(RUNTIME) annotation class" protected fun doTest(ktFileName: String) { val ktFile = File(ktFileName) val environment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable, ConfigurationKind.ALL) - val jetFile = JetTestUtils.createFile(ktFileName, FileUtil.loadFile(ktFile, true), environment.getProject()) + val jetFile = JetTestUtils.createFile(ktFileName, loadFileAddingRuntimeRetention(ktFile), environment.getProject()) val classFileFactory = GenerationUtils.compileFileGetClassFileFactoryForTest(jetFile) val classLoader = GeneratedClassLoader(classFileFactory, null, ForTestCompileRuntime.runtimeJarForTests().toURI().toURL()) @@ -103,7 +104,7 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi val klass = classLoader.loadClass(className).sure("Couldn't load class $className") - when (ReflectKotlinClass(klass).getClassHeader().kind) { + when (ReflectKotlinClass.create(klass)!!.getClassHeader().kind) { KotlinClassHeader.Kind.PACKAGE_FACADE -> { val packageView = module.getPackage(actual.getFqName()) ?: error("Couldn't resolve package ${actual.getFqName()}") for (descriptor in packageView.getMemberScope().getAllDescriptors()) { @@ -125,13 +126,21 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi val expected = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(tmpdir, getTestRootDisposable(), ConfigurationKind.ALL) - val configuration = RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT + val comparatorConfiguration = RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT .checkPrimaryConstructors(true) .checkPropertyAccessors(true) .withRenderer(renderer) - RecursiveDescriptorComparator.validateAndCompareDescriptors(expected.first, actual, configuration, null) + RecursiveDescriptorComparator.validateAndCompareDescriptors(expected.first, actual, comparatorConfiguration, null) } + private fun loadFileAddingRuntimeRetention(file: File): String { + return file.readText().replace( + "annotation class", + "[java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)] annotation class" + ) + } + + // Resolves not only top level classes, but also nested classes, including class objects and classes nested within them private fun resolveClassByFqNameInModule(module: ModuleDescriptor, fqName: FqNameUnsafe): ClassDescriptor? { if (fqName.isRoot()) return null diff --git a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/structure/JavaMethod.java b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/structure/JavaMethod.java index 3f4727dd251..1511b7bbc2d 100644 --- a/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/structure/JavaMethod.java +++ b/core/descriptor.loader.java/src/org/jetbrains/kotlin/load/java/structure/JavaMethod.java @@ -18,11 +18,13 @@ package org.jetbrains.kotlin.load.java.structure; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.ReadOnly; import java.util.List; public interface JavaMethod extends JavaMember, JavaTypeParameterListOwner { @NotNull + @ReadOnly List getValueParameters(); boolean hasAnnotationParameterDefaultValue(); diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaArrayType.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaArrayType.kt new file mode 100644 index 00000000000..5005d9548bc --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaArrayType.kt @@ -0,0 +1,24 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import java.lang.reflect.GenericArrayType +import org.jetbrains.kotlin.load.java.structure.JavaArrayType + +public class ReflectJavaArrayType(private val arrayType: GenericArrayType) : ReflectJavaType(), JavaArrayType { + override fun getComponentType() = ReflectJavaType.create(arrayType.getGenericComponentType()!!) +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt index 652d56fbfad..f11fddeb5e6 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClass.kt @@ -23,10 +23,10 @@ import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.utils.emptyOrSingletonList public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(), JavaClass { - override fun getInnerClasses(): Collection { - // TODO - return listOf() - } + override fun getInnerClasses() = + klass.getDeclaredClasses() + .filter { it.getSimpleName().isNotEmpty() } + .map { ReflectJavaClass(it) } override fun getFqName(): FqName? { // TODO: can there be primitive types, arrays? @@ -41,18 +41,12 @@ public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement( override fun getSupertypes(): Collection { // TODO: also call getSuperclass() / getInterfaces() for classes without generic signature val supertypes = emptyOrSingletonList(klass.getGenericSuperclass()) + klass.getGenericInterfaces() - return supertypes.map { supertype -> ReflectJavaClassifierType(supertype) } + return supertypes.map { supertype -> ReflectJavaType.create(supertype) as JavaClassifierType } } - override fun getMethods(): Collection { - // TODO - return listOf() - } + override fun getMethods() = klass.getDeclaredMethods().map { method -> ReflectJavaMethod(method) } - override fun getFields(): Collection { - // TODO - return listOf() - } + override fun getFields() = klass.getDeclaredFields().map { field -> ReflectJavaField(field) } override fun getConstructors(): Collection { // TODO diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClassifierType.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClassifierType.kt index 41ff58708b7..a1694e15572 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClassifierType.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaClassifierType.kt @@ -29,9 +29,8 @@ public class ReflectJavaClassifierType(val classifierType: Type) : ReflectJavaTy return when (classifierType) { is Class<*> -> ReflectJavaClass(classifierType) is TypeVariable<*> -> ReflectJavaTypeParameter(classifierType) - // TODO is ParameterizedType -> ReflectJavaClass(classifierType.getRawType() as Class<*>) - else -> throw UnsupportedOperationException("Unsupported type (${classifierType.javaClass}): $classifierType") + else -> throw IllegalStateException("Not a classifier type (${classifierType.javaClass}): $classifierType") } } @@ -44,7 +43,9 @@ public class ReflectJavaClassifierType(val classifierType: Type) : ReflectJavaTy override fun isRaw(): Boolean = with(classifierType) { this is Class<*> && getTypeParameters().isNotEmpty() } override fun getTypeArguments(): List { - // TODO + if (classifierType is ParameterizedType) { + return classifierType.getActualTypeArguments()!!.map { ReflectJavaType.create(it) } + } return listOf() } } diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaField.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaField.kt new file mode 100644 index 00000000000..84c9a220b98 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaField.kt @@ -0,0 +1,45 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import java.lang.reflect.Field +import org.jetbrains.kotlin.load.java.structure.JavaField +import org.jetbrains.kotlin.load.java.structure.JavaAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.load.java.structure.JavaType + +public class ReflectJavaField(field: Field) : ReflectJavaMember(field), JavaField { + val field: Field + get() = member as Field + + override fun getAnnotations(): Collection { + // TODO + return listOf() + } + + override fun findAnnotation(fqName: FqName): JavaAnnotation? { + // TODO + return null + } + + override fun isEnumEntry() = field.isEnumConstant() + + override fun getType(): JavaType { + // TODO + throw UnsupportedOperationException() + } +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt new file mode 100644 index 00000000000..df985582a59 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMember.kt @@ -0,0 +1,35 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import java.lang.reflect.Member +import java.lang.reflect.Modifier +import org.jetbrains.kotlin.load.java.structure.JavaMember +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames + +public abstract class ReflectJavaMember(protected val member: Member) : ReflectJavaElement(), JavaMember { + override fun getName() = member.getName()?.let { Name.identifier(it) } ?: SpecialNames.NO_NAME_PROVIDED + + override fun getContainingClass() = ReflectJavaClass(member.getDeclaringClass()) + + override fun isAbstract() = Modifier.isAbstract(member.getModifiers()) + override fun isStatic() = Modifier.isStatic(member.getModifiers()) + override fun isFinal() = Modifier.isFinal(member.getModifiers()) + + override fun getVisibility() = calculateVisibility(member.getModifiers()) +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMethod.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMethod.kt new file mode 100644 index 00000000000..f574916dff2 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaMethod.kt @@ -0,0 +1,61 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import java.lang.reflect.Method +import java.util.ArrayList +import org.jetbrains.kotlin.load.java.structure.JavaMethod +import org.jetbrains.kotlin.load.java.structure.JavaAnnotation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.load.java.structure.JavaValueParameter +import org.jetbrains.kotlin.load.java.structure.JavaType + +public class ReflectJavaMethod(method: Method) : ReflectJavaMember(method), JavaMethod { + private val method: Method + get() = member as Method + + override fun getAnnotations(): Collection { + // TODO + return listOf() + } + + override fun findAnnotation(fqName: FqName): JavaAnnotation? { + // TODO + return null + } + + override fun getValueParameters(): List { + val types = method.getGenericParameterTypes()!! + val annotations = method.getParameterAnnotations() + val result = ArrayList() + val methodIsVararg = method.isVarArgs() + for (i in types.indices) { + val isVararg = methodIsVararg && i == types.lastIndex + result.add(ReflectJavaValueParameter(ReflectJavaType.create(types[i]), annotations[i], isVararg)) + } + return result + } + + override fun getReturnType(): JavaType? { + // TODO + return ReflectJavaType.create(method.getGenericReturnType()!!) + } + + override fun hasAnnotationParameterDefaultValue() = method.getDefaultValue() != null + + override fun getTypeParameters() = method.getTypeParameters().map { ReflectJavaTypeParameter(it) } +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaPrimitiveType.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaPrimitiveType.kt new file mode 100644 index 00000000000..cd66e0c70f4 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaPrimitiveType.kt @@ -0,0 +1,23 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import org.jetbrains.kotlin.load.java.structure.JavaPrimitiveType + +public class ReflectJavaPrimitiveType(private val klass: Class<*>): ReflectJavaType(), JavaPrimitiveType { + override fun getCanonicalText() = klass.getName() +} diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaType.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaType.kt index e543f2d1582..303ff554100 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaType.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaType.kt @@ -16,9 +16,28 @@ package org.jetbrains.kotlin.load.java.structure.reflect -import org.jetbrains.kotlin.load.java.structure.JavaType import org.jetbrains.kotlin.load.java.structure.JavaArrayType +import org.jetbrains.kotlin.load.java.structure.JavaType +import java.lang.reflect.* + +private val PRIMITIVE_TYPES = setOf( + java.lang.Integer.TYPE, java.lang.Character.TYPE, java.lang.Byte.TYPE, java.lang.Long.TYPE, + java.lang.Short.TYPE, java.lang.Boolean.TYPE, java.lang.Double.TYPE, java.lang.Float.TYPE, + java.lang.Void.TYPE +) public abstract class ReflectJavaType : JavaType { override fun createArrayType(): JavaArrayType = throw UnsupportedOperationException() + + class object { + fun create(reflectType: Type): ReflectJavaType { + return when (reflectType) { + in PRIMITIVE_TYPES -> ReflectJavaPrimitiveType(reflectType as Class<*>) + is Class<*>, is TypeVariable<*>, is ParameterizedType -> ReflectJavaClassifierType(reflectType) + is GenericArrayType -> ReflectJavaArrayType(reflectType) + is WildcardType -> ReflectJavaWildcardType(reflectType) + else -> throw UnsupportedOperationException("Unsupported type (${reflectType.javaClass}): $reflectType") + } + } + } } diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaTypeParameter.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaTypeParameter.kt index b354ba1e165..5de27e60db2 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaTypeParameter.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaTypeParameter.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.load.java.structure.JavaType import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner import org.jetbrains.kotlin.name.Name +import java.lang.reflect.Method import java.lang.reflect.TypeVariable public class ReflectJavaTypeParameter( @@ -32,8 +33,12 @@ public class ReflectJavaTypeParameter( } override fun getOwner(): JavaTypeParameterListOwner? { - // TODO - throw UnsupportedOperationException() + val owner = typeVariable.getGenericDeclaration() + return when (owner) { + is Method -> ReflectJavaMethod(owner) + is Class<*> -> ReflectJavaClass(owner) + else -> throw UnsupportedOperationException("Unsupported type parameter list owner (${owner.javaClass}): $owner") + } } override fun getType(): JavaType { diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt new file mode 100644 index 00000000000..1a01a4cb999 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaValueParameter.kt @@ -0,0 +1,41 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import org.jetbrains.kotlin.load.java.structure.JavaValueParameter +import org.jetbrains.kotlin.load.java.structure.JavaAnnotation +import org.jetbrains.kotlin.name.FqName + +public class ReflectJavaValueParameter( + private val returnType: ReflectJavaType, + private val annotations: Array, + private val isVararg: Boolean +) : ReflectJavaElement(), JavaValueParameter { + override fun getAnnotations(): Collection { + // TODO + return listOf() + } + + override fun findAnnotation(fqName: FqName): JavaAnnotation? { + // TODO + return null + } + + override fun getName() = null // TODO: use ParameterNames on JDK 8 + override fun getType() = returnType + override fun isVararg() = isVararg +} \ No newline at end of file diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaWildcardType.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaWildcardType.kt new file mode 100644 index 00000000000..178a12f2a94 --- /dev/null +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/java/structure/reflect/ReflectJavaWildcardType.kt @@ -0,0 +1,39 @@ +/* + * 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 org.jetbrains.kotlin.load.java.structure.reflect + +import org.jetbrains.kotlin.load.java.structure.JavaWildcardType +import java.lang.reflect.WildcardType + +public class ReflectJavaWildcardType(private val wildcardType: WildcardType): ReflectJavaType(), JavaWildcardType { + override fun getBound(): ReflectJavaType? { + val upperBounds = wildcardType.getUpperBounds() + val lowerBounds = wildcardType.getLowerBounds() + if (upperBounds.size() > 1 || lowerBounds.size() > 1) { + throw UnsupportedOperationException("Wildcard types with many bounds are not yet supported: $wildcardType") + } + return when { + lowerBounds.size() == 1 -> ReflectJavaType.create(lowerBounds.single()) + upperBounds.size() == 1 -> upperBounds.single().let { ub -> if (ub != javaClass()) ReflectJavaType.create(ub) else null } + else -> null + } + } + + override fun isExtends() = wildcardType.getUpperBounds().firstOrNull() != javaClass() + + override fun getTypeProvider() = ReflectJavaTypeProvider +} \ No newline at end of file diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt index 0368419ee87..c927cba1528 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClass.kt @@ -16,40 +16,205 @@ package org.jetbrains.kotlin.load.kotlin.reflect +import org.jetbrains.kotlin.load.java.structure.reflect.classId import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader -import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.load.java.structure.reflect.classId import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor +import org.jetbrains.kotlin.name.Name +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Method -public class ReflectKotlinClass(private val klass: Class<*>) : KotlinJvmBinaryClass { - private val classHeader: KotlinClassHeader +suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") +private val TYPES_ELIGIBLE_FOR_SIMPLE_VISIT = setOf( + // Primitives + javaClass(), javaClass(), javaClass(), javaClass(), + javaClass(), javaClass(), javaClass(), javaClass(), + // Arrays of primitives + javaClass(), javaClass(), javaClass(), javaClass(), + javaClass(), javaClass(), javaClass(), javaClass(), + // Others + javaClass>(), javaClass() +) - { - val headerReader = ReadKotlinClassHeaderAnnotationVisitor() - loadClassAnnotations(headerReader) - val header = headerReader.createHeader() - - if (header == null) { - // This exception must be caught in the caller - throw NotAKotlinClass() +public class ReflectKotlinClass private( + private val klass: Class<*>, + private val classHeader: KotlinClassHeader +) : KotlinJvmBinaryClass { + default object Factory { + public fun create(klass: Class<*>): ReflectKotlinClass? { + val headerReader = ReadKotlinClassHeaderAnnotationVisitor() + ReflectClassStructure.loadClassAnnotations(klass, headerReader) + return ReflectKotlinClass(klass, headerReader.createHeader() ?: return null) } - - classHeader = header } - class NotAKotlinClass : RuntimeException() + override fun getClassId() = klass.classId - override fun getClassId(): ClassId = klass.classId + override fun getClassHeader() = classHeader override fun loadClassAnnotations(visitor: KotlinJvmBinaryClass.AnnotationVisitor) { - // TODO - visitor.visitEnd() + ReflectClassStructure.loadClassAnnotations(klass, visitor) } override fun visitMembers(visitor: KotlinJvmBinaryClass.MemberVisitor) { - // TODO + ReflectClassStructure.visitMembers(klass, visitor) + } +} + +private object ReflectClassStructure { + fun loadClassAnnotations(klass: Class<*>, visitor: KotlinJvmBinaryClass.AnnotationVisitor) { + for (annotation in klass.getDeclaredAnnotations()) { + processAnnotation(visitor, annotation) + } + visitor.visitEnd() } - override fun getClassHeader() = classHeader + fun visitMembers(klass: Class<*>, memberVisitor: KotlinJvmBinaryClass.MemberVisitor) { + loadMethodAnnotations(klass, memberVisitor) + loadConstructorAnnotations(klass, memberVisitor) + loadFieldAnnotations(klass, memberVisitor) + } + + private fun loadMethodAnnotations(klass: Class<*>, memberVisitor: KotlinJvmBinaryClass.MemberVisitor) { + for (method in klass.getDeclaredMethods()) { + val visitor = memberVisitor.visitMethod(Name.identifier(method.getName()), SignatureSerializer.methodDesc(method)) ?: continue + + for (annotation in method.getDeclaredAnnotations()) { + processAnnotation(visitor, annotation) + } + + for ((parameterIndex, annotations) in method.getParameterAnnotations().withIndex()) { + for (annotation in annotations) { + val annotationType = annotation.annotationType() + visitor.visitParameterAnnotation(parameterIndex, annotationType.classId)?.let { + processAnnotationArguments(it, annotation, annotationType) + } + } + } + + visitor.visitEnd() + } + } + + private fun loadConstructorAnnotations(klass: Class<*>, memberVisitor: KotlinJvmBinaryClass.MemberVisitor) { + for (constructor in klass.getDeclaredConstructors()) { + // TODO: load annotations on constructors + val visitor = memberVisitor.visitMethod(Name.special(""), SignatureSerializer.constructorDesc(constructor)) ?: continue + + // Constructors of enums have 2 additional synthetic parameters + // TODO: the similar logic should probably be present for annotations on parameters of inner class constructors + val shift = if (klass.isEnum()) 2 else 0 + for ((parameterIndex, annotations) in constructor.getParameterAnnotations().withIndex()) { + for (annotation in annotations) { + val annotationType = annotation.annotationType() + visitor.visitParameterAnnotation(parameterIndex + shift, annotationType.classId)?.let { + processAnnotationArguments(it, annotation, annotationType) + } + } + } + } + } + + private fun loadFieldAnnotations(klass: Class<*>, memberVisitor: KotlinJvmBinaryClass.MemberVisitor) { + for (field in klass.getDeclaredFields()) { + val visitor = memberVisitor.visitField(Name.identifier(field.getName()), SignatureSerializer.fieldDesc(field), null) ?: continue + + for (annotation in field.getDeclaredAnnotations()) { + processAnnotation(visitor, annotation) + } + + visitor.visitEnd() + } + } + + private fun processAnnotation(visitor: KotlinJvmBinaryClass.AnnotationVisitor, annotation: Annotation) { + val annotationType = annotation.annotationType() + visitor.visitAnnotation(annotationType.classId)?.let { + processAnnotationArguments(it, annotation, annotationType) + } + } + + private fun processAnnotationArguments( + visitor: KotlinJvmBinaryClass.AnnotationArgumentVisitor, + annotation: Annotation, + annotationType: Class<*> + ) { + for (method in annotationType.getDeclaredMethods()) { + processAnnotationArgumentValue(visitor, Name.identifier(method.getName()), method(annotation)) + } + visitor.visitEnd() + } + + private fun processAnnotationArgumentValue(visitor: KotlinJvmBinaryClass.AnnotationArgumentVisitor, name: Name, value: Any) { + val clazz = value.javaClass + when { + clazz in TYPES_ELIGIBLE_FOR_SIMPLE_VISIT -> { + visitor.visit(name, value) + } + javaClass>().isAssignableFrom(clazz) -> { + visitor.visitEnum(name, clazz.classId, Name.identifier(value.toString())) + } + javaClass().isAssignableFrom(clazz) -> { + // TODO: support values of annotation types + throw UnsupportedOperationException("Values of annotation types are not yet supported in Kotlin reflection: $value") + } + clazz.isArray() -> { + val elementVisitor = visitor.visitArray(name) ?: return + val componentType = clazz.getComponentType() + if (javaClass>().isAssignableFrom(componentType)) { + val componentClassName = componentType.classId + for (element in value as Array<*>) { + elementVisitor.visitEnum(componentClassName, Name.identifier(element.toString())) + } + } + else { + for (element in value as Array<*>) { + elementVisitor.visit(element) + } + } + elementVisitor.visitEnd() + } + else -> { + throw UnsupportedOperationException("Unsupported annotation argument value ($clazz): $value") + } + } + } +} + +private object SignatureSerializer { + fun methodDesc(method: Method): String { + val sb = StringBuilder() + sb.append("(") + for (parameterType in method.getParameterTypes()) { + sb.append(typeDesc(parameterType)) + } + sb.append(")") + sb.append(typeDesc(method.getReturnType())) + return sb.toString() + } + + fun constructorDesc(constructor: Constructor<*>): String { + val sb = StringBuilder() + sb.append("(") + for (parameterType in constructor.getParameterTypes()) { + sb.append(typeDesc(parameterType)) + } + sb.append(")V") + return sb.toString() + } + + fun fieldDesc(field: Field): String { + return typeDesc(field.getType()) + } + + suppress("UNCHECKED_CAST") + fun typeDesc(clazz: Class<*>): String { + if (clazz == Void.TYPE) return "V"; + // This is a clever exploitation of a format returned by Class.getName(): for arrays, it's almost an internal name, + // but with '.' instead of '/' + // TODO: ensure there are tests on arrays of nested classes, multi-dimensional arrays, etc. + val arrayClass = java.lang.reflect.Array.newInstance(clazz as Class, 0).javaClass + return arrayClass.getName().substring(1).replace('.', '/') + } } diff --git a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClassFinder.kt b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClassFinder.kt index 59399c8e6ef..ab2e3f813e8 100644 --- a/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClassFinder.kt +++ b/core/descriptors.runtime/src/org/jetbrains/kotlin/load/kotlin/reflect/ReflectKotlinClassFinder.kt @@ -24,12 +24,7 @@ import org.jetbrains.kotlin.name.ClassId public class ReflectKotlinClassFinder(private val classLoader: ClassLoader) : KotlinClassFinder { private fun findKotlinClass(fqName: String): KotlinJvmBinaryClass? { - return classLoader.tryLoadClass(fqName)?.let { try { - ReflectKotlinClass(it) - } - catch (e: ReflectKotlinClass.NotAKotlinClass) { - null - } } + return classLoader.tryLoadClass(fqName)?.let { ReflectKotlinClass.create(it) } } override fun findKotlinClass(classId: ClassId) = findKotlinClass(classId.toRuntimeFqName())