Merge "Infer args types" into flatfoot-navigation
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
index b46c066..9c47b29 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
@@ -38,8 +38,9 @@
private const val NAMESPACE_RES_AUTO = "http://schemas.android.com/apk/res-auto"
private const val NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"
-private fun parseDestination(parser: XmlPullParser, rFilePackage: String,
- applicationId: String): Destination {
+private fun parseDestination(
+ parser: XmlPullParser, rFilePackage: String,
+ applicationId: String): Destination {
val type = parser.name
val name = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_NAME) ?: ""
val idValue = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_ID)
@@ -68,15 +69,32 @@
val name = parser.attrValueOrThrow(NAMESPACE_ANDROID, ATTRIBUTE_NAME)
val defaultValue = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_DEFAULT_VALUE)
val typeString = parser.attrValue(NAMESPACE_RES_AUTO, ATTRIBUTE_TYPE)
+ if (typeString == null && defaultValue != null) {
+ return inferArgument(name, defaultValue, rFilePackage)
+ }
val (type, defaultTypedValue) = when (typeString) {
- "integer" -> NavType.INT to parseIntValue(defaultValue)
- "reference" -> NavType.REFERENCE to parseReferenceValue(defaultValue, rFilePackage)
+ "integer" -> NavType.INT to defaultValue?.let { parseIntValue(defaultValue) }
+ "reference" -> NavType.REFERENCE to defaultValue?.let {
+ ReferenceValue(parseReference(defaultValue, rFilePackage))
+ }
else -> NavType.STRING to defaultValue?.let { StringValue(it) }
}
return Argument(name, type, defaultTypedValue)
}
+internal fun inferArgument(name: String, defaultValue: String, rFilePackage: String): Argument {
+ val reference = tryToParseReference(defaultValue, rFilePackage)
+ if (reference != null) {
+ return Argument(name, NavType.REFERENCE, ReferenceValue(reference))
+ }
+ val intValue = tryToParseIntValue(defaultValue)
+ if (intValue != null) {
+ return Argument(name, NavType.INT, intValue)
+ }
+ return Argument(name, NavType.STRING, StringValue(defaultValue))
+}
+
private fun parseAction(parser: XmlPullParser, rFilePackage: String): Action {
val idValue = parser.attrValueOrThrow(NAMESPACE_ANDROID, ATTRIBUTE_ID)
val destValue = parser.attrValue(NAMESPACE_RES_AUTO, ATTRIBUTE_DESTINATION)
@@ -91,7 +109,7 @@
}
fun parseNavigationFile(navigationXml: File, rFilePackage: String,
- applicationId: String): Destination {
+ applicationId: String): Destination {
FileReader(navigationXml).use { reader ->
val parser = XmlPullParserFactory.newInstance().newPullParser().apply {
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
@@ -103,15 +121,20 @@
}
// @[+][package:]id/resource_name -> package.R.id.resource_name
+private val RESOURCE_REGEX = Regex("^@[+]?(.+?:)?(.+?)/(.+)$")
+
internal fun parseReference(xmlValue: String, rFilePackage: String): ResReference {
- val split = xmlValue.removePrefix("@").removePrefix("+").split(":", "/")
- if (split.size != 2 && split.size != 3) {
- throw IllegalArgumentException("id should be in format: " +
- "@[+][package:]res_type/resource_name, but is: $xmlValue")
- }
- val resourceName = split.last()
- val resType = split[split.size - 2]
- val packageName = if (split.size == 3) split[0] else rFilePackage
+ return tryToParseReference(xmlValue, rFilePackage) ?:
+ throw IllegalArgumentException("id should be in format: " +
+ "@[+][package:]res_type/resource_name, but is: $xmlValue")
+}
+
+internal fun tryToParseReference(xmlValue: String, rFilePackage: String): ResReference? {
+ val matchEntire = RESOURCE_REGEX.matchEntire(xmlValue) ?: return null
+ val groups = matchEntire.groupValues
+ val resourceName = groups.last()
+ val resType = groups[groups.size - 2]
+ val packageName = if (groups[1].isNotEmpty()) groups[1].removeSuffix(":") else rFilePackage
return ResReference(packageName, resType, resourceName)
}
@@ -127,26 +150,20 @@
parseId(it, rFilePackage)
}
-internal fun parseIntValue(value: String?): IntValue? {
- if (value == null) {
+private fun tryToParseIntValue(value: String): IntValue? {
+ try {
+ if (value.startsWith("0x")) {
+ Integer.parseUnsignedInt(value.substring(2), 16)
+ } else {
+ Integer.parseInt(value)
+ }
+ } catch (ex: NumberFormatException) {
return null
}
- if (value.startsWith("0x")) {
- try {
- Integer.parseUnsignedInt(value.substring(2), 16)
- return IntValue(value)
- } catch (ex: NumberFormatException) {
- throw IllegalArgumentException("Failed to parse $value as int")
- }
- }
-
- try {
- Integer.parseInt(value)
- return IntValue(value)
- } catch (ex: NumberFormatException) {
- throw IllegalArgumentException("Failed to parse $value as int")
- }
+ return IntValue(value)
}
-internal fun parseReferenceValue(value: String?, rFilePackage: String) =
- value?.let { ReferenceValue(parseReference(value, rFilePackage)) }
+internal fun parseIntValue(value: String): IntValue {
+ return tryToParseIntValue(value)
+ ?: throw IllegalArgumentException("Failed to parse $value as int")
+}
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
index 1fab491..2164674 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
@@ -17,6 +17,7 @@
package androidx.navigation.safe.args.generator
import androidx.navigation.safe.args.generator.NavType.INT
+import androidx.navigation.safe.args.generator.NavType.REFERENCE
import androidx.navigation.safe.args.generator.NavType.STRING
import androidx.navigation.safe.args.generator.models.Action
import androidx.navigation.safe.args.generator.models.Argument
@@ -85,6 +86,26 @@
instanceOf(IllegalArgumentException::class.java))
}
+ @Test
+ fun testArgInference() {
+ val infer = { value: String -> inferArgument("foo", value, "a.b") }
+ val intArg = { value: String -> Argument("foo", INT, IntValue(value)) }
+ val stringArg = { value: String -> Argument("foo", STRING, StringValue(value)) }
+ val referenceArg = { pName: String, type: String, value: String ->
+ Argument("foo", REFERENCE, ReferenceValue(ResReference(pName, type, value)))
+ }
+
+ assertThat(infer("spb"), `is`(stringArg("spb")))
+ assertThat(infer("10"), `is`(intArg("10")))
+ assertThat(infer("0x10"), `is`(intArg("0x10")))
+ assertThat(infer("@android:id/some_la"), `is`(referenceArg("android", "id", "some_la")))
+ assertThat(infer("@foo"), `is`(stringArg("@foo")))
+ assertThat(infer("@+id/foo"), `is`(referenceArg("a.b", "id", "foo")))
+ assertThat(infer("@foo:stuff"), `is`(stringArg("@foo:stuff")))
+ assertThat(infer("@/stuff"), `is`(stringArg("@/stuff")))
+ assertThat(infer("10101010100100"), `is`(stringArg("10101010100100")))
+ }
+
private fun errorOf(f: () -> Unit, message: String = ""): Exception {
try {
f()