Refactoring: handle base AndroidResource class (in order to support <fragment>)
This commit is contained in:
+8
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
+43
-36
@@ -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<JetFile>? = cachedJetFiles.getValue()
|
||||
|
||||
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidWidget>
|
||||
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidResource>
|
||||
|
||||
private fun renderMainLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
|
||||
return renderLayoutFile(layoutName + AndroidConst.LAYOUT_POSTFIX,
|
||||
escapeAndroidIdentifier(layoutName), resources) { it.mainProperties }
|
||||
}
|
||||
|
||||
private fun renderViewLayoutFile(layoutName: String, resources: List<AndroidResource>): AndroidSyntheticFile {
|
||||
return renderLayoutFile(layoutName + AndroidConst.VIEW_LAYOUT_POSTFIX,
|
||||
escapeAndroidIdentifier(layoutName) + ".view", resources) { it.viewProperties }
|
||||
}
|
||||
|
||||
private fun renderLayoutFile(
|
||||
name: String,
|
||||
packageName: String,
|
||||
widgets: List<AndroidWidget> = 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<AndroidResource>,
|
||||
properties: (AndroidResource) -> List<Pair<String, String>>): 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<AndroidWidget>): List<AndroidWidget> {
|
||||
val widgetMap = linkedMapOf<String, AndroidWidget>()
|
||||
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<AndroidResource>): List<AndroidResource> {
|
||||
val resourceMap = linkedMapOf<String, AndroidResource>()
|
||||
val resourcesToExclude = hashSetOf<String>()
|
||||
|
||||
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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+4
-4
@@ -43,16 +43,16 @@ public class CliAndroidUIXmlProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun parseLayout(files: List<PsiFile>): List<AndroidWidget> {
|
||||
val widgets = arrayListOf<AndroidWidget>()
|
||||
val handler = AndroidXmlHandler { id, widgetType -> widgets.add(AndroidWidget(id, widgetType)) }
|
||||
override fun parseLayout(files: List<PsiFile>): List<AndroidResource> {
|
||||
val resources = arrayListOf<AndroidResource>()
|
||||
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)
|
||||
|
||||
+28
-2
@@ -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
|
||||
public abstract val mainProperties: List<Pair<String, String>>
|
||||
|
||||
public open val viewProperties: List<Pair<String, String>> = 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
|
||||
}
|
||||
Reference in New Issue
Block a user