1 /*
<lambda>null2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.navigation.serialization
18
19 import androidx.navigation.CollectionNavType
20 import androidx.navigation.NavType
21 import androidx.savedstate.SavedState
22 import java.io.Serializable
23 import kotlinx.serialization.ExperimentalSerializationApi
24 import kotlinx.serialization.descriptors.SerialDescriptor
25 import kotlinx.serialization.descriptors.SerialKind
26
27 internal actual fun SerialDescriptor.parseEnum(): NavType<*> =
28 NavType.parseSerializableOrParcelableType(getClass(), false) ?: UNKNOWN
29
30 internal actual fun SerialDescriptor.parseNullableEnum(): NavType<*> {
31 val clazz = getClass()
32 return if (Enum::class.java.isAssignableFrom(clazz)) {
33 @Suppress("UNCHECKED_CAST")
34 InternalAndroidNavType.EnumNullableType(clazz as Class<Enum<*>?>)
35 } else UNKNOWN
36 }
37
38 @OptIn(ExperimentalSerializationApi::class)
parseEnumListnull39 internal actual fun SerialDescriptor.parseEnumList(): NavType<*> {
40 @Suppress("UNCHECKED_CAST")
41 return InternalAndroidNavType.EnumListType(getElementDescriptor(0).getClass() as Class<Enum<*>>)
42 }
43
44 @OptIn(ExperimentalSerializationApi::class)
getClassnull45 private fun SerialDescriptor.getClass(): Class<*> {
46 var className = serialName.replace("?", "")
47 // first try to get class with original class name
48 try {
49 return Class.forName(className)
50 } catch (_: ClassNotFoundException) {}
51 // Otherwise, it might be nested Class. Try incrementally replacing last `.` with `$`
52 // until we find the correct enum class name.
53 while (className.contains(".")) {
54 className = Regex("(\\.+)(?!.*\\.)").replace(className, "\\$")
55 try {
56 return Class.forName(className)
57 } catch (_: ClassNotFoundException) {}
58 }
59 var errorMsg =
60 "Cannot find class with name \"$serialName\". Ensure that the " +
61 "serialName for this argument is the default fully qualified name"
62 if (kind is SerialKind.ENUM) {
63 errorMsg =
64 "$errorMsg.\nIf the build is minified, try annotating the Enum class with \"androidx.annotation.Keep\" to ensure the Enum is not removed."
65 }
66 throw IllegalArgumentException(errorMsg)
67 }
68
69 internal object InternalAndroidNavType {
70 class EnumNullableType<D : Enum<*>?>(type: Class<D?>) : SerializableNullableType<D?>(type) {
71 private val type: Class<D?>
72
73 /** Constructs a NavType that supports a given Enum type. */
74 init {
<lambda>null75 require(type.isEnum) { "$type is not an Enum type." }
76 this.type = type
77 }
78
79 override val name: String
80 get() = type.name
81
parseValuenull82 override fun parseValue(value: String): D? {
83 return if (value == "null") {
84 null
85 } else {
86 type.enumConstants!!.firstOrNull { constant ->
87 constant!!.name.equals(value, ignoreCase = true)
88 }
89 ?: throw IllegalArgumentException(
90 "Enum value $value not found for type ${type.name}."
91 )
92 }
93 }
94 }
95
96 // Base Serializable class to support nullable EnumNullableType
97 open class SerializableNullableType<D : Serializable?>(private val type: Class<D?>) :
98 NavType<D?>(true) {
99
100 override val name: String
101 get() = type.name
102
103 init {
<lambda>null104 require(Serializable::class.java.isAssignableFrom(type)) {
105 "$type does not implement Serializable."
106 }
107 }
108
putnull109 override fun put(bundle: SavedState, key: String, value: D?) {
110 bundle.putSerializable(key, type.cast(value))
111 }
112
113 @Suppress("UNCHECKED_CAST", "DEPRECATION")
getnull114 override fun get(bundle: SavedState, key: String): D? {
115 return bundle[key] as? D
116 }
117
118 /**
119 * @throws UnsupportedOperationException since Serializables do not support default values
120 */
parseValuenull121 public override fun parseValue(value: String): D? {
122 throw UnsupportedOperationException("Serializables don't support default values.")
123 }
124
equalsnull125 public override fun equals(other: Any?): Boolean {
126 if (this === other) return true
127 if (other !is SerializableNullableType<*>) return false
128 return type == other.type
129 }
130
hashCodenull131 public override fun hashCode(): Int {
132 return type.hashCode()
133 }
134 }
135
136 class EnumListType<D : Enum<*>>(type: Class<D>) : CollectionNavType<List<D>?>(true) {
137 private val enumNavType = NavType.EnumType(type)
138
139 override val name: String
140 get() = "List<${enumNavType.name}}>"
141
putnull142 override fun put(bundle: SavedState, key: String, value: List<D>?) {
143 bundle.putSerializable(key, value?.let { ArrayList(value) })
144 }
145
146 @Suppress("DEPRECATION", "UNCHECKED_CAST")
getnull147 override fun get(bundle: SavedState, key: String): List<D>? = (bundle[key] as? List<D>?)
148
149 override fun parseValue(value: String): List<D> = listOf(enumNavType.parseValue(value))
150
151 override fun parseValue(value: String, previousValue: List<D>?): List<D>? =
152 previousValue?.plus(parseValue(value)) ?: parseValue(value)
153
154 override fun valueEquals(value: List<D>?, other: List<D>?): Boolean {
155 val valueArrayList = value?.let { ArrayList(value) }
156 val otherArrayList = other?.let { ArrayList(other) }
157 return valueArrayList == otherArrayList
158 }
159
serializeAsValuesnull160 override fun serializeAsValues(value: List<D>?): List<String> =
161 value?.map { it.toString() } ?: emptyList()
162
emptyCollectionnull163 override fun emptyCollection(): List<D> = emptyList()
164
165 public override fun equals(other: Any?): Boolean {
166 if (this === other) return true
167 if (other !is EnumListType<*>) return false
168 return enumNavType == other.enumNavType
169 }
170
hashCodenull171 public override fun hashCode(): Int {
172 return enumNavType.hashCode()
173 }
174 }
175 }
176