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