Support Fragment in android-compiler-plugin

This commit is contained in:
Yan Zhulanow
2015-03-03 17:41:48 +03:00
parent d4184bd51d
commit da8369066b
68 changed files with 1160 additions and 25 deletions
@@ -42,6 +42,12 @@ import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
private enum class AndroidClassType {
ACTIVITY
FRAGMENT
UNKNOWN
}
public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
default object {
private val PROPERTY_NAME = "_\$_findViewCache"
@@ -64,49 +70,70 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
val extensionReceiver = resolvedCall.getExtensionReceiver()
val declarationDescriptor = extensionReceiver.getType().getConstructor().getDeclarationDescriptor()
if (declarationDescriptor == null) return null
val supportsCache = when {
extensionReceiver is ClassReceiver && declarationDescriptor != null -> true
extensionReceiver is ClassReceiver -> true
else -> {
val source = declarationDescriptor?.getSource()
val source = declarationDescriptor.getSource()
if (source is KotlinSourceElement) true else false
}
}
if (supportsCache) {
val className = DescriptorUtils.getFqName(declarationDescriptor!!).toString()
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)
when (getClassType(declarationDescriptor)) {
AndroidClassType.ACTIVITY -> {
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)
}
AndroidClassType.FRAGMENT -> {
receiver.put(Type.getType("Landroid/app/Fragment;"), c.v)
c.v.invokevirtual("android/app/Fragment", "getView", "()Landroid/view/View;", false)
c.v.getstatic(androidPackage.replace(".", "/") + "/R\$id", propertyDescriptor.getName().asString(), "I")
c.v.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
else -> return null
}
}
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
private fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType {
fun getClassTypeInternal(name: String): AndroidClassType? = when (name) {
"android.app.Activity" -> AndroidClassType.ACTIVITY
"android.app.Fragment" -> AndroidClassType.FRAGMENT
else -> null
}
if (descriptor is LazyJavaClassDescriptor) {
if (classNameSupported(descriptor.fqName.asString()))
return true
val androidClassType = getClassTypeInternal(descriptor.fqName.asString())
if (androidClassType != null) return androidClassType
} else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity)
if (classNameSupported(DescriptorUtils.getFqName(descriptor).toString()))
return true
val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString())
if (androidClassType != null) return androidClassType
}
return descriptor.getTypeConstructor().getSupertypes().any {
val declarationDescriptor = it.getConstructor().getDeclarationDescriptor()
declarationDescriptor != null && isClassSupported(declarationDescriptor)
for (supertype in descriptor.getTypeConstructor().getSupertypes()) {
val declarationDescriptor = supertype.getConstructor().getDeclarationDescriptor()
if (declarationDescriptor != null) {
val androidClassType = getClassType(declarationDescriptor)
if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType
}
}
return AndroidClassType.UNKNOWN
}
override fun generateClassSyntheticParts(
@@ -118,7 +145,8 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
if (descriptor.getKind() != ClassKind.CLASS || descriptor.isInner() || DescriptorUtils.isLocal(descriptor)) return
// Do not generate anything if class is not supported
if (!isClassSupported(descriptor)) return
val androidClassType = getClassType(descriptor)
if (androidClassType == AndroidClassType.UNKNOWN) return
val classType = JetTypeMapper(bindingContext, ClassBuilderMode.FULL).mapClass(descriptor)
val className = classType.getInternalName()
@@ -167,8 +195,17 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension {
// Resolve View via findViewById if not in cache
iv.load(0, classType)
loadId()
iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false)
when (androidClassType) {
AndroidClassType.ACTIVITY -> {
loadId()
iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false)
}
AndroidClassType.FRAGMENT -> {
iv.invokevirtual(className, "getView", "()Landroid/view/View;", false)
loadId()
iv.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false)
}
}
iv.store(2, viewType)
// Store resolved View in cache
@@ -59,6 +59,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
private val androidImports = listOf(
"android.app.Activity",
"android.app.Fragment",
"android.view.View",
"android.widget.*")
@@ -101,7 +102,10 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
stringWriter.writePackage(layoutPackage)
stringWriter.writeAndroidImports()
widgets.forEach { stringWriter.writeSyntheticActivityProperty(it) }
widgets.forEach {
stringWriter.writeSyntheticProperty("Activity", it, "findViewById(0)")
stringWriter.writeSyntheticProperty("Fragment", it, "getView().findViewById(0)")
}
val contents = stringWriter.toStringBuffer().toString()
contents
@@ -122,9 +126,9 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
return AndroidConst.SYNTHETIC_PACKAGE + getName().substringBefore('.')
}
private fun KotlinStringWriter.writeSyntheticActivityProperty(widget: AndroidWidget) {
val body = arrayListOf("return findViewById(0) as ${widget.className}")
writeImmutableExtensionProperty(receiver = "Activity",
private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidWidget, stubCall: String) {
val body = arrayListOf("return $stubCall as ${widget.className}")
writeImmutableExtensionProperty(receiver,
name = widget.id,
retType = widget.className,
getterBody = body)
@@ -1,9 +1,13 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
val Activity.MyButton: org.my.cool.Button
get() = findViewById(0) as org.my.cool.Button
val Fragment.MyButton: org.my.cool.Button
get() = getView().findViewById(0) as org.my.cool.Button
@@ -1,9 +1,13 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
val Activity.MyButton: org.my.cool.Button
get() = findViewById(0) as org.my.cool.Button
val Fragment.MyButton: org.my.cool.Button
get() = getView().findViewById(0) as org.my.cool.Button
@@ -1,18 +1,31 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
val Activity.item_detail_container: FrameLayout
get() = findViewById(0) as FrameLayout
val Fragment.item_detail_container: FrameLayout
get() = getView().findViewById(0) as FrameLayout
val Activity.textView1: TextView
get() = findViewById(0) as TextView
val Fragment.textView1: TextView
get() = getView().findViewById(0) as TextView
val Activity.password: EditText
get() = findViewById(0) as EditText
val Fragment.password: EditText
get() = getView().findViewById(0) as EditText
val Activity.login: Button
get() = findViewById(0) as Button
val Fragment.login: Button
get() = getView().findViewById(0) as Button
@@ -1,18 +1,30 @@
package kotlinx.android.synthetic.layout1
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
val Activity.frameLayout: FrameLayout
get() = findViewById(0) as FrameLayout
val Fragment.frameLayout: FrameLayout
get() = getView().findViewById(0) as FrameLayout
val Activity.passwordField: TextView
get() = findViewById(0) as TextView
val Fragment.passwordField: TextView
get() = getView().findViewById(0) as TextView
val Activity.passwordCaption: EditText
get() = findViewById(0) as EditText
val Fragment.passwordCaption: EditText
get() = getView().findViewById(0) as EditText
val Activity.loginButton: Button
get() = findViewById(0) as Button
val Fragment.loginButton: Button
get() = getView().findViewById(0) as Button
@@ -1,18 +1,31 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
val Activity.item_detail_container: FrameLayout
get() = findViewById(0) as FrameLayout
val Fragment.item_detail_container: FrameLayout
get() = getView().findViewById(0) as FrameLayout
val Activity.textView1: TextView
get() = findViewById(0) as TextView
val Fragment.textView1: TextView
get() = getView().findViewById(0) as TextView
val Activity.password: EditText
get() = findViewById(0) as EditText
val Fragment.password: EditText
get() = getView().findViewById(0) as EditText
val Activity.login: Button
get() = findViewById(0) as Button
val Fragment.login: Button
get() = getView().findViewById(0) as Button
@@ -0,0 +1,8 @@
package android.app
import android.view.View
import android.content.Context
abstract class Fragment {
abstract fun getView(): View
}
@@ -2,4 +2,6 @@ package android.view
import android.content.Context
open class View(ctx: Context)
open class View(ctx: Context) {
open fun findViewById(id: Int): View? = null
}
@@ -0,0 +1,16 @@
package com.myapp
import android.app.Fragment
import kotlinx.android.synthetic.layout.*
fun Fragment.a() {
val x = login
val y = this.login
}
// 2 GETSTATIC
// 4 INVOKEVIRTUAL
// 2 CHECKCAST
// 0 _\$_findCachedViewById
// 2 findViewById
// 2 getView
@@ -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,22 @@
package com.myapp
import android.app.Fragment
import android.os.Bundle
import java.io.File
import kotlinx.android.synthetic.layout.*
public class MyFragment : Fragment() {
}
fun Fragment.b() {
val x = login
val y = this.login
}
// 2 GETSTATIC
// 8 INVOKEVIRTUAL
// 3 CHECKCAST
// 1 _\$_findCachedViewById
// 3 findViewById
// 3 getView
@@ -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,16 @@
package com.myapp
import android.app.Fragment
import kotlinx.android.synthetic.layout.*
class MyFragment: Fragment() {
val button = this.MyButton
val button2 = MyButton
}
// 2 GETSTATIC
// 6 INVOKEVIRTUAL
// 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
// 1 getView
@@ -0,0 +1,15 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<view
class="android.widget.Button"
android:id="@+id/MyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -0,0 +1,16 @@
package com.myapp
import android.app.Fragment
import kotlinx.android.synthetic.layout.*
class MyFragment: Fragment() {
val button = this.MyButton
val button2 = MyButton
}
// 2 GETSTATIC
// 6 INVOKEVIRTUAL
// 3 CHECKCAST
// 3 _\$_findCachedViewById
// 1 findViewById
// 1 getView
@@ -0,0 +1,14 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<android.widget.Button
android:id="@+id/MyButton"
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.app.Fragment
import kotlinx.android.synthetic.layout.*
import kotlinx.android.synthetic.layout1.*
class MyActivity: Activity() {
val button = this.login
}
class MyFragment: Fragment() {
val button1 = this.loginButton
}
// 2 GETSTATIC
// 9 INVOKEVIRTUAL
// 4 CHECKCAST
// 4 _\$_findCachedViewById
// 2 findViewById
// 1 getView
@@ -0,0 +1,29 @@
<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" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter your password" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword" />
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -0,0 +1,29 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ItemDetailActivity"
tools:ignore="MergeRootFrame" >
<TextView
android:id="@+id/passwordField"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enter your password" />
<EditText
android:id="@+id/passwordCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPassword" />
<Button
android:id="@+id/loginButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sign in" />
</FrameLayout>
@@ -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,16 @@
package com.myapp
import android.app.Fragment
import java.io.File
import kotlinx.android.synthetic.layout.*
public class MyFragment : Fragment() {
{login}
}
// 1 GETSTATIC
// 5 INVOKEVIRTUAL
// 2 CHECKCAST
// 2 _\$_findCachedViewById
// 1 findViewById
// 1 getView
@@ -54,6 +54,12 @@ public class AndroidBoxTestGenerated extends AbstractAndroidBoxTest {
doCompileAgainstAndroidSdkTest(fileName);
}
@TestMetadata("fragment")
public void testFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/android/fragment/");
doCompileAgainstAndroidSdkTest(fileName);
}
@TestMetadata("manyWidgets")
public void testManyWidgets() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/android/manyWidgets/");
@@ -93,6 +99,12 @@ public class AndroidBoxTestGenerated extends AbstractAndroidBoxTest {
doFakeInvocationTest(fileName);
}
@TestMetadata("fragment")
public void testFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/android/fragment/");
doFakeInvocationTest(fileName);
}
@TestMetadata("manyWidgets")
public void testManyWidgets() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/android/manyWidgets/");
@@ -42,6 +42,12 @@ public class AndroidBytecodeShapeTestGenerated extends AbstractAndroidBytecodeSh
doTest(fileName);
}
@TestMetadata("baseClassFragment")
public void testBaseClassFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/baseClassFragment/");
doTest(fileName);
}
@TestMetadata("extensionFunctions")
public void testExtensionFunctions() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/extensionFunctions/");
@@ -54,27 +60,63 @@ public class AndroidBytecodeShapeTestGenerated extends AbstractAndroidBytecodeSh
doTest(fileName);
}
@TestMetadata("extensionFunctionsBaseClassFragment")
public void testExtensionFunctionsBaseClassFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/extensionFunctionsBaseClassFragment/");
doTest(fileName);
}
@TestMetadata("extensionFunctionsFragment")
public void testExtensionFunctionsFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/extensionFunctionsFragment/");
doTest(fileName);
}
@TestMetadata("fqNameInAttr")
public void testFqNameInAttr() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttr/");
doTest(fileName);
}
@TestMetadata("fqNameInAttrFragment")
public void testFqNameInAttrFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInAttrFragment/");
doTest(fileName);
}
@TestMetadata("fqNameInTag")
public void testFqNameInTag() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTag/");
doTest(fileName);
}
@TestMetadata("fqNameInTagFragment")
public void testFqNameInTagFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/fqNameInTagFragment/");
doTest(fileName);
}
@TestMetadata("multiFile")
public void testMultiFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFile/");
doTest(fileName);
}
@TestMetadata("multiFileFragment")
public void testMultiFileFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/multiFileFragment/");
doTest(fileName);
}
@TestMetadata("simple")
public void testSimple() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simple/");
doTest(fileName);
}
@TestMetadata("simpleFragment")
public void testSimpleFragment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/codegen/bytecodeShape/simpleFragment/");
doTest(fileName);
}
}