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)
This commit is contained in:
Alexander Udalov
2014-08-26 16:18:56 +04:00
parent 356f54b5df
commit 73e4287aee
18 changed files with 524 additions and 52 deletions
@@ -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; package test;
public interface StringConstantInParam { public interface StringConstantInParam {
@@ -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; package test;
public class StaticFinal { 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;
} }
@@ -4,5 +4,10 @@ public open class StaticFinal {
public constructor StaticFinal() public constructor StaticFinal()
// Static members // 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!
} }
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.jvm.runtime 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.cli.common.output.outputUtils.writeAllTo
import org.jetbrains.kotlin.codegen.GeneratedClassLoader import org.jetbrains.kotlin.codegen.GeneratedClassLoader
import org.jetbrains.kotlin.codegen.GenerationUtils import org.jetbrains.kotlin.codegen.GenerationUtils
@@ -64,11 +63,13 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi
.build() .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) { protected fun doTest(ktFileName: String) {
val ktFile = File(ktFileName) val ktFile = File(ktFileName)
val environment = JetTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable, ConfigurationKind.ALL) 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 classFileFactory = GenerationUtils.compileFileGetClassFileFactoryForTest(jetFile)
val classLoader = GeneratedClassLoader(classFileFactory, null, ForTestCompileRuntime.runtimeJarForTests().toURI().toURL()) 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") 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 -> { KotlinClassHeader.Kind.PACKAGE_FACADE -> {
val packageView = module.getPackage(actual.getFqName()) ?: error("Couldn't resolve package ${actual.getFqName()}") val packageView = module.getPackage(actual.getFqName()) ?: error("Couldn't resolve package ${actual.getFqName()}")
for (descriptor in packageView.getMemberScope().getAllDescriptors()) { for (descriptor in packageView.getMemberScope().getAllDescriptors()) {
@@ -125,13 +126,21 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi
val expected = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(tmpdir, getTestRootDisposable(), val expected = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(tmpdir, getTestRootDisposable(),
ConfigurationKind.ALL) ConfigurationKind.ALL)
val configuration = RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT val comparatorConfiguration = RecursiveDescriptorComparator.DONT_INCLUDE_METHODS_OF_OBJECT
.checkPrimaryConstructors(true) .checkPrimaryConstructors(true)
.checkPropertyAccessors(true) .checkPropertyAccessors(true)
.withRenderer(renderer) .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? { private fun resolveClassByFqNameInModule(module: ModuleDescriptor, fqName: FqNameUnsafe): ClassDescriptor? {
if (fqName.isRoot()) return null if (fqName.isRoot()) return null
@@ -18,11 +18,13 @@ package org.jetbrains.kotlin.load.java.structure;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.ReadOnly;
import java.util.List; import java.util.List;
public interface JavaMethod extends JavaMember, JavaTypeParameterListOwner { public interface JavaMethod extends JavaMember, JavaTypeParameterListOwner {
@NotNull @NotNull
@ReadOnly
List<JavaValueParameter> getValueParameters(); List<JavaValueParameter> getValueParameters();
boolean hasAnnotationParameterDefaultValue(); boolean hasAnnotationParameterDefaultValue();
@@ -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()!!)
}
@@ -23,10 +23,10 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.emptyOrSingletonList import org.jetbrains.kotlin.utils.emptyOrSingletonList
public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(), JavaClass { public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(), JavaClass {
override fun getInnerClasses(): Collection<JavaClass> { override fun getInnerClasses() =
// TODO klass.getDeclaredClasses()
return listOf() .filter { it.getSimpleName().isNotEmpty() }
} .map { ReflectJavaClass(it) }
override fun getFqName(): FqName? { override fun getFqName(): FqName? {
// TODO: can there be primitive types, arrays? // TODO: can there be primitive types, arrays?
@@ -41,18 +41,12 @@ public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(
override fun getSupertypes(): Collection<JavaClassifierType> { override fun getSupertypes(): Collection<JavaClassifierType> {
// TODO: also call getSuperclass() / getInterfaces() for classes without generic signature // TODO: also call getSuperclass() / getInterfaces() for classes without generic signature
val supertypes = emptyOrSingletonList(klass.getGenericSuperclass()) + klass.getGenericInterfaces() 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<JavaMethod> { override fun getMethods() = klass.getDeclaredMethods().map { method -> ReflectJavaMethod(method) }
// TODO
return listOf()
}
override fun getFields(): Collection<JavaField> { override fun getFields() = klass.getDeclaredFields().map { field -> ReflectJavaField(field) }
// TODO
return listOf()
}
override fun getConstructors(): Collection<JavaConstructor> { override fun getConstructors(): Collection<JavaConstructor> {
// TODO // TODO
@@ -29,9 +29,8 @@ public class ReflectJavaClassifierType(val classifierType: Type) : ReflectJavaTy
return when (classifierType) { return when (classifierType) {
is Class<*> -> ReflectJavaClass(classifierType) is Class<*> -> ReflectJavaClass(classifierType)
is TypeVariable<*> -> ReflectJavaTypeParameter(classifierType) is TypeVariable<*> -> ReflectJavaTypeParameter(classifierType)
// TODO
is ParameterizedType -> ReflectJavaClass(classifierType.getRawType() as Class<*>) 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 isRaw(): Boolean = with(classifierType) { this is Class<*> && getTypeParameters().isNotEmpty() }
override fun getTypeArguments(): List<JavaType> { override fun getTypeArguments(): List<JavaType> {
// TODO if (classifierType is ParameterizedType) {
return classifierType.getActualTypeArguments()!!.map { ReflectJavaType.create(it) }
}
return listOf() return listOf()
} }
} }
@@ -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<JavaAnnotation> {
// TODO
return listOf()
}
override fun findAnnotation(fqName: FqName): JavaAnnotation? {
// TODO
return null
}
override fun isEnumEntry() = field.isEnumConstant()
override fun getType(): JavaType {
// TODO
throw UnsupportedOperationException()
}
}
@@ -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())
}
@@ -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<JavaAnnotation> {
// TODO
return listOf()
}
override fun findAnnotation(fqName: FqName): JavaAnnotation? {
// TODO
return null
}
override fun getValueParameters(): List<JavaValueParameter> {
val types = method.getGenericParameterTypes()!!
val annotations = method.getParameterAnnotations()
val result = ArrayList<JavaValueParameter>()
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) }
}
@@ -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()
}
@@ -16,9 +16,28 @@
package org.jetbrains.kotlin.load.java.structure.reflect 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.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 { public abstract class ReflectJavaType : JavaType {
override fun createArrayType(): JavaArrayType = throw UnsupportedOperationException() 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")
}
}
}
} }
@@ -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.JavaTypeParameter
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import java.lang.reflect.Method
import java.lang.reflect.TypeVariable import java.lang.reflect.TypeVariable
public class ReflectJavaTypeParameter( public class ReflectJavaTypeParameter(
@@ -32,8 +33,12 @@ public class ReflectJavaTypeParameter(
} }
override fun getOwner(): JavaTypeParameterListOwner? { override fun getOwner(): JavaTypeParameterListOwner? {
// TODO val owner = typeVariable.getGenericDeclaration()
throw UnsupportedOperationException() 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 { override fun getType(): JavaType {
@@ -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<Annotation>,
private val isVararg: Boolean
) : ReflectJavaElement(), JavaValueParameter {
override fun getAnnotations(): Collection<JavaAnnotation> {
// 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
}
@@ -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<Any>()) ReflectJavaType.create(ub) else null }
else -> null
}
}
override fun isExtends() = wildcardType.getUpperBounds().firstOrNull() != javaClass<Any>()
override fun getTypeProvider() = ReflectJavaTypeProvider
}
@@ -16,40 +16,205 @@
package org.jetbrains.kotlin.load.kotlin.reflect 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.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader 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.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 { suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
private val classHeader: KotlinClassHeader private val TYPES_ELIGIBLE_FOR_SIMPLE_VISIT = setOf(
// Primitives
javaClass<java.lang.Integer>(), javaClass<java.lang.Character>(), javaClass<java.lang.Byte>(), javaClass<java.lang.Long>(),
javaClass<java.lang.Short>(), javaClass<java.lang.Boolean>(), javaClass<java.lang.Double>(), javaClass<java.lang.Float>(),
// Arrays of primitives
javaClass<IntArray>(), javaClass<CharArray>(), javaClass<ByteArray>(), javaClass<LongArray>(),
javaClass<ShortArray>(), javaClass<BooleanArray>(), javaClass<DoubleArray>(), javaClass<FloatArray>(),
// Others
javaClass<Class<*>>(), javaClass<String>()
)
{ public class ReflectKotlinClass private(
val headerReader = ReadKotlinClassHeaderAnnotationVisitor() private val klass: Class<*>,
loadClassAnnotations(headerReader) private val classHeader: KotlinClassHeader
val header = headerReader.createHeader() ) : KotlinJvmBinaryClass {
default object Factory {
if (header == null) { public fun create(klass: Class<*>): ReflectKotlinClass? {
// This exception must be caught in the caller val headerReader = ReadKotlinClassHeaderAnnotationVisitor()
throw NotAKotlinClass() 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) { override fun loadClassAnnotations(visitor: KotlinJvmBinaryClass.AnnotationVisitor) {
// TODO ReflectClassStructure.loadClassAnnotations(klass, visitor)
visitor.visitEnd()
} }
override fun visitMembers(visitor: KotlinJvmBinaryClass.MemberVisitor) { 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("<init>"), 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<Enum<*>>().isAssignableFrom(clazz) -> {
visitor.visitEnum(name, clazz.classId, Name.identifier(value.toString()))
}
javaClass<Annotation>().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<Enum<*>>().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<Any>, 0).javaClass
return arrayClass.getName().substring(1).replace('.', '/')
}
} }
@@ -24,12 +24,7 @@ import org.jetbrains.kotlin.name.ClassId
public class ReflectKotlinClassFinder(private val classLoader: ClassLoader) : KotlinClassFinder { public class ReflectKotlinClassFinder(private val classLoader: ClassLoader) : KotlinClassFinder {
private fun findKotlinClass(fqName: String): KotlinJvmBinaryClass? { private fun findKotlinClass(fqName: String): KotlinJvmBinaryClass? {
return classLoader.tryLoadClass(fqName)?.let { try { return classLoader.tryLoadClass(fqName)?.let { ReflectKotlinClass.create(it) }
ReflectKotlinClass(it)
}
catch (e: ReflectKotlinClass.NotAKotlinClass) {
null
} }
} }
override fun findKotlinClass(classId: ClassId) = findKotlinClass(classId.toRuntimeFqName()) override fun findKotlinClass(classId: ClassId) = findKotlinClass(classId.toRuntimeFqName())