1 /*
<lambda>null2  * Copyright 2018 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 package androidx.navigation
17 
18 import android.os.Parcelable
19 import androidx.annotation.AnyRes
20 import androidx.annotation.RestrictTo
21 import androidx.savedstate.SavedState
22 import androidx.savedstate.read
23 import androidx.savedstate.write
24 import java.io.Serializable
25 
26 public actual abstract class NavType<T>
27 actual constructor(public actual open val isNullableAllowed: Boolean) {
28 
29     public actual abstract fun put(bundle: SavedState, key: String, value: T)
30 
31     public actual abstract operator fun get(bundle: SavedState, key: String): T?
32 
33     public actual abstract fun parseValue(value: String): T
34 
35     public actual open fun parseValue(value: String, previousValue: T): T = parseValue(value)
36 
37     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
38     public actual fun parseAndPut(bundle: SavedState, key: String, value: String): T =
39         navTypeParseAndPut(bundle, key, value)
40 
41     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
42     public actual fun parseAndPut(
43         bundle: SavedState,
44         key: String,
45         value: String?,
46         previousValue: T
47     ): T = navTypeParseAndPut(bundle, key, value, previousValue)
48 
49     public actual open fun serializeAsValue(value: T): String = value.toString()
50 
51     public actual open val name: String = "nav_type"
52 
53     public actual open fun valueEquals(value: T, other: T): Boolean = value == other
54 
55     override fun toString(): String = name
56 
57     public actual companion object {
58         @Suppress("NON_FINAL_MEMBER_IN_OBJECT", "UNCHECKED_CAST") // this needs to be open to
59         // maintain api compatibility and type cast are unchecked
60         @JvmStatic
61         public actual open fun fromArgType(type: String?, packageName: String?): NavType<*> {
62             return navTypeFromArgType(type)
63                 ?: when {
64                     ReferenceType.name == type -> return ReferenceType
65                     !type.isNullOrEmpty() -> {
66                         try {
67                             var className: String
68                             className =
69                                 if (type.startsWith(".") && packageName != null) {
70                                     packageName + type
71                                 } else {
72                                     type
73                                 }
74                             val isArray = type.endsWith("[]")
75                             if (isArray) className = className.substring(0, className.length - 2)
76                             val clazz = Class.forName(className)
77                             return requireNotNull(
78                                 parseSerializableOrParcelableType(clazz, isArray)
79                             ) {
80                                 "$className is not Serializable or Parcelable."
81                             }
82                         } catch (e: ClassNotFoundException) {
83                             throw RuntimeException(e)
84                         }
85                     }
86                     else -> StringType
87                 }
88         }
89 
90         @Suppress("UNCHECKED_CAST")
91         internal fun parseSerializableOrParcelableType(
92             clazz: Class<*>,
93             isArray: Boolean
94         ): NavType<*>? =
95             when {
96                 Parcelable::class.java.isAssignableFrom(clazz) -> {
97                     if (isArray) {
98                         ParcelableArrayType(clazz as Class<Parcelable>)
99                     } else {
100                         ParcelableType(clazz as Class<Any?>)
101                     }
102                 }
103                 Enum::class.java.isAssignableFrom(clazz) && !isArray ->
104                     EnumType(clazz as Class<Enum<*>>)
105                 Serializable::class.java.isAssignableFrom(clazz) -> {
106                     if (isArray) {
107                         SerializableArrayType(clazz as Class<Serializable>)
108                     } else {
109                         SerializableType(clazz as Class<Serializable>)
110                     }
111                 }
112                 else -> null
113             }
114 
115         @JvmStatic
116         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
117         public actual fun inferFromValue(value: String): NavType<Any> = navTypeInferFromValue(value)
118 
119         @Suppress("UNCHECKED_CAST") // needed for cast to NavType<Any>
120         @JvmStatic
121         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
122         public actual fun inferFromValueType(value: Any?): NavType<Any> {
123             return navTypeInferFromValueType(value)
124                 ?: when {
125                     value is Array<*> && value.isArrayOf<String>() ->
126                         StringArrayType as NavType<Any>
127                     value!!.javaClass.isArray &&
128                         Parcelable::class
129                             .java
130                             .isAssignableFrom(value.javaClass.componentType!!) -> {
131                         ParcelableArrayType(value.javaClass.componentType as Class<Parcelable>)
132                             as NavType<Any>
133                     }
134                     value.javaClass.isArray &&
135                         Serializable::class
136                             .java
137                             .isAssignableFrom(value.javaClass.componentType!!) -> {
138                         SerializableArrayType(value.javaClass.componentType as Class<Serializable>)
139                             as NavType<Any>
140                     }
141                     value is Parcelable -> ParcelableType(value.javaClass) as NavType<Any>
142                     value is Enum<*> -> EnumType(value.javaClass) as NavType<Any>
143                     value is Serializable -> SerializableType(value.javaClass) as NavType<Any>
144                     else -> {
145                         throw IllegalArgumentException(
146                             "Object of type ${value.javaClass.name} is not supported for navigation " +
147                                 "arguments."
148                         )
149                     }
150                 }
151         }
152 
153         @JvmField public actual val IntType: NavType<Int> = IntNavType()
154 
155         /**
156          * NavType for storing integer values representing resource ids, corresponding with the
157          * "reference" type in a Navigation XML file.
158          *
159          * Null values are not supported.
160          */
161         @JvmField
162         public val ReferenceType: NavType<Int> =
163             object : NavType<Int>(false) {
164                 override val name: String
165                     get() = "reference"
166 
167                 override fun put(bundle: SavedState, key: String, @AnyRes value: Int) {
168                     bundle.write { putInt(key, value) }
169                 }
170 
171                 @AnyRes
172                 @Suppress("DEPRECATION")
173                 override fun get(bundle: SavedState, key: String): Int = bundle.read { getInt(key) }
174 
175                 override fun parseValue(value: String): Int {
176                     return if (value.startsWith("0x")) {
177                         value.substring(2).toInt(16)
178                     } else {
179                         value.toInt()
180                     }
181                 }
182             }
183 
184         @JvmField public actual val IntArrayType: NavType<IntArray?> = IntArrayNavType()
185         @JvmField public actual val IntListType: NavType<List<Int>?> = IntListNavType()
186         @JvmField public actual val LongType: NavType<Long> = LongNavType()
187         @JvmField public actual val LongArrayType: NavType<LongArray?> = LongArrayNavType()
188         @JvmField public actual val LongListType: NavType<List<Long>?> = LongListNavType()
189         @JvmField public actual val FloatType: NavType<Float> = FloatNavType()
190         @JvmField public actual val FloatArrayType: NavType<FloatArray?> = FloatArrayNavType()
191         @JvmField public actual val FloatListType: NavType<List<Float>?> = FloatListNavType()
192         @JvmField public actual val BoolType: NavType<Boolean> = BoolNavType()
193         @JvmField public actual val BoolArrayType: NavType<BooleanArray?> = BoolArrayNavType()
194         @JvmField public actual val BoolListType: NavType<List<Boolean>?> = BoolListNavType()
195         @JvmField public actual val StringType: NavType<String?> = StringNavType()
196         @JvmField public actual val StringArrayType: NavType<Array<String>?> = StringArrayNavType()
197         @JvmField public actual val StringListType: NavType<List<String>?> = StringListNavType()
198     }
199 
200     /**
201      * ParcelableType is used for passing Parcelables in [NavArgument]s.
202      *
203      * Null values are supported. Default values in Navigation XML files are not supported.
204      *
205      * @param type the Parcelable class that is supported by this NavType
206      */
207     public class ParcelableType<D>(type: Class<D>) : NavType<D>(true) {
208         private val type: Class<D>
209 
210         public override val name: String
211             get() = type.name
212 
213         public override fun put(bundle: SavedState, key: String, value: D) {
214             type.cast(value)
215             if (value == null || value is Parcelable) {
216                 bundle.putParcelable(key, value as Parcelable?)
217             } else if (value is Serializable) {
218                 bundle.putSerializable(key, value as Serializable?)
219             }
220         }
221 
222         @Suppress("UNCHECKED_CAST", "DEPRECATION")
223         public override fun get(bundle: SavedState, key: String): D? {
224             return bundle[key] as D?
225         }
226 
227         /** @throws UnsupportedOperationException since Parcelables do not support default values */
228         public override fun parseValue(value: String): D {
229             throw UnsupportedOperationException("Parcelables don't support default values.")
230         }
231 
232         public override fun equals(other: Any?): Boolean {
233             if (this === other) return true
234             if (other == null || javaClass != other.javaClass) return false
235             val that = other as ParcelableType<*>
236             return type == that.type
237         }
238 
239         public override fun hashCode(): Int {
240             return type.hashCode()
241         }
242 
243         /** Constructs a NavType that supports a given Parcelable type. */
244         init {
245             require(
246                 Parcelable::class.java.isAssignableFrom(type) ||
247                     Serializable::class.java.isAssignableFrom(type)
248             ) {
249                 "$type does not implement Parcelable or Serializable."
250             }
251             this.type = type
252         }
253     }
254 
255     /**
256      * ParcelableArrayType is used for [NavArgument]s which hold arrays of Parcelables.
257      *
258      * Null values are supported. Default values in Navigation XML files are not supported.
259      *
260      * @param type the type of Parcelable component class of the array
261      */
262     public class ParcelableArrayType<D : Parcelable>(type: Class<D>) : NavType<Array<D>?>(true) {
263         private val arrayType: Class<Array<D>>
264 
265         public override val name: String
266             get() = arrayType.name
267 
268         public override fun put(bundle: SavedState, key: String, value: Array<D>?) {
269             arrayType.cast(value)
270             bundle.putParcelableArray(key, value)
271         }
272 
273         @Suppress("UNCHECKED_CAST", "DEPRECATION")
274         public override fun get(bundle: SavedState, key: String): Array<D>? {
275             return bundle[key] as Array<D>?
276         }
277 
278         /** @throws UnsupportedOperationException since Arrays do not support default values */
279         public override fun parseValue(value: String): Array<D> {
280             throw UnsupportedOperationException("Arrays don't support default values.")
281         }
282 
283         @Suppress("UNCHECKED_CAST")
284         public override fun equals(other: Any?): Boolean {
285             if (this === other) return true
286             if (other == null || javaClass != other.javaClass) return false
287             val that = other as ParcelableArrayType<Parcelable>
288             return arrayType == that.arrayType
289         }
290 
291         public override fun hashCode(): Int {
292             return arrayType.hashCode()
293         }
294 
295         override fun valueEquals(
296             @Suppress("ArrayReturn") value: Array<D>?,
297             @Suppress("ArrayReturn") other: Array<D>?
298         ): Boolean = value.contentDeepEquals(other)
299 
300         /** Constructs a NavType that supports arrays of a given Parcelable type. */
301         init {
302             require(Parcelable::class.java.isAssignableFrom(type)) {
303                 "$type does not implement Parcelable."
304             }
305             val arrayType: Class<Array<D>> =
306                 try {
307                     @Suppress("UNCHECKED_CAST")
308                     Class.forName("[L${type.name};") as Class<Array<D>>
309                 } catch (e: ClassNotFoundException) {
310                     throw RuntimeException(e) // should never happen
311                 }
312             this.arrayType = arrayType
313         }
314     }
315 
316     /**
317      * SerializableType is used for Serializable [NavArgument]s. For handling Enums you must use
318      * [EnumType] instead.
319      *
320      * Null values are supported. Default values in Navigation XML files are not supported.
321      *
322      * @see EnumType
323      */
324     public open class SerializableType<D : Serializable> : NavType<D> {
325         private val type: Class<D>
326 
327         public override val name: String
328             get() = type.name
329 
330         /**
331          * Constructs a NavType that supports a given Serializable type.
332          *
333          * @param type class that is a subtype of Serializable
334          */
335         public constructor(type: Class<D>) : super(true) {
336             require(Serializable::class.java.isAssignableFrom(type)) {
337                 "$type does not implement Serializable."
338             }
339             require(!type.isEnum) { "$type is an Enum. You should use EnumType instead." }
340             this.type = type
341         }
342 
343         internal constructor(nullableAllowed: Boolean, type: Class<D>) : super(nullableAllowed) {
344             require(Serializable::class.java.isAssignableFrom(type)) {
345                 "$type does not implement Serializable."
346             }
347             this.type = type
348         }
349 
350         public override fun put(bundle: SavedState, key: String, value: D) {
351             type.cast(value)
352             bundle.putSerializable(key, value)
353         }
354 
355         @Suppress("UNCHECKED_CAST", "DEPRECATION")
356         public override fun get(bundle: SavedState, key: String): D? {
357             return bundle[key] as D?
358         }
359 
360         /**
361          * @throws UnsupportedOperationException since Serializables do not support default values
362          */
363         public override fun parseValue(value: String): D {
364             throw UnsupportedOperationException("Serializables don't support default values.")
365         }
366 
367         public override fun equals(other: Any?): Boolean {
368             if (this === other) return true
369             if (other !is SerializableType<*>) return false
370             return type == other.type
371         }
372 
373         public override fun hashCode(): Int {
374             return type.hashCode()
375         }
376     }
377 
378     /**
379      * EnumType is used for [NavArgument]s holding enum values.
380      *
381      * Null values are not supported. To specify a default value in a Navigation XML file, simply
382      * use the enum constant without the class name, e.g. `app:defaultValue="MONDAY"`.
383      *
384      * @param type the Enum class that is supported by this NavType
385      */
386     public class EnumType<D : Enum<*>>(type: Class<D>) : SerializableType<D>(false, type) {
387         private val type: Class<D>
388 
389         public override val name: String
390             get() = type.name
391 
392         /**
393          * Parse a value of this type from a String.
394          *
395          * @param value string representation of a value of this type
396          * @return parsed value of the type represented by this NavType
397          * @throws IllegalArgumentException if value cannot be parsed into this type
398          */
399         @Suppress("RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
400         public override fun parseValue(value: String): D {
401             return type.enumConstants.firstOrNull { constant ->
402                 constant.name.equals(value, ignoreCase = true)
403             }
404                 ?: throw IllegalArgumentException(
405                     "Enum value $value not found for type ${type.name}."
406                 )
407         }
408 
409         /** Constructs a NavType that supports a given Enum type. */
410         init {
411             require(type.isEnum) { "$type is not an Enum type." }
412             this.type = type
413         }
414     }
415 
416     /**
417      * SerializableArrayType is used for [NavArgument]s that hold arrays of Serializables. This type
418      * also supports arrays of Enums.
419      *
420      * Null values are supported. Default values in Navigation XML files are not supported.
421      *
422      * @param type the Serializable component class of the array
423      */
424     public class SerializableArrayType<D : Serializable>(type: Class<D>) :
425         NavType<Array<D>?>(true) {
426         private val arrayType: Class<Array<D>>
427 
428         public override val name: String
429             get() = arrayType.name
430 
431         public override fun put(bundle: SavedState, key: String, value: Array<D>?) {
432             arrayType.cast(value)
433             bundle.putSerializable(key, value)
434         }
435 
436         @Suppress("UNCHECKED_CAST", "DEPRECATION")
437         public override fun get(bundle: SavedState, key: String): Array<D>? {
438             return bundle[key] as Array<D>?
439         }
440 
441         /** @throws UnsupportedOperationException since Arrays do not support default values */
442         public override fun parseValue(value: String): Array<D> {
443             throw UnsupportedOperationException("Arrays don't support default values.")
444         }
445 
446         public override fun equals(other: Any?): Boolean {
447             if (this === other) return true
448             if (other == null || javaClass != other.javaClass) return false
449             val that = other as SerializableArrayType<*>
450             return arrayType == that.arrayType
451         }
452 
453         public override fun hashCode(): Int {
454             return arrayType.hashCode()
455         }
456 
457         override fun valueEquals(
458             @Suppress("ArrayReturn") value: Array<D>?,
459             @Suppress("ArrayReturn") other: Array<D>?
460         ): Boolean = value.contentDeepEquals(other)
461 
462         /** Constructs a NavType that supports arrays of a given Serializable type. */
463         init {
464             require(Serializable::class.java.isAssignableFrom(type)) {
465                 "$type does not implement Serializable."
466             }
467             val arrayType: Class<Array<D>> =
468                 try {
469                     @Suppress("UNCHECKED_CAST")
470                     Class.forName("[L${type.name};") as Class<Array<D>>
471                 } catch (e: ClassNotFoundException) {
472                     throw RuntimeException(e) // should never happen
473                 }
474             this.arrayType = arrayType
475         }
476     }
477 }
478