diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/extensions/ExpressionCodegenExtension.kt b/compiler/backend/src/org/jetbrains/jet/codegen/extensions/ExpressionCodegenExtension.kt index 1b61580ec2b..a932ca2d647 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/extensions/ExpressionCodegenExtension.kt +++ b/compiler/backend/src/org/jetbrains/jet/codegen/extensions/ExpressionCodegenExtension.kt @@ -21,6 +21,10 @@ import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall import org.jetbrains.jet.codegen.StackValue import org.jetbrains.jet.codegen.state.JetTypeMapper import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.jet.codegen.ClassBodyCodegen +import org.jetbrains.jet.codegen.ClassBuilder +import org.jetbrains.jet.lang.psi.JetClassOrObject +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor public trait ExpressionCodegenExtension { class object : ProjectExtensionDescriptor("org.jetbrains.kotlin.expressionCodegenExtension", javaClass()) @@ -32,4 +36,8 @@ public trait ExpressionCodegenExtension { // return null if not applicable public fun apply(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? + + public fun generateClassSyntheticParts(codegen: ClassBuilder, clazz: JetClassOrObject, descriptor: DeclarationDescriptor) { + + } } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java index 629f5748625..173954312b6 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java @@ -23,6 +23,7 @@ import com.intellij.util.ArrayUtil; import kotlin.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.codegen.extensions.ExpressionCodegenExtension; import org.jetbrains.kotlin.backend.common.CodegenUtil; import org.jetbrains.kotlin.backend.common.CodegenUtilKt; import org.jetbrains.kotlin.backend.common.DataClassMethodGenerator; @@ -400,6 +401,10 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen { generateToArray(); genClosureFields(context.closure, v, typeMapper); + + for (ExpressionCodegenExtension extension : ExpressionCodegenExtension.Default.getInstances(state.getProject())) { + extension.generateClassSyntheticParts(v, myClass, descriptor); + } } private void generateReflectionObjectFieldIfNeeded() { diff --git a/plugins/android-compiler-plugin/android-compiler-plugin.iml b/plugins/android-compiler-plugin/android-compiler-plugin.iml index 6955ef54ef5..5847993ab9f 100644 --- a/plugins/android-compiler-plugin/android-compiler-plugin.iml +++ b/plugins/android-compiler-plugin/android-compiler-plugin.iml @@ -10,6 +10,7 @@ + diff --git a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt index a61413727aa..b92c71f58d9 100644 --- a/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt +++ b/plugins/android-compiler-plugin/src/AndroidComponentRegistrar.kt @@ -36,6 +36,20 @@ import org.jetbrains.jet.codegen.StackValue import org.jetbrains.jet.lang.descriptors.PropertyDescriptor import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils +import org.jetbrains.jet.codegen.ClassBuilder +import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.jet.lang.resolve.java.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.jet.lang.psi.JetClassOrObject +import org.jetbrains.jet.codegen.FunctionCodegen +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.jet.lang.psi.JetClass +import org.jetbrains.jet.lang.psi.JetClassBody +import org.jetbrains.jet.lang.resolve.lazy.descriptors.LazyClassDescriptor +import org.jetbrains.jet.lang.descriptors.ClassifierDescriptor +import org.jetbrains.jet.lang.resolve.java.lazy.descriptors.LazyJavaClassDescriptor +import org.jetbrains.jet.lang.resolve.DescriptorUtils public object AndroidConfigurationKeys { @@ -73,25 +87,129 @@ public class AndroidDeclarationsProvider(private val project: Project) : Externa } public class AndroidExpressionCodegen : ExpressionCodegenExtension { + val propertyName = "_\$_findViewByIdCache" + val methodName = "_\$_findCachedViewById" + override fun apply(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? { - if (resolvedCall.getResultingDescriptor() !is PropertyDescriptor) return null + if (resolvedCall.getResultingDescriptor() !is PropertyDescriptor) return null - val propertyDescriptor = resolvedCall.getResultingDescriptor() as PropertyDescriptor + val propertyDescriptor = resolvedCall.getResultingDescriptor() as PropertyDescriptor - val file = DescriptorToSourceUtils.getContainingFile(propertyDescriptor) - if (file == null) return null + val file = DescriptorToSourceUtils.getContainingFile(propertyDescriptor) + if (file == null) return null - val androidPackage = file.getUserData(AndroidConst.ANDROID_USER_PACKAGE) - if (androidPackage == null) return null + val androidPackage = file.getUserData(AndroidConst.ANDROID_USER_PACKAGE) + if (androidPackage == null) return null - val retType = c.typeMapper.mapType(propertyDescriptor.getReturnType()!!) - receiver.put(Type.getType("Landroid/app/Activity;"), c.v) - c.v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.getName().asString(), "I") - c.v.invokevirtual("android/app/Activity", "findViewById", "(I)" + "Landroid/view/View;", false) - c.v.checkcast(retType) + val className = DescriptorUtils.getFqName( + resolvedCall.getExtensionReceiver().getType().getConstructor().getDeclarationDescriptor()).toString() + val bytecodeClassName = className.replace('.', '/') - return StackValue.onStack(retType) + val retType = c.typeMapper.mapType(propertyDescriptor.getReturnType()!!) + receiver.put(Type.getType("L$bytecodeClassName;"), c.v) + c.v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.getName().asString(), "I") + c.v.invokevirtual(bytecodeClassName, methodName, "(I)Landroid/view/View;", false) + c.v.checkcast(retType) + + return StackValue.onStack(retType) + } + + private fun isClassSupported(descriptor: ClassifierDescriptor): Boolean { + fun classNameSupported(name: String): Boolean = when (name) { + "android.app.Activity" -> true + else -> false } + + if (descriptor is LazyJavaClassDescriptor) { + if (classNameSupported(descriptor.fqName.asString())) + return true + } else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity) + if (classNameSupported(DescriptorUtils.getFqName(descriptor).toString())) + return true + } + + return descriptor.getTypeConstructor().getSupertypes().any { + isClassSupported(it.getConstructor().getDeclarationDescriptor()) + } + } + + override fun generateClassSyntheticParts(codegen: ClassBuilder, clazz: JetClassOrObject, descriptor: DeclarationDescriptor) { + if (clazz !is JetClass || (clazz.getParent() is JetClassBody) || descriptor !is LazyClassDescriptor) return + + // Do not generate anything if class is not supported + if (clazz.isEnum() || clazz.isTrait() || clazz.isAnnotation() || clazz.isInner() || !isClassSupported(descriptor)) + return + + val className = clazz.getFqName().toString().replace('.', '/') + + val classType = Type.getType(className) + val viewType = Type.getType("Landroid/view/View;") + + codegen.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, propertyName, "Ljava/util/HashMap;", null, null) + + val methodVisitor = codegen.newMethod( + JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, methodName, "(I)Landroid/view/View;", null, null) + methodVisitor.visitCode() + val iv = InstructionAdapter(methodVisitor) + + fun getCache() { + iv.load(0, classType) + iv.getfield(className, propertyName, "Ljava/util/HashMap;") + } + + fun getId() = iv.load(1, Type.INT_TYPE) + + // Get cache property + iv.visitLabel(Label()) + getCache() + + val lCacheIsNull = Label() + val lCacheNonNull = Label() + iv.ifnonnull(lCacheNonNull) + + // Init cache if null + iv.visitLabel(lCacheIsNull) + iv.load(0, classType) + iv.anew(Type.getType("Ljava/util/HashMap;")) + iv.dup() + iv.invokespecial("java/util/HashMap", "", "()V", false) + iv.putfield(className, propertyName, "Ljava/util/HashMap;") + + // Get View from cache + iv.visitLabel(lCacheNonNull) + getCache() + getId() + iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) + iv.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false) + iv.checkcast(viewType) + iv.store(2, viewType) + + val lViewIsNull = Label() + val lViewNonNull = Label() + iv.load(2, viewType) + iv.ifnonnull(lViewNonNull) + + // Resolve View via findViewById if not in cache + iv.visitLabel(lViewIsNull) + iv.load(0, classType) + getId() + iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false) + iv.store(2, viewType) + + // Store resolved View in cache + getCache() + getId() + iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) + iv.load(2, viewType) + iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false) + iv.pop() + + iv.visitLabel(lViewNonNull) + iv.load(2, viewType) + iv.areturn(viewType) + + FunctionCodegen.endVisit(methodVisitor, methodName, clazz) + } } public class AndroidComponentRegistrar : ComponentRegistrar { diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/fqNameInAttr.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/fqNameInAttr.kt index ef8715e5506..aab67b137aa 100644 --- a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/fqNameInAttr.kt +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/fqNameInAttr.kt @@ -6,6 +6,7 @@ import android.app.Activity class MyActivity: Activity() { val button = this.MyButton } + // 1 GETSTATIC -// 1 INVOKEVIRTUAL +// 4 INVOKEVIRTUAL // 2 CHECKCAST diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTag/fqNameInTag.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTag/fqNameInTag.kt index fd6f3ac6700..aab67b137aa 100644 --- a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTag/fqNameInTag.kt +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTag/fqNameInTag.kt @@ -8,5 +8,5 @@ class MyActivity: Activity() { } // 1 GETSTATIC -// 1 INVOKEVIRTUAL +// 4 INVOKEVIRTUAL // 2 CHECKCAST diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFile/multiFile.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFile/multiFile.kt index 00262d7dc8a..f4bd65e7804 100644 --- a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFile/multiFile.kt +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFile/multiFile.kt @@ -2,12 +2,11 @@ package com.myapp import android.app.Activity - class MyActivity: Activity() { val button = this.login val button1 = this.loginButton } // 2 GETSTATIC -// 2 INVOKEVIRTUAL -// 4 CHECKCAST +// 5 INVOKEVIRTUAL +// 3 CHECKCAST diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simple/simple.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simple/simple.kt index 2c75fa6f43f..5715a5b5bf6 100644 --- a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simple/simple.kt +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simple/simple.kt @@ -10,5 +10,5 @@ public class MyActivity : Activity() { } // 1 GETSTATIC -// 1 INVOKEVIRTUAL -// 1 CHECKCAST +// 4 INVOKEVIRTUAL +// 2 CHECKCAST