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())