Fix Android layout widget redeclarations
This commit is contained in:
+4
-3
@@ -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? {
|
||||
|
||||
+21
-5
@@ -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()
|
||||
|
||||
+7
-5
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
+33
@@ -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>
|
||||
+14
@@ -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
|
||||
|
||||
+11
@@ -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
|
||||
|
||||
+10
@@ -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>
|
||||
+10
@@ -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>
|
||||
+33
@@ -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
|
||||
|
||||
+25
@@ -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>
|
||||
+12
@@ -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/");
|
||||
|
||||
+2
-1
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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
|
||||
}))
|
||||
|
||||
+6
-5
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user