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.savedstate.serialization
18 
19 import androidx.savedstate.SavedState
20 import androidx.savedstate.read
21 import kotlin.jvm.JvmOverloads
22 import kotlinx.serialization.DeserializationStrategy
23 import kotlinx.serialization.ExperimentalSerializationApi
24 import kotlinx.serialization.SerializationException
25 import kotlinx.serialization.descriptors.SerialDescriptor
26 import kotlinx.serialization.descriptors.StructureKind
27 import kotlinx.serialization.encoding.AbstractDecoder
28 import kotlinx.serialization.encoding.CompositeDecoder
29 import kotlinx.serialization.modules.SerializersModule
30 import kotlinx.serialization.serializer
31 
32 /**
33  * Decode a serializable object from a [SavedState] with the default deserializer.
34  *
35  * @sample androidx.savedstate.decode
36  * @param savedState The [SavedState] to decode from.
37  * @param configuration The [SavedStateConfiguration] to use. Defaults to
38  *   [SavedStateConfiguration.DEFAULT].
39  * @return The decoded object.
40  * @throws SerializationException in case of any decoding-specific error.
41  * @throws IllegalArgumentException if the decoded input is not a valid instance of [T].
42  */
decodeFromSavedStatenull43 public inline fun <reified T : Any> decodeFromSavedState(
44     savedState: SavedState,
45     configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT
46 ): T = decodeFromSavedState(configuration.serializersModule.serializer(), savedState, configuration)
47 
48 /**
49  * Decodes and deserializes the given [SavedState] to the value of type [T] using the given
50  * [deserializer].
51  *
52  * @sample androidx.savedstate.decodeWithExplicitSerializerAndConfig
53  * @param deserializer The deserializer to use.
54  * @param savedState The [SavedState] to decode from.
55  * @param configuration The [SavedStateConfiguration] to use. Defaults to
56  *   [SavedStateConfiguration.DEFAULT].
57  * @return The deserialized object.
58  * @throws SerializationException in case of any decoding-specific error.
59  * @throws IllegalArgumentException if the decoded input is not a valid instance of [T].
60  */
61 @JvmOverloads
62 public fun <T : Any> decodeFromSavedState(
63     deserializer: DeserializationStrategy<T>,
64     savedState: SavedState,
65     configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT,
66 ): T {
67     return SavedStateDecoder(savedState, configuration).decodeSerializableValue(deserializer)
68 }
69 
70 /**
71  * A [kotlinx.serialization.encoding.Decoder] that can decode a serializable object from a
72  * [SavedState]. The instance should not be reused after decoding.
73  *
74  * @property savedState The [SavedState] to decode from.
75  */
76 @OptIn(ExperimentalSerializationApi::class)
77 internal class SavedStateDecoder(
78     internal val savedState: SavedState,
79     private val configuration: SavedStateConfiguration
80 ) : AbstractDecoder() {
81     internal var key: String = ""
82         private set
83 
84     private var index = 0
85 
86     override val serializersModule: SerializersModule = configuration.serializersModule
87 
decodeElementIndexnull88     override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
89         val size =
90             if (descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP) {
91                 // Use the number of elements encoded for collections.
92                 savedState.read { size() }
93             } else {
94                 // We may skip elements when encoding so if we used `size()`
95                 // here we may miss some fields.
96                 descriptor.elementsCount
97             }
98         fun hasDefaultValueDefined(index: Int) = descriptor.isElementOptional(index)
99         fun presentInEncoding(index: Int) =
100             savedState.read {
101                 val key = descriptor.getElementName(index)
102                 contains(key)
103             }
104         // Skip elements omitted from encoding (those assigned with its default values).
105         while (index < size && hasDefaultValueDefined(index) && !presentInEncoding(index)) {
106             index++
107         }
108         if (index < size) {
109             key = descriptor.getElementName(index)
110             return index++
111         } else {
112             return CompositeDecoder.DECODE_DONE
113         }
114     }
115 
<lambda>null116     override fun decodeBoolean(): Boolean = savedState.read { getBoolean(key) }
117 
<lambda>null118     override fun decodeByte(): Byte = savedState.read { getInt(key).toByte() }
119 
<lambda>null120     override fun decodeShort(): Short = savedState.read { getInt(key).toShort() }
121 
<lambda>null122     override fun decodeInt(): Int = savedState.read { getInt(key) }
123 
<lambda>null124     override fun decodeLong(): Long = savedState.read { getLong(key) }
125 
decodeFloatnull126     override fun decodeFloat(): Float = savedState.read { getFloat(key) }
127 
<lambda>null128     override fun decodeDouble(): Double = savedState.read { getDouble(key) }
129 
<lambda>null130     override fun decodeChar(): Char = savedState.read { getChar(key) }
131 
<lambda>null132     override fun decodeString(): String = savedState.read { getString(key) }
133 
<lambda>null134     override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = savedState.read { getInt(key) }
135 
decodeIntListnull136     private fun decodeIntList(): List<Int> {
137         return savedState.read { getIntList(key) }
138     }
139 
decodeStringListnull140     private fun decodeStringList(): List<String> {
141         return savedState.read { getStringList(key) }
142     }
143 
decodeBooleanArraynull144     private fun decodeBooleanArray(): BooleanArray {
145         return savedState.read { getBooleanArray(key) }
146     }
147 
decodeCharArraynull148     private fun decodeCharArray(): CharArray {
149         return savedState.read { getCharArray(key) }
150     }
151 
decodeDoubleArraynull152     private fun decodeDoubleArray(): DoubleArray {
153         return savedState.read { getDoubleArray(key) }
154     }
155 
decodeFloatArraynull156     private fun decodeFloatArray(): FloatArray {
157         return savedState.read { getFloatArray(key) }
158     }
159 
decodeIntArraynull160     private fun decodeIntArray(): IntArray {
161         return savedState.read { getIntArray(key) }
162     }
163 
decodeLongArraynull164     private fun decodeLongArray(): LongArray {
165         return savedState.read { getLongArray(key) }
166     }
167 
decodeStringArraynull168     private fun decodeStringArray(): Array<String> {
169         return savedState.read { getStringArray(key) }
170     }
171 
beginStructurenull172     override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
173         if (key == "") {
174             this
175         } else {
176             SavedStateDecoder(
<lambda>null177                 savedState = savedState.read { getSavedState(key) },
178                 configuration = configuration
179             )
180         }
181 
182     // We don't encode NotNullMark so this will actually read either a `null` from
183     // `encodeNull()` or a value from other encode functions.
<lambda>null184     override fun decodeNotNullMark(): Boolean = savedState.read { !isNull(key) }
185 
decodeSerializableValuenull186     override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
187         return decodeFormatSpecificTypes(deserializer)
188             ?: super.decodeSerializableValue(deserializer)
189     }
190 
191     /** @return `T` if `T` has a special representation in `SavedState`, `null` otherwise. */
192     @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
decodeFormatSpecificTypesnull193     private fun <T> decodeFormatSpecificTypes(deserializer: DeserializationStrategy<T>): T? {
194         return decodeFormatSpecificTypesOnPlatform(deserializer)
195             ?: when (deserializer.descriptor) {
196                 intListDescriptor -> decodeIntList()
197                 stringListDescriptor -> decodeStringList()
198                 booleanArrayDescriptor -> decodeBooleanArray()
199                 charArrayDescriptor -> decodeCharArray()
200                 doubleArrayDescriptor -> decodeDoubleArray()
201                 floatArrayDescriptor -> decodeFloatArray()
202                 intArrayDescriptor -> decodeIntArray()
203                 longArrayDescriptor -> decodeLongArray()
204                 stringArrayDescriptor -> decodeStringArray()
205                 else -> null
206             }
207                 as T?
208     }
209 }
210 
211 /** @return `T` if `T` has an internal representation in `SavedState`, `null` otherwise. */
decodeFormatSpecificTypesOnPlatformnull212 internal expect fun <T> SavedStateDecoder.decodeFormatSpecificTypesOnPlatform(
213     strategy: DeserializationStrategy<T>
214 ): T?
215