Call findViewById if invoked on anything but our Activity class

This commit is contained in:
Yan Zhulanow
2015-02-06 16:32:05 +03:00
parent 8ebd63ee96
commit db45ab4ad6
14 changed files with 210 additions and 56 deletions
@@ -403,7 +403,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
genClosureFields(context.closure, v, typeMapper); genClosureFields(context.closure, v, typeMapper);
for (ExpressionCodegenExtension extension : ExpressionCodegenExtension.Default.getInstances(state.getProject())) { for (ExpressionCodegenExtension extension : ExpressionCodegenExtension.Default.getInstances(state.getProject())) {
extension.generateClassSyntheticParts(v, myClass, descriptor); extension.generateClassSyntheticParts(v, bindingContext, myClass, descriptor);
} }
} }
@@ -23,7 +23,8 @@ import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.psi.JetClassOrObject import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.*
public trait ExpressionCodegenExtension { public trait ExpressionCodegenExtension {
class object : ProjectExtensionDescriptor<ExpressionCodegenExtension>("org.jetbrains.kotlin.expressionCodegenExtension", javaClass<ExpressionCodegenExtension>()) class object : ProjectExtensionDescriptor<ExpressionCodegenExtension>("org.jetbrains.kotlin.expressionCodegenExtension", javaClass<ExpressionCodegenExtension>())
@@ -36,7 +37,10 @@ public trait ExpressionCodegenExtension {
// return null if not applicable // return null if not applicable
public fun apply(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: Context): StackValue? public fun apply(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: Context): StackValue?
public fun generateClassSyntheticParts(codegen: ClassBuilder, clazz: JetClassOrObject, descriptor: DeclarationDescriptor) { public fun generateClassSyntheticParts(
classBuilder: ClassBuilder,
} bindingContext: BindingContext,
classOrObject: JetClassOrObject,
descriptor: ClassDescriptor
) {}
} }
@@ -31,26 +31,27 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.psi.JetFile import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor
import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.psi.JetClassOrObject import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.JetClass import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassBody import org.jetbrains.kotlin.psi.JetClassBody
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.codegen.FunctionCodegen
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.CompilerConfigurationKey import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.lang.resolve.android.AndroidConst import org.jetbrains.kotlin.lang.resolve.android.AndroidConst
import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor import org.jetbrains.kotlin.lang.resolve.android.AndroidUIXmlProcessor
import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor import org.jetbrains.kotlin.lang.resolve.android.CliAndroidUIXmlProcessor
import org.jetbrains.kotlin.resolve.scopes.receivers.ClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.psi.JetThisExpression
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.codegen.state.*
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.resolve.*
public object AndroidConfigurationKeys { public object AndroidConfigurationKeys {
@@ -88,8 +89,10 @@ public class CliAndroidDeclarationsProvider(private val project: Project) : Exte
} }
public class AndroidExpressionCodegen : ExpressionCodegenExtension { public class AndroidExpressionCodegen : ExpressionCodegenExtension {
val propertyName = "_\$_findViewByIdCache" class object {
val methodName = "_\$_findCachedViewById" private val PROPERTY_NAME = "_\$_findViewCache"
private val METHOD_NAME = "_\$_findCachedViewById"
}
override fun apply(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? { 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
@@ -102,16 +105,33 @@ public class AndroidExpressionCodegen : ExpressionCodegenExtension {
val androidPackage = file.getUserData<String>(AndroidConst.ANDROID_USER_PACKAGE) val androidPackage = file.getUserData<String>(AndroidConst.ANDROID_USER_PACKAGE)
if (androidPackage == null) return null if (androidPackage == null) return null
val className = DescriptorUtils.getFqName(
resolvedCall.getExtensionReceiver().getType().getConstructor().getDeclarationDescriptor()).toString()
val bytecodeClassName = className.replace('.', '/')
val retType = c.typeMapper.mapType(propertyDescriptor.getReturnType()!!) 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)
val extensionReceiver = resolvedCall.getExtensionReceiver()
val declarationDescriptor = extensionReceiver.getType().getConstructor().getDeclarationDescriptor()
val supportsCache = when {
extensionReceiver is ClassReceiver && declarationDescriptor != null -> true
else -> {
val source = declarationDescriptor?.getSource()
if (source is KotlinSourceElement) true else false
}
}
if (supportsCache) {
val className = DescriptorUtils.getFqName(declarationDescriptor!!).toString()
val bytecodeClassName = className.replace('.', '/')
receiver.put(Type.getType("L$bytecodeClassName;"), c.v)
c.v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.getName().asString(), "I")
c.v.invokevirtual(bytecodeClassName, METHOD_NAME, "(I)Landroid/view/View;", false)
} else {
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)
return StackValue.onStack(retType) return StackValue.onStack(retType)
} }
@@ -130,76 +150,76 @@ public class AndroidExpressionCodegen : ExpressionCodegenExtension {
} }
return descriptor.getTypeConstructor().getSupertypes().any { return descriptor.getTypeConstructor().getSupertypes().any {
isClassSupported(it.getConstructor().getDeclarationDescriptor()) val declarationDescriptor = it.getConstructor().getDeclarationDescriptor()
declarationDescriptor != null && isClassSupported(declarationDescriptor)
} }
} }
override fun generateClassSyntheticParts(codegen: ClassBuilder, clazz: JetClassOrObject, descriptor: DeclarationDescriptor) { override fun generateClassSyntheticParts(
if (clazz !is JetClass || (clazz.getParent() is JetClassBody) || descriptor !is LazyClassDescriptor) return classBuilder: ClassBuilder,
bindingContext: BindingContext,
classOrObject: JetClassOrObject,
descriptor: ClassDescriptor
) {
if (descriptor.getKind() != ClassKind.CLASS || descriptor.isInner() || DescriptorUtils.isLocal(descriptor)) return
// Do not generate anything if class is not supported // Do not generate anything if class is not supported
if (clazz.isEnum() || clazz.isTrait() || clazz.isAnnotation() || clazz.isInner() || !isClassSupported(descriptor)) if (!isClassSupported(descriptor)) return
return
val className = clazz.getFqName().toString().replace('.', '/') val classType = JetTypeMapper(bindingContext, ClassBuilderMode.FULL).mapClass(descriptor)
val className = classType.getInternalName()
val classType = Type.getType(className) val viewType = Type.getObjectType("android/view/View")
val viewType = Type.getType("Landroid/view/View;")
codegen.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, propertyName, "Ljava/util/HashMap;", null, null) classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null)
val methodVisitor = codegen.newMethod( val methodVisitor = classBuilder.newMethod(
JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, methodName, "(I)Landroid/view/View;", null, null) JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, METHOD_NAME, "(I)Landroid/view/View;", null, null)
methodVisitor.visitCode() methodVisitor.visitCode()
val iv = InstructionAdapter(methodVisitor) val iv = InstructionAdapter(methodVisitor)
fun getCache() { fun loadCache() {
iv.load(0, classType) iv.load(0, classType)
iv.getfield(className, propertyName, "Ljava/util/HashMap;") iv.getfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
} }
fun getId() = iv.load(1, Type.INT_TYPE) fun loadId() = iv.load(1, Type.INT_TYPE)
// Get cache property // Get cache property
iv.visitLabel(Label()) loadCache()
getCache()
val lCacheIsNull = Label()
val lCacheNonNull = Label() val lCacheNonNull = Label()
iv.ifnonnull(lCacheNonNull) iv.ifnonnull(lCacheNonNull)
// Init cache if null // Init cache if null
iv.visitLabel(lCacheIsNull)
iv.load(0, classType) iv.load(0, classType)
iv.anew(Type.getType("Ljava/util/HashMap;")) iv.anew(Type.getType("Ljava/util/HashMap;"))
iv.dup() iv.dup()
iv.invokespecial("java/util/HashMap", "<init>", "()V", false) iv.invokespecial("java/util/HashMap", "<init>", "()V", false)
iv.putfield(className, propertyName, "Ljava/util/HashMap;") iv.putfield(className, PROPERTY_NAME, "Ljava/util/HashMap;")
// Get View from cache // Get View from cache
iv.visitLabel(lCacheNonNull) iv.visitLabel(lCacheNonNull)
getCache() loadCache()
getId() loadId()
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) 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.invokevirtual("java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", false)
iv.checkcast(viewType) iv.checkcast(viewType)
iv.store(2, viewType) iv.store(2, viewType)
val lViewIsNull = Label()
val lViewNonNull = Label() val lViewNonNull = Label()
iv.load(2, viewType) iv.load(2, viewType)
iv.ifnonnull(lViewNonNull) iv.ifnonnull(lViewNonNull)
// Resolve View via findViewById if not in cache // Resolve View via findViewById if not in cache
iv.visitLabel(lViewIsNull)
iv.load(0, classType) iv.load(0, classType)
getId() loadId()
iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false) iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false)
iv.store(2, viewType) iv.store(2, viewType)
// Store resolved View in cache // Store resolved View in cache
getCache() loadCache()
getId() loadId()
iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false) iv.invokestatic("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
iv.load(2, viewType) iv.load(2, viewType)
iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false) iv.invokevirtual("java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)
@@ -209,7 +229,7 @@ public class AndroidExpressionCodegen : ExpressionCodegenExtension {
iv.load(2, viewType) iv.load(2, viewType)
iv.areturn(viewType) iv.areturn(viewType)
FunctionCodegen.endVisit(methodVisitor, methodName, clazz) FunctionCodegen.endVisit(methodVisitor, METHOD_NAME, classOrObject)
} }
} }
@@ -0,0 +1,15 @@
package com.myapp
import android.app.Activity
import kotlinx.android.synthetic.layout.*
fun Activity.a() {
val x = login
val y = this.login
}
// 2 GETSTATIC
// 2 INVOKEVIRTUAL
// 2 CHECKCAST
// 0 _\$_findCachedViewById
// 2 findViewById
@@ -0,0 +1,17 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -0,0 +1,21 @@
package com.myapp
import android.app.Activity
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.layout.*
public class MyActivity : Activity() {
}
fun MyActivity.b() {
val x = login
val y = this.login
}
// 2 GETSTATIC
// 5 INVOKEVIRTUAL
// 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
@@ -0,0 +1,17 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -0,0 +1,21 @@
package com.myapp
import android.app.Activity
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.layout.*
public class MyActivity : Activity() {
}
fun Activity.b() {
val x = login
val y = this.login
}
// 2 GETSTATIC
// 5 INVOKEVIRTUAL
// 3 CHECKCAST
// 1 _\$_findCachedViewById
// 3 findViewById
@@ -0,0 +1,17 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -5,8 +5,11 @@ import kotlinx.android.synthetic.layout.*
class MyActivity: Activity() { class MyActivity: Activity() {
val button = this.MyButton val button = this.MyButton
val button2 = MyButton
} }
// 1 GETSTATIC // 2 GETSTATIC
// 4 INVOKEVIRTUAL // 5 INVOKEVIRTUAL
// 2 CHECKCAST // 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
@@ -5,8 +5,11 @@ import kotlinx.android.synthetic.layout.*
class MyActivity: Activity() { class MyActivity: Activity() {
val button = this.MyButton val button = this.MyButton
val button2 = MyButton
} }
// 1 GETSTATIC // 2 GETSTATIC
// 4 INVOKEVIRTUAL // 5 INVOKEVIRTUAL
// 2 CHECKCAST // 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
@@ -12,3 +12,5 @@ class MyActivity: Activity() {
// 2 GETSTATIC // 2 GETSTATIC
// 5 INVOKEVIRTUAL // 5 INVOKEVIRTUAL
// 3 CHECKCAST // 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
@@ -12,3 +12,5 @@ public class MyActivity : Activity() {
// 1 GETSTATIC // 1 GETSTATIC
// 4 INVOKEVIRTUAL // 4 INVOKEVIRTUAL
// 2 CHECKCAST // 2 CHECKCAST
// 2 _\$_findCachedViewById
// 1 findViewById
@@ -36,6 +36,18 @@ public class AndroidBytecodeShapeTestGenerated extends AbstractAndroidBytecodeSh
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/android-compiler-plugin/testData/codegen/bytecodeShape"), Pattern.compile("^([^\\.]+)$"), false); JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/android-compiler-plugin/testData/codegen/bytecodeShape"), Pattern.compile("^([^\\.]+)$"), false);
} }
@TestMetadata("baseClass")
public void testBaseClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/baseClass/");
doTest(fileName);
}
@TestMetadata("extensionFunctions")
public void testExtensionFunctions() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/extensionFunctions/");
doTest(fileName);
}
@TestMetadata("fqNameInAttr") @TestMetadata("fqNameInAttr")
public void testFqNameInAttr() throws Exception { public void testFqNameInAttr() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/"); String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/");