1 /*
2  * 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.annotation.RestrictTo
20 import androidx.navigation.CollectionNavType
21 import androidx.navigation.NavType
22 import kotlinx.serialization.ExperimentalSerializationApi
23 import kotlinx.serialization.KSerializer
24 import kotlinx.serialization.SerializationStrategy
25 import kotlinx.serialization.descriptors.SerialDescriptor
26 import kotlinx.serialization.encoding.AbstractEncoder
27 import kotlinx.serialization.encoding.Encoder
28 import kotlinx.serialization.modules.EmptySerializersModule
29 import kotlinx.serialization.modules.SerializersModule
30 
31 /** Encodes KClass of type T into a route filled with arguments */
32 @OptIn(ExperimentalSerializationApi::class)
33 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
34 public class RouteEncoder<T : Any>(
35     private val serializer: KSerializer<T>,
36     private val typeMap: Map<String, NavType<Any?>>
37 ) : AbstractEncoder() {
38     override val serializersModule: SerializersModule = EmptySerializersModule()
39     private val map: MutableMap<String, List<String>> = mutableMapOf()
40     private var elementIndex: Int = -1
41 
42     /**
43      * Entry point to set up and start encoding [T].
44      *
45      * The default entry point is [encodeSerializableValue] but we need to override it to handle
46      * primitive and non-primitive values by converting them directly to string (instead of the
47      * default implementation which further serializes nested non-primitive values). So we delegate
48      * to the default entry by directly calling [super.encodeSerializableValue].
49      */
50     @Suppress("UNCHECKED_CAST")
encodeToArgMapnull51     public fun encodeToArgMap(value: Any): Map<String, List<String>> {
52         super.encodeSerializableValue(serializer, value as T)
53         return map.toMap()
54     }
55 
56     /**
57      * Can handle both primitives and non-primitives. This method is called in three possible
58      * scenarios:
59      * 1. nullable primitive type with non-null value
60      * 2. nullable non-primitive type with non-null value
61      * 3. non-nullable non-primitive type
62      *
63      * String literal "null" is considered non-null value.
64      */
encodeSerializableValuenull65     override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
66         internalEncodeValue(value)
67     }
68 
69     /** Essentially called for every single argument. */
encodeElementnull70     override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
71         elementIndex = index
72         return true
73     }
74 
75     /**
76      * Called for non-nullable primitives of non-null value.
77      *
78      * String literal "null" is considered non-null value.
79      */
encodeValuenull80     override fun encodeValue(value: Any) {
81         internalEncodeValue(value)
82     }
83 
84     /** Called for primitive / non-primitives of null value */
encodeNullnull85     override fun encodeNull() {
86         internalEncodeValue(null)
87     }
88 
encodeInlinenull89     override fun encodeInline(descriptor: SerialDescriptor): Encoder {
90         if (descriptor.isValueClass()) elementIndex = 0
91         return super.encodeInline(descriptor)
92     }
93 
internalEncodeValuenull94     private fun internalEncodeValue(value: Any?) {
95         val argName = serializer.descriptor.getElementName(elementIndex)
96         val navType = typeMap[argName]
97         checkNotNull(navType) {
98             "Cannot find NavType for argument $argName. Please provide NavType through typeMap."
99         }
100         val parsedValue =
101             if (navType is CollectionNavType) {
102                 navType.serializeAsValues(value)
103             } else {
104                 listOf(navType.serializeAsValue(value))
105             }
106         map[argName] = parsedValue
107     }
108 }
109