Refactoring: handle base AndroidResource class (in order to support <fragment>)

This commit is contained in:
Yan Zhulanow
2015-04-03 16:27:13 +03:00
parent 2cf478c864
commit bc4a342a38
7 changed files with 88 additions and 50 deletions
@@ -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)
}
}
@@ -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 {
@@ -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)
@@ -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)
@@ -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
}