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;
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;
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()
// 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
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
@@ -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<JavaValueParameter> getValueParameters();
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
public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(), JavaClass {
override fun getInnerClasses(): Collection<JavaClass> {
// 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<JavaClassifierType> {
// 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<JavaMethod> {
// TODO
return listOf()
}
override fun getMethods() = klass.getDeclaredMethods().map { method -> ReflectJavaMethod(method) }
override fun getFields(): Collection<JavaField> {
// TODO
return listOf()
}
override fun getFields() = klass.getDeclaredFields().map { field -> ReflectJavaField(field) }
override fun getConstructors(): Collection<JavaConstructor> {
// TODO
@@ -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<JavaType> {
// TODO
if (classifierType is ParameterizedType) {
return classifierType.getActualTypeArguments()!!.map { ReflectJavaType.create(it) }
}
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
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")
}
}
}
}
@@ -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 {
@@ -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
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<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>()
)
{
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("<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 {
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())