Extend data-binding resource types
In DB expressions users can use different key words for resource types, which are later converted to R class field references, e.g. "text" for "string".
Extend the resource types based on these rules.
Fixes: 184833062
Test: verified with user's project + added and updated unit tests + MultiModuleTestApp
Change-Id: I62f6caf2badddbcb47cafc4fa3251373ccd4f057
diff --git a/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java b/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java
index b0d1b1c..14b4c9d 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/ResourceExpr.java
@@ -22,6 +22,7 @@
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.writer.KCode;
import android.databinding.tool.writer.LayoutBinderWriterKt;
+import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.List;
@@ -32,6 +33,7 @@
public class ResourceExpr extends Expr {
private final static Map<String, String> RESOURCE_TYPE_TO_R_OBJECT;
+ public final static Map<String, List<String>> R_OBJECT_TO_RESOURCE_TYPE;
static {
RESOURCE_TYPE_TO_R_OBJECT = new HashMap<String, String>();
RESOURCE_TYPE_TO_R_OBJECT.put("colorStateList", "color");
@@ -42,6 +44,15 @@
RESOURCE_TYPE_TO_R_OBJECT.put("stringArray", "array");
RESOURCE_TYPE_TO_R_OBJECT.put("text", "string");
RESOURCE_TYPE_TO_R_OBJECT.put("typedArray", "array");
+
+ // TODO(184833062): clean up so it reads from the above map and reverses it more elegantly
+ R_OBJECT_TO_RESOURCE_TYPE = new HashMap<String, List<String>>();
+ R_OBJECT_TO_RESOURCE_TYPE.put("color", Lists.newArrayList("colorStateList"));
+ R_OBJECT_TO_RESOURCE_TYPE.put("dimen", Lists.newArrayList("dimenOffset", "dimenSize"));
+ R_OBJECT_TO_RESOURCE_TYPE.put(
+ "array", Lists.newArrayList("intArray", "stringArray", "typedArray"));
+ R_OBJECT_TO_RESOURCE_TYPE.put("animator", Lists.newArrayList("stateListAnimator"));
+ R_OBJECT_TO_RESOURCE_TYPE.put("string", Lists.newArrayList("text"));
}
// lazily initialized
private Map<String, ModelClass> mResourceToTypeMapping;
diff --git a/compiler/src/main/java/android/databinding/tool/util/SymbolTableUtil.kt b/compiler/src/main/java/android/databinding/tool/util/SymbolTableUtil.kt
index 5b0bd1d..ab57d62 100644
--- a/compiler/src/main/java/android/databinding/tool/util/SymbolTableUtil.kt
+++ b/compiler/src/main/java/android/databinding/tool/util/SymbolTableUtil.kt
@@ -16,6 +16,7 @@
@file:JvmName("SymbolTableUtil")
package android.databinding.tool.util
+import android.databinding.tool.expr.ResourceExpr
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMultimap
import java.io.File
@@ -29,6 +30,9 @@
* Returns the prefix to be used in R class references in Java/Kotlin. For example: "android."
* for "android.R.color.white", "com.my.lib." for "com.my.lib.R.string.text" and an empty string
* for default R class reference e.g. "R.attr.my_attr".
+ *
+ * This method accepts data-binding specific "types", e.g. using "text" (for "string" resources)
+ * or "intArray" ( for "array" resources).
*/
fun getRPackagePrefix(packageName: String?, type: String, name: String): String {
when {
@@ -192,7 +196,7 @@
// Format is <type> <name> for all resources apart from Styleables.
if (chunks.size < 2 || (chunks[0] != STYLEABLE && chunks.size != 2))
error("Illegal line in R.txt: '$line'")
- resources.put(chunks[0], sanitizeName(chunks[1]))
+ addResource(chunks[0], sanitizeName(chunks[1]), resources)
if (chunks[0] == STYLEABLE) {
// For styleables the format is <type> <name> <child1> <child2> ... <childN>
// The resulting children need to be added as Styleable <parent>_<child>.
@@ -207,6 +211,22 @@
return resources.build()
}
+fun addResource(
+ type: String,
+ name: String,
+ resourcesBuilder: ImmutableMultimap.Builder<String, String>) {
+
+ // Some expressions in data-binding correspond to different names in the R class. To be able
+ // to verify them, we need to add the resource to all matching expressions, e.g:
+ // - "string" to "text"
+ // - "array" to "intArray" AND "stringArray" AND "typedArray"
+ ResourceExpr.R_OBJECT_TO_RESOURCE_TYPE[type]?.forEach {
+ resourcesBuilder.put(it, name)
+ }
+
+ resourcesBuilder.put(type, name)
+}
+
private fun sanitizeName(name: String): String {
return name.replace('.', '_').replace(':', '_')
}
diff --git a/compiler/src/test/java/android/databinding/tool/util/SymbolTableUtilTest.kt b/compiler/src/test/java/android/databinding/tool/util/SymbolTableUtilTest.kt
index 867477a..f33738c 100644
--- a/compiler/src/test/java/android/databinding/tool/util/SymbolTableUtilTest.kt
+++ b/compiler/src/test/java/android/databinding/tool/util/SymbolTableUtilTest.kt
@@ -56,14 +56,15 @@
val result = parseLocalRTxt(testFile.toFile())
assertEquals(result.rPackage, "")
- assertEquals(result.resources.keySet().size, 3)
- assertEquals(result.resources.values().size, 5)
+ assertEquals(result.resources.keySet().size, 4) // 3 + "text"
+ assertEquals(result.resources.values().size, 6) // "first" counted twice
assertTrue(result.contains("string", "first"))
assertTrue(result.contains("int", "second"))
assertTrue(result.contains("styleable", "parent"))
assertTrue(result.contains("styleable", "parent_child1"))
assertTrue(result.contains("styleable", "parent_child2"))
+ assertTrue(result.contains("text", "first"))
}
@Test
@@ -79,9 +80,10 @@
val result = parsePackageAwareRTxt(testFile.toFile())
assertEquals(result.rPackage, "com.test.lib")
- assertEquals(result.resources.keySet().size, 1)
- assertEquals(result.resources.values().size, 1)
+ assertEquals(result.resources.keySet().size, 2) // string -> string, text
+ assertEquals(result.resources.values().toSet().size, 1) // "first" twice
assertTrue(result.contains("string", "first"))
+ assertTrue(result.contains("text", "first"))
}
@Test
@@ -142,8 +144,9 @@
val symbolTables = result.build()
assertEquals(symbolTables.size, 4)
assertEquals(symbolTables[0].rPackage, "com.test.mid.lib")
- assertEquals(symbolTables[0].resources.size(), 2)
+ assertEquals(symbolTables[0].resources.size(), 4) // string -> string, text
assertTrue(symbolTables[0].contains("string", "bar"))
+ assertTrue(symbolTables[0].contains("text", "bar"))
assertEquals(symbolTables[2].rPackage, "com.test.empty.lib")
assertEquals(symbolTables[2].resources.size(), 0)
@@ -178,6 +181,36 @@
}
@Test
+ fun testDataBindingKeyWords() {
+ val testFile = Files.createTempFile("dependency", "R.txt")
+ Files.write(
+ testFile,
+ """
+ com.test.lib
+ string first
+ array my_list
+ dimen dimension
+ color foo
+ """.trimIndent().toByteArray())
+
+ val result = parsePackageAwareRTxt(testFile.toFile())
+
+ assertEquals(result.resources.size(), 11)
+ assertTrue(result.contains("string", "first"))
+ assertTrue(result.contains("text", "first"))
+ assertTrue(result.contains("array", "my_list"))
+ assertTrue(result.contains("intArray", "my_list"))
+ assertTrue(result.contains("stringArray", "my_list"))
+ assertTrue(result.contains("typedArray", "my_list"))
+ assertTrue(result.contains("dimen", "dimension"))
+ assertTrue(result.contains("dimenOffset", "dimension"))
+ assertTrue(result.contains("dimenSize", "dimension"))
+ assertTrue(result.contains("color", "foo"))
+ assertTrue(result.contains("colorStateList", "foo"))
+
+ }
+
+ @Test
fun testEmptyResources() {
val testFile = Files.createTempFile("local", "R.txt")
Files.write(testFile, "".toByteArray())
diff --git a/integration-tests/MultiModuleTestApp/app/src/main/res/layout/layout_with_text_ref.xml b/integration-tests/MultiModuleTestApp/app/src/main/res/layout/layout_with_text_ref.xml
new file mode 100644
index 0000000..1dcee03
--- /dev/null
+++ b/integration-tests/MultiModuleTestApp/app/src/main/res/layout/layout_with_text_ref.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+ <LinearLayout
+ android:orientation="vertical" android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:text="@{@text/my_text}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+</layout>
diff --git a/integration-tests/MultiModuleTestApp/testlibrary1/src/main/res/values/strings.xml b/integration-tests/MultiModuleTestApp/testlibrary1/src/main/res/values/strings.xml
index 4fbed97..b24bfb3 100644
--- a/integration-tests/MultiModuleTestApp/testlibrary1/src/main/res/values/strings.xml
+++ b/integration-tests/MultiModuleTestApp/testlibrary1/src/main/res/values/strings.xml
@@ -21,5 +21,6 @@
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="library_string">Lib</string>
+ <string name="my_text">Hello world</string>
</resources>