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 ab36673021d..5dcd5eaecb5 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 @@ -64,8 +64,14 @@ public fun isWidgetTypeIgnored(xmlType: String): Boolean { return (xmlType.isEmpty() || xmlType in AndroidConst.IGNORED_XML_WIDGET_TYPES) } -public fun getRealWidgetType(xmlType: String): String = if (xmlType == "include") "View" else xmlType - fun escapeAndroidIdentifier(id: String): String { return if (id in AndroidConst.ESCAPED_IDENTIFIERS) "`$id`" else id +} + +public fun parseAndroidResource(id: String, type: String): AndroidResource { + return when (type) { + "fragment" -> AndroidFragment(id) + "include" -> AndroidWidget(id, "View") + else -> AndroidWidget(id, type) + } } \ No newline at end of file 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 94ab5a79987..19bef63c64c 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 @@ -103,40 +103,45 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { return resourceManager.getLayoutXmlFiles().flatMap { entry -> val files = entry.getValue() - val widgets = parseLayout(files) + val resources = parseLayout(files) val layoutName = files[0].getName().substringBefore('.') - val packageName = AndroidConst.SYNTHETIC_PACKAGE + "." + files[0].getEscapedLayoutName() - - val mainLayoutFile = renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, packageName, widgets) { - writeSyntheticProperty("android.app.Activity", it, "findViewById(0)") - writeSyntheticProperty("android.app.Fragment", it, "getView().findViewById(0)") - } - - val viewLayoutFile = renderLayoutFile(layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX, "$packageName.view", widgets) { - writeSyntheticProperty("android.view.View", it, "findViewById(0)") - } + val mainLayoutFile = renderMainLayoutFile(layoutName, resources) + val viewLayoutFile = renderViewLayoutFile(layoutName, resources) listOf(mainLayoutFile, viewLayoutFile) }.filterNotNull() + commonFiles } public fun parseToPsi(): List? = cachedJetFiles.getValue() - protected abstract fun parseLayout(files: List): List + protected abstract fun parseLayout(files: List): List + + private fun renderMainLayoutFile(layoutName: String, resources: List): AndroidSyntheticFile { + return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX, + escapeAndroidIdentifier(layoutName), resources) { it.mainProperties } + } + + private fun renderViewLayoutFile(layoutName: String, resources: List): AndroidSyntheticFile { + return renderLayoutFile(layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX, + escapeAndroidIdentifier(layoutName) + ".view", resources) { it.viewProperties } + } private fun renderLayoutFile( - name: String, - packageName: String, - widgets: List = listOf(), - widgetWriter: KotlinStringWriter.(AndroidWidget) -> Unit - ): AndroidSyntheticFile { - val stringWriter = KotlinStringWriter() - stringWriter.writePackage(packageName) - stringWriter.writeAndroidImports() - widgets.forEach { stringWriter.widgetWriter(it) } + filename: String, + packageSegment: String, + resources: List, + properties: (AndroidResource) -> List>): AndroidSyntheticFile { + return renderSyntheticFile(filename) { + writePackage(AndroidConst.SYNTHETIC_PACKAGE + "." + packageSegment) + writeAndroidImports() - return AndroidSyntheticFile(name, stringWriter.toStringBuffer().toString()) + for (res in resources) { + properties(res).forEach { + writeSyntheticProperty(it.first, res, it.second) + } + } + } } private fun renderSyntheticFile(filename: String, init: KotlinStringWriter.() -> Unit): AndroidSyntheticFile { @@ -150,11 +155,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { writeEmptyLine() } - private fun PsiFile.getEscapedLayoutName(): String { - return escapeAndroidIdentifier(getName().substringBefore('.')) - } - - private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidWidget, stubCall: String) { + private fun KotlinStringWriter.writeSyntheticProperty(receiver: String, widget: AndroidResource, stubCall: String) { val cast = if (widget.className != "View") " as? ${widget.className}" else "" val body = arrayListOf("return $stubCall$cast") val type = widget.className @@ -172,19 +173,25 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) { return CachedValuesManager.getManager(project).createCachedValue(result, false) } - protected fun removeDuplicates(widgets: List): List { - val widgetMap = linkedMapOf() - for (widget in widgets) { - if (widgetMap.contains(widget.id)) { - val existingElement = widgetMap.get(widget.id) - if (existingElement.className != widget.className && existingElement.className != "View") { + protected fun removeDuplicates(resources: List): List { + val resourceMap = linkedMapOf() + val resourcesToExclude = hashSetOf() + + for (res in resources) { + if (resourceMap.contains(res.id)) { + val existing = resourceMap[res.id] + + if (!res.sameClass(existing)) { + resourcesToExclude.add(res.id) + } else if (res is AndroidWidget && existing.className != res.className && existing.className != "View") { // Widgets with the same id but different types exist. - widgetMap.put(widget.id, widget.copy(className = "View")) + resourceMap.put(res.id, AndroidWidget(res.id, "View")) } } - else widgetMap.put(widget.id, widget) + else resourceMap.put(res.id, res) } - return widgetMap.values().toList() + resourcesToExclude.forEach { resourceMap.remove(it) } + return resourceMap.values().toList() } companion object { diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidXmlHandler.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidXmlHandler.kt index c514f7716fb..eddbac7b516 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidXmlHandler.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/AndroidXmlHandler.kt @@ -34,7 +34,7 @@ class AndroidXmlHandler(private val elementCallback: (String, String) -> Unit) : if (isWidgetTypeIgnored(localName)) return val attributesMap = attributes.toMap() val idAttribute = attributesMap[AndroidConst.ID_ATTRIBUTE_NO_NAMESPACE] - val widgetType = getRealWidgetType(attributesMap[AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE] ?: localName) + val widgetType = attributesMap[AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE] ?: localName if (isResourceDeclarationOrUsage(idAttribute)) { val name = idToName(idAttribute) if (name != null) elementCallback(name, widgetType) diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt index 2a8ad77019f..c705d53244e 100644 --- a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/lang/resolve/android/CliAndroidUIXmlProcessor.kt @@ -43,16 +43,16 @@ public class CliAndroidUIXmlProcessor( } } - override fun parseLayout(files: List): List { - val widgets = arrayListOf() - val handler = AndroidXmlHandler { id, widgetType -> widgets.add(AndroidWidget(id, widgetType)) } + override fun parseLayout(files: List): List { + val resources = arrayListOf() + val handler = AndroidXmlHandler { id, widgetType -> resources.add(parseAndroidResource(id, widgetType)) } try { for (file in files) { val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray()) resourceManager.saxParser.parse(inputStream, handler) } - return removeDuplicates(widgets) + return removeDuplicates(resources) } catch (e: Throwable) { LOG.error(e) 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 23f2d4e4849..665c803f592 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 @@ -18,6 +18,32 @@ package org.jetbrains.kotlin.lang.resolve.android public data class AndroidModuleInfo(val applicationPackage: String, val mainResDirectory: String?) -trait AndroidResource +public abstract class AndroidResource(val id: String) { + public abstract val className: String -public data class AndroidWidget(val id: String, val className: String) : AndroidResource \ No newline at end of file + public abstract val mainProperties: List> + + public open val viewProperties: List> = listOf() + + public open fun sameClass(other: AndroidResource): Boolean = false +} + +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)") + + override val viewProperties = listOf("android.view.View" to "findViewById(0)") + + override fun sameClass(other: AndroidResource) = other is AndroidWidget +} + +public class AndroidFragment(id: String) : AndroidResource(id) { + override val className = "Fragment" + + override val mainProperties = listOf( + "android.app.Activity" to "getFragmentManager().findFragmentById(0)", + "android.app.Fragment" to "getActivity().getFragmentManager().findFragmentById(0)") + + override fun sameClass(other: AndroidResource) = other is AndroidFragment +} \ No newline at end of file diff --git a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/AndroidXmlVisitor.kt b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/AndroidXmlVisitor.kt index 78c0a401f1c..44df1bcaeab 100644 --- a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/AndroidXmlVisitor.kt +++ b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/AndroidXmlVisitor.kt @@ -44,10 +44,9 @@ class AndroidXmlVisitor(val elementCallback: (String, String, XmlAttribute) -> U if (idAttributeValue != null) { val classAttributeValue = tag?.getAttribute(AndroidConst.CLASS_ATTRIBUTE_NO_NAMESPACE)?.getValue() val xmlType = classAttributeValue ?: localName - val widgetType = xmlType.let { getRealWidgetType(it) } if (isResourceDeclarationOrUsage(idAttributeValue)) { val name = idToName(idAttributeValue) - if (name != null) elementCallback(name, widgetType, idAttribute) + if (name != null) elementCallback(name, xmlType, idAttribute) } } } diff --git a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt index 9eb72db6843..9458b6fc84b 100644 --- a/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt +++ b/plugins/android-idea-plugin/src/org/jetbrains/kotlin/plugin/android/IDEAndroidUIXmlProcessor.kt @@ -41,10 +41,10 @@ class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(modul } } - override fun parseLayout(files: List): List { - val widgets = arrayListOf() + override fun parseLayout(files: List): List { + val widgets = arrayListOf() val visitor = AndroidXmlVisitor { id, widgetType, attribute -> - widgets.add(AndroidWidget(id, widgetType)) + widgets.add(parseAndroidResource(id, widgetType)) } files.forEach { it.accept(visitor) }