Fix Android layout widget redeclarations

This commit is contained in:
Yan Zhulanow
2015-03-20 21:24:43 +03:00
parent c40598b3e9
commit a4dc6870dc
17 changed files with 224 additions and 21 deletions
@@ -33,9 +33,9 @@ public abstract class AndroidResourceManager(val project: Project) {
public open fun idToXmlAttribute(id: String): PsiElement? = null
open fun getLayoutXmlFiles(): List<PsiFile> {
public fun getLayoutXmlFiles(): Map<String, List<PsiFile>> {
val info = androidModuleInfo
if (info == null) return listOf()
if (info == null) return mapOf()
val psiManager = PsiManager.getInstance(project)
val fileManager = VirtualFileManager.getInstance()
@@ -61,7 +61,8 @@ public abstract class AndroidResourceManager(val project: Project) {
.filter { it.getParent().getName().startsWith("layout") && it.getName().toLowerCase().endsWith(".xml") }
.map { psiManager.findFile(it) }
.filterNotNull()
.sortBy { it.getName() }
.groupBy { it.getName().substringBeforeLast('.') }
.mapValues { it.getValue().sortBy { it.getParent().getName().length() } }
}
fun getMainResDirectory(): VirtualFile? {
@@ -69,7 +69,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
val psiManager = PsiManager.getInstance(project)
val applicationPackage = resourceManager.androidModuleInfo?.applicationPackage
val jetFiles = cachedSources.getValue().mapIndexed { (index, text) ->
val jetFiles = cachedSources.getValue().mapIndexed { index, text ->
val virtualFile = LightVirtualFile(AndroidConst.SYNTHETIC_FILENAME + index + ".kt", text)
val jetFile = psiManager.findFile(virtualFile) as JetFile
if (applicationPackage != null) {
@@ -89,10 +89,11 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
listOf(clearCacheFile, FLEXIBLE_TYPE_FILE)
} else listOf()
return resourceManager.getLayoutXmlFiles().flatMap { file ->
val widgets = parseSingleFile(file)
return resourceManager.getLayoutXmlFiles().flatMap { entry ->
val files = entry.getValue()
val widgets = parseLayout(files)
if (widgets.isNotEmpty()) {
val layoutPackage = file.genSyntheticPackageName()
val layoutPackage = files[0].genSyntheticPackageName()
val mainLayoutFile = renderLayoutFile(layoutPackage, widgets) {
writeSyntheticProperty("Activity", it, "findViewById(0)")
@@ -110,7 +111,7 @@ public abstract class AndroidUIXmlProcessor(protected val project: Project) {
public fun parseToPsi(): List<JetFile>? = cachedJetFiles.getValue()
protected abstract fun parseSingleFile(file: PsiFile): List<AndroidWidget>
protected abstract fun parseLayout(files: List<PsiFile>): List<AndroidWidget>
private fun renderLayoutFile(
packageName: String,
@@ -150,6 +151,21 @@ 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) {
// Widgets with the same id but different types exist.
widgetMap.put(widget.id, widget.copy(className = "View"))
}
}
else widgetMap.put(widget.id, widget)
}
return widgetMap.values().toList()
}
companion object {
private val EXPLICIT_FLEXIBLE_PACKAGE = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.getPackageFqName().asString()
private val EXPLICIT_FLEXIBLE_CLASS_NAME = Flexibility.FLEXIBLE_TYPE_CLASSIFIER.getRelativeClassName().asString()
@@ -43,14 +43,16 @@ public class CliAndroidUIXmlProcessor(
}
}
override fun parseSingleFile(file: PsiFile): List<AndroidWidget> {
override fun parseLayout(files: List<PsiFile>): List<AndroidWidget> {
val widgets = arrayListOf<AndroidWidget>()
val handler = AndroidXmlHandler { id, clazz -> widgets.add(AndroidWidget(id, clazz)) }
val handler = AndroidXmlHandler { id, widgetType -> widgets.add(AndroidWidget(id, widgetType)) }
try {
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
resourceManager.saxParser.parse(inputStream, handler)
return widgets
for (file in files) {
val inputStream = ByteArrayInputStream(file.getVirtualFile().contentsToByteArray())
resourceManager.saxParser.parse(inputStream, handler)
}
return removeDuplicates(widgets)
}
catch (e: Throwable) {
LOG.error(e)
@@ -20,4 +20,4 @@ public data class AndroidModuleInfo(val applicationPackage: String, val mainResD
trait AndroidResource
public class AndroidWidget(val id: String, val className: String) : AndroidResource
public data class AndroidWidget(val id: String, val className: String) : AndroidResource
@@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<permission android:name="android"></permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Sample" >
<activity
android:name="com.example.android.basiccontactables.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,14 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
import kotlin.internal.flexible.ft
val Activity.button: ft<View, View?>
get() = findViewById(0) : View
val Fragment.button: ft<View, View?>
get() = getView().findViewById(0) : View
@@ -0,0 +1,11 @@
package kotlinx.android.synthetic.layout.view
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
import kotlin.internal.flexible.ft
val View.button: ft<View, View?>
get() = findViewById(0) : View
@@ -0,0 +1,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
@@ -0,0 +1,10 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
@@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myapp"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<permission android:name="android"></permission>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Sample" >
<activity
android:name="com.example.android.basiccontactables.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop">
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,20 @@
package kotlinx.android.synthetic.layout
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
import kotlin.internal.flexible.ft
val Activity.textView1: ft<View, View?>
get() = findViewById(0) : View
val Fragment.textView1: ft<View, View?>
get() = getView().findViewById(0) : View
val Activity.textView2: ft<TextView, TextView?>
get() = findViewById(0) as TextView
val Fragment.textView2: ft<TextView, TextView?>
get() = getView().findViewById(0) as TextView
@@ -0,0 +1,14 @@
package kotlinx.android.synthetic.layout.view
import android.app.Activity
import android.app.Fragment
import android.view.View
import android.widget.*
import kotlin.internal.flexible.ft
val View.textView1: ft<View, View?>
get() = findViewById(0) : View
val View.textView2: ft<TextView, TextView?>
get() = findViewById(0) as TextView
@@ -0,0 +1,25 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
@@ -54,6 +54,12 @@ public class AndroidXml2KConversionTestGenerated extends AbstractAndroidXml2KCon
doTest(fileName);
}
@TestMetadata("layoutVariants")
public void testLayoutVariants() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/android/converter/simple/layoutVariants/");
doTest(fileName);
}
@TestMetadata("multiFile")
public void testMultiFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/android/converter/simple/multiFile/");
@@ -66,6 +72,12 @@ public class AndroidXml2KConversionTestGenerated extends AbstractAndroidXml2KCon
doTest(fileName);
}
@TestMetadata("sameIds")
public void testSameIds() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/android/converter/simple/sameIds/");
doTest(fileName);
}
@TestMetadata("singleFile")
public void testSingleFile() throws Exception {
String fileName = JetTestUtils.navigationMetadata("plugins/android-compiler-plugin/testData/android/converter/simple/singleFile/");
@@ -51,7 +51,8 @@ class AndroidFindMemberUsagesHandler(
override fun getPrimaryElements(): Array<PsiElement> {
assert(isAndroidSyntheticElement(declaration))
val name = (declaration as JetProperty).getName()!!
val property = declaration as JetProperty
val name = property.getName()!!
val parser = ServiceManager.getService(declaration.getProject(), javaClass<AndroidUIXmlProcessor>())
val psiElement = parser?.resourceManager?.idToXmlAttribute(name) as? XmlAttribute
if (psiElement != null && psiElement.getValueElement() != null) {
@@ -30,7 +30,7 @@ public class IDEAndroidResourceManager(val module: Module) : AndroidResourceMana
override fun idToXmlAttribute(id: String): PsiElement? {
var ret: PsiElement? = null
for (file in getLayoutXmlFiles()) {
for (file in getLayoutXmlFiles().values().flatMap { it }) {
file.accept(AndroidXmlVisitor({ retId, wClass, valueElement ->
if (retId == id) ret = valueElement
}))
@@ -41,13 +41,14 @@ class IDEAndroidUIXmlProcessor(val module: Module) : AndroidUIXmlProcessor(modul
}
}
override fun parseSingleFile(file: PsiFile): List<AndroidWidget> {
override fun parseLayout(files: List<PsiFile>): List<AndroidWidget> {
val widgets = arrayListOf<AndroidWidget>()
file.accept(AndroidXmlVisitor({ id, wClass, valueElement ->
widgets.add(AndroidWidget(id, wClass))
}))
val visitor = AndroidXmlVisitor { id, widgetType, attribute ->
widgets.add(AndroidWidget(id, widgetType))
}
return widgets
files.forEach { it.accept(visitor) }
return removeDuplicates(widgets)
}
}