From 53921c3f74b99da0135dbefcde501baddf4917f7 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 23 Jun 2016 20:19:15 +0300 Subject: [PATCH] Kapt: Drop custom annotation stub factory, implement it with AnnotationParser in sun.reflect as done in Javac (cherry picked from commit 82dcbf3) --- .../java-model-wrappers.iml | 1 - .../kotlin/java/model/JeAnnotationOwner.kt | 10 +- .../java/model/internal/AnnotationUtil.java | 54 ++++ .../internal/KotlinAnnotationProxyMaker.kt | 178 ++++++++++++ .../model/internal/annotationStubFactory.kt | 272 ------------------ 5 files changed, 239 insertions(+), 276 deletions(-) create mode 100644 plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/AnnotationUtil.java create mode 100644 plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/KotlinAnnotationProxyMaker.kt delete mode 100644 plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/annotationStubFactory.kt diff --git a/plugins/java-model-wrappers/java-model-wrappers.iml b/plugins/java-model-wrappers/java-model-wrappers.iml index 3ebc8d39248..bd0c3feaf10 100755 --- a/plugins/java-model-wrappers/java-model-wrappers.iml +++ b/plugins/java-model-wrappers/java-model-wrappers.iml @@ -8,7 +8,6 @@ - diff --git a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/JeAnnotationOwner.kt b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/JeAnnotationOwner.kt index 7adb4e02233..ce620704039 100644 --- a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/JeAnnotationOwner.kt +++ b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/JeAnnotationOwner.kt @@ -19,7 +19,7 @@ package org.jetbrains.kotlin.java.model import com.intellij.psi.PsiAnnotationOwner import com.intellij.psi.PsiClass import org.jetbrains.kotlin.java.model.elements.JeAnnotationMirror -import org.jetbrains.kotlin.java.model.internal.createAnnotation +import org.jetbrains.kotlin.java.model.internal.KotlinAnnotationProxyMaker import javax.lang.model.element.AnnotationMirror import javax.lang.model.element.Element import java.lang.reflect.Array as RArray @@ -30,14 +30,18 @@ interface JeAnnotationOwner : Element { override fun getAnnotationMirrors() = annotationOwner?.annotations?.map { JeAnnotationMirror(it) } ?: emptyList() override fun getAnnotation(annotationClass: Class): A? { + if (!annotationClass.isAnnotation) { + throw IllegalArgumentException("Not an annotation class: " + annotationClass) + } + val annotationFqName = annotationClass.canonicalName val annotation = annotationOwner?.annotations ?.firstOrNull { it.qualifiedName == annotationFqName } ?: return null val annotationDeclaration = annotation.nameReferenceElement?.resolve() as? PsiClass ?: return null - + @Suppress("UNCHECKED_CAST") - return createAnnotation(annotation, annotationDeclaration, annotationClass) as? A + return KotlinAnnotationProxyMaker(annotation, annotationDeclaration, annotationClass).generate() as? A } @Suppress("UNCHECKED_CAST") diff --git a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/AnnotationUtil.java b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/AnnotationUtil.java new file mode 100644 index 00000000000..4708dbb81f7 --- /dev/null +++ b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/AnnotationUtil.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2016 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.java.model.internal; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiType; +import sun.reflect.annotation.EnumConstantNotPresentExceptionProxy; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class AnnotationUtil { + public static final Map> ARRAY_TYPES_MAP; + + static { + Map> classes = new HashMap>(); + + classes.put(PsiType.BYTE, byte.class); + classes.put(PsiType.SHORT, short.class); + classes.put(PsiType.INT, int.class); + classes.put(PsiType.CHAR, char.class); + classes.put(PsiType.BOOLEAN, boolean.class); + classes.put(PsiType.LONG, long.class); + classes.put(PsiType.FLOAT, float.class); + classes.put(PsiType.DOUBLE, double.class); + + ARRAY_TYPES_MAP = Collections.unmodifiableMap(classes); + } + + public static Object createEnumValue(Class enumClass, String name) { + try { + return Enum.valueOf((Class)enumClass, name); + } catch (IllegalArgumentException ex) { + //noinspection unchecked + return new EnumConstantNotPresentExceptionProxy((Class>) enumClass, name); + } + + } +} diff --git a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/KotlinAnnotationProxyMaker.kt b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/KotlinAnnotationProxyMaker.kt new file mode 100644 index 00000000000..8626c0d28ef --- /dev/null +++ b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/KotlinAnnotationProxyMaker.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2010-2016 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.java.model.internal + +import com.intellij.psi.* +import org.jetbrains.kotlin.asJava.elements.KtLightAnnotation +import org.jetbrains.kotlin.java.model.types.toJeType +import sun.reflect.annotation.AnnotationParser +import sun.reflect.annotation.ExceptionProxy +import java.io.ObjectInputStream +import java.lang.reflect.Array +import java.lang.reflect.Method +import java.util.* +import javax.lang.model.type.MirroredTypeException +import javax.lang.model.type.MirroredTypesException +import javax.lang.model.type.TypeMirror + +class KotlinAnnotationProxyMaker(val annotation: PsiAnnotation, val annotationClass: PsiClass, val annotationType: Class) { + fun generate(): Any { + return AnnotationParser.annotationForMap(annotationType, getAllValuesForParser(getAllPsiValues())) + } + + private fun getAllPsiValues(): List> { + val values = mutableListOf>() + for (method in annotationClass.methods) { + val jMethod = try { annotationType.getMethod(method.name) } catch (e : NoSuchMethodException) { continue } + if (method !is PsiAnnotationMethod) continue + if (method.returnType == null) continue + + val value = annotation.findAttributeValue(method.name) ?: method.defaultValue ?: continue + values += Triple(method, value, jMethod) + } + return values + } + + private fun getAllValuesForParser(values: List>): Map { + val parserValues = mutableMapOf() + val evaluator = JavaPsiFacade.getInstance(annotation.project).constantEvaluationHelper + for ((method, value, jMethod) in values) { + val jReturnType = jMethod.returnType ?: unexpectedType("no return type for ${jMethod.name}") + parserValues.put(method.name, getConstantValue(value, method.returnType!!, jReturnType, evaluator)) + } + return parserValues + } +} + +private fun getConstantValue( + psiValue: PsiAnnotationMemberValue, + returnType: PsiType, + jReturnType: Class<*>, + evaluator: PsiConstantEvaluationHelper +): Any? { + val manager = psiValue.manager + + when { + returnType == PsiType.NULL || returnType == PsiType.VOID -> unexpectedType("void") + jReturnType == String::class.java -> return calculateConstantValue(psiValue, evaluator) + jReturnType == Class::class.java -> { + val type = (psiValue as PsiClassObjectAccessExpression).operand.type.toJeType(manager) + return MirroredTypeExceptionProxy(type) + } + jReturnType.isArray -> { + val jComponentType = jReturnType.componentType ?: unexpectedType("no component type for $jReturnType") + if (returnType !is PsiArrayType) unexpectedType(returnType) + + val arrayValues = when (psiValue) { + is PsiArrayInitializerMemberValue -> psiValue.initializers.toList() + else -> listOf(psiValue) + } + + if (jComponentType == Class::class.java) { + val typeMirrors = arrayValues.map { (it as PsiClassObjectAccessExpression).operand.type.toJeType(manager) } + return MirroredTypesExceptionProxy(Collections.unmodifiableList(typeMirrors)) + } else { + val arr = Array.newInstance(jComponentType, arrayValues.size) + arrayValues.forEachIndexed { i, componentPsiValue -> + val componentValue = getConstantValue(componentPsiValue, returnType.componentType, jComponentType, evaluator) + try { Array.set(arr, i, componentValue) } catch (e: IllegalArgumentException) { return null } + } + return arr + } + } + jReturnType.isEnum -> { + val enumConstant = (psiValue.originalElement as? PsiReference)?.resolve() as? PsiEnumConstant + ?: error("$psiValue can not be resolved to enum constant") + return AnnotationUtil.createEnumValue(jReturnType, enumConstant.name) + } + else -> return castPrimitiveValue(returnType, calculateConstantValue(psiValue, evaluator)) + } +} + +private fun castPrimitiveValue(type: PsiType, value: Any?): Any? = when (type) { + PsiType.BYTE -> byteValue(value) + PsiType.SHORT -> shortValue(value) + PsiType.INT -> intValue(value) + PsiType.CHAR -> charValue(value) + PsiType.BOOLEAN -> booleanValue(value) + PsiType.LONG -> longValue(value) + PsiType.FLOAT -> floatValue(value) + PsiType.DOUBLE -> doubleValue(value) + else -> unexpectedType(type) +} + +private fun byteValue(value: Any?): Byte = (value as? Number)?.toByte() ?: 0 +private fun intValue(value: Any?): Int = (value as? Number)?.toInt() ?: 0 +private fun shortValue(value: Any?): Short = (value as? Number)?.toShort() ?: 0 +private fun booleanValue(value: Any?): Boolean = if (value == true) true else false +private fun charValue(value: Any?): Char = value as? Char ?: 0.toChar() + +private fun longValue(value: Any?): Long = (value as? Number)?.toLong() ?: 0 +private fun floatValue(value: Any?): Float = (value as? Number)?.toFloat() ?: 0f +private fun doubleValue(value: Any?): Double = (value as? Number)?.toDouble() ?: 0.0 + +private fun calculateConstantValue(value: PsiAnnotationMemberValue?, evaluator: PsiConstantEvaluationHelper) = when (value) { + is PsiLiteral -> value.value + is KtLightAnnotation.LightExpressionValue<*> -> value.getConstantValue() + is PsiExpression -> evaluator.computeConstantExpression(value) + else -> null +} + +private fun unexpectedType(type: String): Nothing = error("Unexpected type: $type") +private fun unexpectedType(type: PsiType): Nothing = unexpectedType(type.presentableText) + +private class MirroredTypeExceptionProxy(@Transient private var type: TypeMirror?) : ExceptionProxy() { + private val typeString = type.toString() + + @Suppress("IMPLICIT_CAST_TO_ANY") + override fun hashCode() = (if (type != null) type else typeString)?.hashCode() ?: 0 + override fun equals(other: Any?) = type != null && other is MirroredTypeExceptionProxy && type == other.type + override fun toString() = typeString + + override fun generateException() = MirroredTypeException(type) + + // Explicitly set all transient fields. + private fun readObject(s: ObjectInputStream) { + s.defaultReadObject() + type = null + } + + companion object { + private const val serialVersionUID: Long = 269 + } +} + +private class MirroredTypesExceptionProxy(@Transient private var types: List?) : ExceptionProxy() { + private val typeStrings: String = types.toString() + + @Suppress("IMPLICIT_CAST_TO_ANY") + override fun hashCode() = (if (types != null) types else typeStrings)?.hashCode() ?: 0 + override fun equals(other: Any?) = types != null && other is MirroredTypesExceptionProxy && types == other.types + override fun toString() = typeStrings + + override fun generateException() = MirroredTypesException(types) + + // Explicitly set all transient fields. + private fun readObject(s: ObjectInputStream) { + s.defaultReadObject() + types = null + } + + companion object { + private const val serialVersionUID: Long = 269 + } +} \ No newline at end of file diff --git a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/annotationStubFactory.kt b/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/annotationStubFactory.kt deleted file mode 100644 index e712b450525..00000000000 --- a/plugins/java-model-wrappers/src/org/jetbrains/kotlin/java/model/internal/annotationStubFactory.kt +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright 2010-2016 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.java.model.internal - -import com.intellij.psi.* -import org.jetbrains.kotlin.asJava.KtLightAnnotation -import org.jetbrains.org.objectweb.asm.ClassWriter -import org.jetbrains.org.objectweb.asm.Opcodes.* -import org.jetbrains.org.objectweb.asm.Type -import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter -import java.net.URL -import java.net.URLClassLoader - -internal fun createAnnotation( - annotation: PsiAnnotation, - annotationDeclaration: PsiClass, - annotationClass: Class<*> -): Annotation? { - val qualifiedName = annotationDeclaration.qualifiedName ?: return null - val implQualifiedName = "stub." + qualifiedName + ".Impl" + Integer.toHexString(annotation.text.hashCode()) - - val annotationInternalName = annotationDeclaration.getInternalName() - val implInternalName = implQualifiedName.replace('.', '/') - - val bytes = createAnnotationImplementationClass(annotation, annotationDeclaration, annotationInternalName, implInternalName) - return loadClass(implQualifiedName, bytes, annotationClass)?.newInstance() as? Annotation -} - -private fun createAnnotationImplementationClass( - annotation: PsiAnnotation, - annotationDeclaration: PsiClass, - annotationInternalName: String, - implInternalName: String -): ByteArray { - return ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS).apply { - visit(V1_6, ACC_SUPER or ACC_PUBLIC, implInternalName, null, "java/lang/Object", arrayOf(annotationInternalName)) - - with(visitMethod(ACC_PUBLIC, "", "()V", null, null)) { - visitCode() - visitVarInsn(ALOAD, 0) - visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false) - visitInsn(RETURN) - visitMaxs(-1, -1) - visitEnd() - } - - with(visitMethod(ACC_PUBLIC, "annotationType", "()Ljava/lang/Class;", - "()Ljava/lang/Class<+Ljava/lang/annotation/Annotation;>;", null)) { - visitCode() - visitLdcInsn(Type.getType("L$annotationInternalName;")) - visitInsn(ARETURN) - visitMaxs(-1, -1) - visitEnd() - } - - if (annotationDeclaration.allMethods.isNotEmpty()) { - val evaluator = JavaPsiFacade.getInstance(annotation.project).constantEvaluationHelper - - for (method in annotationDeclaration.methods) { - if (method !is PsiAnnotationMethod) continue - - if (method.returnType == null) continue - val returnType = method.returnType!! - - val value = annotation.findAttributeValue(method.name) ?: method.defaultValue - - when (returnType) { - PsiType.VOID -> unexpectedType("void") - PsiType.NULL -> unexpectedType("null") - is PsiClassType -> { - val resolvedClass = returnType.resolve() - val resolvedQualifiedName = resolvedClass?.qualifiedName - if (resolvedQualifiedName == "java.lang.String") { - writeStringMethod(method, calculateConstantValue(value, evaluator)) - } - else if (resolvedClass != null && resolvedClass.isEnum) { - writeEnumMethod(method, resolvedClass, value) - } - else { - unexpectedType("$resolvedQualifiedName") - } - } - is PsiPrimitiveType -> writeLiteralMethod(method, calculateConstantValue(value, evaluator)) - is PsiArrayType -> writeArrayMethod(method, value, evaluator) - else -> unexpectedType(returnType) - } - } - } - - visitEnd() - }.toByteArray() -} - -private fun ClassWriter.writeEnumMethod(method: PsiAnnotationMethod, enumClass: PsiClass, value: PsiAnnotationMemberValue?) { - val enumInternalName = enumClass.getInternalName() - val enumConstant = (value?.originalElement as? PsiReference)?.resolve() as? PsiEnumConstant ?: return - - val mv = visitMethod(ACC_PUBLIC, method.name, "()L$enumInternalName;", null, null) - - with (InstructionAdapter(mv)) { - visitCode() - - getstatic(enumInternalName, enumConstant.name!!, "L$enumInternalName;") - visitInsn(ARETURN) - - visitMaxs(-1 ,-1) - visitEnd() - } -} - -private fun ClassWriter.writeStringMethod(method: PsiAnnotationMethod, value: Any?) { - val stringValue = (value as? String) ?: null - - val mv = visitMethod(ACC_PUBLIC, method.name, "()Ljava/lang/String;", null, null) - - with (InstructionAdapter(mv)) { - visitCode() - - if (stringValue != null) { - visitLdcInsn(stringValue) - } else { - visitInsn(ACONST_NULL) - } - visitInsn(ARETURN) - - visitMaxs(-1 ,-1) - visitEnd() - } -} - -private fun ClassWriter.writeArrayMethod( - method: PsiAnnotationMethod, - value: PsiAnnotationMemberValue?, - evaluator: PsiConstantEvaluationHelper -) { - val componentType = (method.returnType as? PsiArrayType)?.componentType ?: return - val componentAsmType = componentType.toAsmType() ?: return - - val initializers = (value as? PsiArrayInitializerMemberValue)?.initializers ?: emptyArray() - - val mv = visitMethod(ACC_PUBLIC, method.name, "()[" + componentAsmType.descriptor, null, null) - - with (InstructionAdapter(mv)) { - visitCode() - - iconst(initializers.size) - newarray(componentAsmType) - - initializers.forEachIndexed { index, value -> - val valueObject = calculateConstantValue(value, evaluator) - - dup() - iconst(index) - putValue(componentAsmType, valueObject) - astore(componentAsmType) - } - - visitInsn(ARETURN) - visitMaxs(-1, -1) - visitEnd() - } -} - -private fun calculateConstantValue(value: PsiAnnotationMemberValue?, evaluator: PsiConstantEvaluationHelper) = when (value) { - is PsiLiteral -> value.value - is KtLightAnnotation.LightExpressionValue<*> -> value.getConstantValue() - is PsiExpression -> evaluator.computeConstantExpression(value) - else -> null -} - -private fun InstructionAdapter.putValue(type: Type, value: Any?) = when (type) { - Type.DOUBLE_TYPE -> dconst(doubleValue(value)) - Type.FLOAT_TYPE -> fconst(floatValue(value)) - Type.LONG_TYPE -> lconst(longValue(value)) - Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE, Type.CHAR_TYPE, Type.BOOLEAN_TYPE -> iconst(type.castValue(value)) - else -> aconst(value) -} - -private fun ClassWriter.writeLiteralMethod(method: PsiAnnotationMethod, value: Any?) { - val returnType = method.returnType ?: return - val asmType = returnType.toAsmType() ?: return - - val mv = visitMethod(ACC_PUBLIC, method.name, "()" + asmType.descriptor, null, null) - with (InstructionAdapter(mv)) { - visitCode() - - putValue(asmType, value) - areturn(asmType) - - visitMaxs(-1, -1) - visitEnd() - } -} - -private fun PsiType.toAsmType(): Type? = when (this) { - PsiType.BYTE -> Type.BYTE_TYPE - PsiType.SHORT -> Type.SHORT_TYPE - PsiType.INT -> Type.INT_TYPE - PsiType.CHAR -> Type.CHAR_TYPE - PsiType.BOOLEAN -> Type.BOOLEAN_TYPE - PsiType.LONG -> Type.LONG_TYPE - PsiType.FLOAT -> Type.FLOAT_TYPE - PsiType.DOUBLE -> Type.DOUBLE_TYPE - is PsiClassType -> resolve()?.let { Type.getObjectType(it.getInternalName()) } - else -> null -} - -private fun Type.castValue(value: Any?): Int = when (this) { - Type.BYTE_TYPE -> byteValue(value) - Type.SHORT_TYPE -> shortValue(value) - Type.INT_TYPE -> intValue(value) - Type.CHAR_TYPE -> charValue(value) - Type.BOOLEAN_TYPE -> booleanValue(value) - else -> unexpectedType(this) -} - -private fun PsiClass.getInternalName(): String { - val containingClass = this.containingClass - return if (containingClass != null) { - containingClass.getInternalName() + "$" + this.name - } else { - qualifiedName?.replace('.', '/') ?: error("Invalid class name ($this)") - } -} - -private fun byteValue(value: Any?): Int = (value as? Number)?.toByte()?.toInt() ?: 0 -private fun intValue(value: Any?): Int = (value as? Number)?.toInt() ?: 0 -private fun shortValue(value: Any?): Int = (value as? Number)?.toShort()?.toInt() ?: 0 -private fun booleanValue(value: Any?): Int = if (value == true) 1 else 0 -private fun charValue(value: Any?): Int = (value as? Char)?.toInt() ?: 0.toChar().toInt() - -private fun longValue(value: Any?): Long = (value as? Number)?.toLong() ?: 0 -private fun floatValue(value: Any?): Float = (value as? Number)?.toFloat() ?: 0f -private fun doubleValue(value: Any?): Double = (value as? Number)?.toDouble() ?: 0.0 - -private fun loadClass(fqName: String, bytes: ByteArray, annotationClass: Class<*>): Class<*>? { - class ByteClassLoader( - urls: Array?, - parent: ClassLoader?, - val extraClasses: MutableMap, - predefinedClasses: List> - ) : URLClassLoader(urls, parent) { - private val predefinedClasses = predefinedClasses.associateBy { it.canonicalName } - - override fun findClass(name: String): Class<*>? { - return extraClasses.remove(name)?.let { - defineClass(name, it, 0, it.size) - } ?: predefinedClasses[name] ?: super.findClass(name) - } - } - - val classLoader = ByteClassLoader(emptyArray(), annotationClass.classLoader, hashMapOf(fqName to bytes), listOf()) - return Class.forName(fqName, false, classLoader) -} - -private fun unexpectedType(type: String): Nothing = error("Unexpected type: $type") -private fun unexpectedType(type: PsiType): Nothing = unexpectedType(type.presentableText) -private fun unexpectedType(type: Type): Nothing = unexpectedType(type.descriptor) \ No newline at end of file