diff --git a/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt b/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt index 0c8487a8708..1ff8af9b4d2 100644 --- a/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt +++ b/plugins/android-compiler-plugin/src/AndroidExpressionCodegenExtension.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyClassDescriptor import org.jetbrains.kotlin.resolve.scopes.receivers.ClassReceiver import org.jetbrains.kotlin.resolve.source.KotlinSourceElement import org.jetbrains.kotlin.types.Flexibility +import org.jetbrains.kotlin.types.lowerIfFlexible import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PRIVATE import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC @@ -46,6 +47,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter private enum class AndroidClassType(val internalClassName: String, val supportsCache: Boolean = false) { ACTIVITY : AndroidClassType(AndroidConst.ACTIVITY_FQNAME.innerName, true) FRAGMENT : AndroidClassType(AndroidConst.FRAGMENT_FQNAME.innerName, true) + SUPPORT_FRAGMENT_ACTIVITY : AndroidClassType(AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME.innerName, true) SUPPORT_FRAGMENT : AndroidClassType(AndroidConst.SUPPORT_FRAGMENT_FQNAME.innerName, true) VIEW : AndroidClassType(AndroidConst.VIEW_FQNAME.innerName) UNKNOWN : AndroidClassType("") @@ -135,8 +137,12 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { ) : StackValue(typeMapper.mapType(descriptor.getReturnType()!!)) { override fun putSelector(type: Type, v: InstructionAdapter) { - if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) { + val returnTypeString = typeMapper.mapType(descriptor.getType().lowerIfFlexible()).getClassName() + if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) { + return putSelectorForFragment(v) + } + if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) { val declarationDescriptorType = typeMapper.mapType(declarationDescriptor) receiver.put(declarationDescriptorType, v) v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") @@ -144,15 +150,15 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { } else { when (androidClassType) { - AndroidClassType.ACTIVITY, AndroidClassType.VIEW -> { + AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW -> { receiver.put(Type.getType("L${androidClassType.internalClassName};"), v) - v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") + getResourceId(v) v.invokevirtual(androidClassType.internalClassName, "findViewById", "(I)Landroid/view/View;", false) } AndroidClassType.FRAGMENT, AndroidClassType.SUPPORT_FRAGMENT -> { receiver.put(Type.getType("L${androidClassType.internalClassName};"), v) v.invokevirtual(androidClassType.internalClassName, "getView", "()Landroid/view/View;", false) - v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") + getResourceId(v) v.invokevirtual("android/view/View", "findViewById", "(I)Landroid/view/View;", false) } else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur @@ -161,6 +167,35 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { v.checkcast(this.type) } + + private fun putSelectorForFragment(v: InstructionAdapter) { + receiver.put(Type.getType("L${androidClassType.internalClassName};"), v) + + when (androidClassType) { + AndroidClassType.ACTIVITY, AndroidClassType.FRAGMENT -> { + v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/app/FragmentManager;", false) + getResourceId(v) + v.invokevirtual("android/app/FragmentManager", "findFragmentById", "(I)Landroid/app/Fragment;", false) + } + AndroidClassType.SUPPORT_FRAGMENT -> { + v.invokevirtual(androidClassType.internalClassName, "getFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false) + getResourceId(v) + v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false) + } + AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY -> { + v.invokevirtual(androidClassType.internalClassName, "getSupportFragmentManager", "()Landroid/support/v4/app/FragmentManager;", false) + getResourceId(v) + v.invokevirtual("android/support/v4/app/FragmentManager", "findFragmentById", "(I)Landroid/support/v4/app/Fragment;", false) + } + else -> throw IllegalStateException("Invalid Android class type: $androidClassType") // Should never occur + } + + v.checkcast(this.type) + } + + fun getResourceId(v: InstructionAdapter) { + v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") + } } private fun CallableDescriptor.getAndroidPackage(): String? { @@ -175,6 +210,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { fun getClassTypeInternal(name: String): AndroidClassType? = when (name) { AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT + AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW else -> null @@ -292,7 +328,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { // Resolve View via findViewById if not in cache iv.load(0, classType) when (androidClassType) { - AndroidClassType.ACTIVITY, AndroidClassType.VIEW -> { + AndroidClassType.ACTIVITY, AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY, AndroidClassType.VIEW -> { loadId() iv.invokevirtual(className, "findViewById", "(I)Landroid/view/View;", false) } diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt index 923e3ef5334..4af074144e9 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidConst.kt @@ -46,8 +46,9 @@ public object AndroidConst { val FRAGMENT_FQNAME = "android.app.Fragment" val SUPPORT_V4_PACKAGE = "android.support.v4" val SUPPORT_FRAGMENT_FQNAME = "$SUPPORT_V4_PACKAGE.app.Fragment" + val SUPPORT_FRAGMENT_ACTIVITY_FQNAME = "$SUPPORT_V4_PACKAGE.app.FragmentActivity" - val IGNORED_XML_WIDGET_TYPES = setOf("requestFocus", "merge", "tag", "check", "fragment") + val IGNORED_XML_WIDGET_TYPES = setOf("requestFocus", "merge", "tag", "check") val ESCAPED_IDENTIFIERS = (JetTokens.KEYWORDS.getTypes() + JetTokens.SOFT_KEYWORDS.getTypes()) .map { it as? JetKeywordToken }.filterNotNull().map { it.getValue() }.toSet() diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt index f66875eb666..9262d9ba70a 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidUIXmlProcessor.kt @@ -66,6 +66,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { protected abstract val cachedSources: CachedValue> + //MAKE CONSTANT (or abstract) var supportV4 = false private val cachedJetFiles: CachedValue> by Delegates.lazy { @@ -141,9 +142,10 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { writeAndroidImports() for (res in resources) { - properties(res).forEach { - if (supportV4 || !it.first.startsWith(AndroidConst.SUPPORT_V4_PACKAGE)) { - writeSyntheticProperty(it.first, res, it.second) + properties(res).forEach { property -> + // Comment + if (supportV4 || !isFromSupportV4Package(property.first)) { + writeSyntheticProperty(property.first, res, property.second) } } } @@ -162,12 +164,13 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { } private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidResource, stubCall: String) { - val cast = if (widget.className != "View") " as? ${widget.className}" else "" + // extract startsWith() to fun + val className = if (isFromSupportV4Package(receiver)) widget.supportClassName() else widget.className + val cast = if (widget.className != "View") " as? $className" else "" val body = arrayListOf("return $stubCall$cast") - val type = widget.className writeImmutableExtensionProperty(receiver, name = widget.id, - retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$type, $type?>", + retType = "$EXPLICIT_FLEXIBLE_CLASS_NAME<$className, $className?>", getterBody = body) } @@ -179,6 +182,10 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { return CachedValuesManager.getManager(project).createCachedValue(result, false) } + private fun isFromSupportV4Package(fqName: String): Boolean { + return fqName.startsWith(AndroidConst.SUPPORT_V4_PACKAGE) + } + protected fun removeDuplicates(resources: List): List { val resourceMap = linkedMapOf() val resourcesToExclude = hashSetOf() diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt index 7a5e6dcecfe..dfe0c8eb8d9 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/androidResources.kt @@ -26,25 +26,41 @@ public abstract class AndroidResource(val id: String) { public open val viewProperties: List> = listOf() public open fun sameClass(other: AndroidResource): Boolean = false + + public open fun supportClassName(): String = className } public class AndroidWidget(id: String, override val className: String) : AndroidResource(id) { - override val mainProperties = listOf( - "android.app.Activity" to "findViewById(0)", - "android.app.Fragment" to "getView().findViewById(0)", - "android.support.v4.app.Fragment" to "getView().findViewById(0)") + private companion object { + val MAIN_PROPERTIES = listOf( + AndroidConst.ACTIVITY_FQNAME to "findViewById(0)", + AndroidConst.FRAGMENT_FQNAME to "getView().findViewById(0)", + AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getView().findViewById(0)") - override val viewProperties = listOf("android.view.View" to "findViewById(0)") + val VIEW_PROPERTIES = listOf("android.view.View" to "findViewById(0)") + } + + override val mainProperties = MAIN_PROPERTIES + + override val viewProperties = VIEW_PROPERTIES override fun sameClass(other: AndroidResource) = other is AndroidWidget } public class AndroidFragment(id: String) : AndroidResource(id) { - override val className = "Fragment" + private companion object { + val MAIN_PROPERTIES = listOf( + AndroidConst.ACTIVITY_FQNAME to "getFragmentManager().findFragmentById(0)", + AndroidConst.FRAGMENT_FQNAME to "getFragmentManager().findFragmentById(0)", + AndroidConst.SUPPORT_FRAGMENT_FQNAME to "getFragmentManager().findFragmentById(0)", + AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME to "getSupportFragmentManager().findFragmentById(0)") + } - override val mainProperties = listOf( - "android.app.Activity" to "getFragmentManager().findFragmentById(0)", - "android.app.Fragment" to "getActivity().getFragmentManager().findFragmentById(0)") + override val className = AndroidConst.FRAGMENT_FQNAME + + override val mainProperties = MAIN_PROPERTIES override fun sameClass(other: AndroidResource) = other is AndroidFragment + + override fun supportClassName() = AndroidConst.SUPPORT_FRAGMENT_FQNAME } \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/android/converter/simple/specialTags/test_LAYOUT.kt b/plugins/android-compiler-plugin/testData/android/converter/simple/specialTags/test_LAYOUT.kt index 2c3cf719911..384bf7848fe 100644 --- a/plugins/android-compiler-plugin/testData/android/converter/simple/specialTags/test_LAYOUT.kt +++ b/plugins/android-compiler-plugin/testData/android/converter/simple/specialTags/test_LAYOUT.kt @@ -18,6 +18,12 @@ val android.app.Activity.includeTag: ft val android.app.Fragment.includeTag: ft get() = getView().findViewById(0) +val android.app.Activity.fragmentTag: ft + get() = getFragmentManager().findFragmentById(0) as? android.app.Fragment + +val android.app.Fragment.fragmentTag: ft + get() = getFragmentManager().findFragmentById(0) as? android.app.Fragment + val android.app.Activity.`fun`: ft get() = findViewById(0) as? TextView diff --git a/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/AndroidManifest.xml b/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/AndroidManifest.xml new file mode 100644 index 00000000000..580c474f5de --- /dev/null +++ b/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/res/layout/test.xml b/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/res/layout/test.xml new file mode 100644 index 00000000000..fcfbf4f171b --- /dev/null +++ b/plugins/android-compiler-plugin/testData/android/converter/simple/supportSpecialTags/res/layout/test.xml @@ -0,0 +1,29 @@ + + + + + + + + + + +