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