xref: /aosp_15_r20/external/moshi/moshi-kotlin/src/main/java/com/squareup/moshi/kotlin/reflect/KotlinJsonAdapter.kt (revision 238ab3e782f339ab327592a602fa7df0a3f729ad)
1 /*
<lambda>null2  * Copyright (C) 2017 Square, Inc.
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  *    https://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 com.squareup.moshi.kotlin.reflect
17 
18 import com.squareup.moshi.Json
19 import com.squareup.moshi.JsonAdapter
20 import com.squareup.moshi.JsonDataException
21 import com.squareup.moshi.JsonReader
22 import com.squareup.moshi.JsonWriter
23 import com.squareup.moshi.Moshi
24 import com.squareup.moshi.Types
25 import com.squareup.moshi.internal.Util
26 import com.squareup.moshi.internal.Util.generatedAdapter
27 import com.squareup.moshi.internal.Util.resolve
28 import com.squareup.moshi.rawType
29 import java.lang.reflect.Modifier
30 import java.lang.reflect.Type
31 import kotlin.reflect.KClass
32 import kotlin.reflect.KFunction
33 import kotlin.reflect.KMutableProperty1
34 import kotlin.reflect.KParameter
35 import kotlin.reflect.KProperty1
36 import kotlin.reflect.KTypeParameter
37 import kotlin.reflect.full.findAnnotation
38 import kotlin.reflect.full.memberProperties
39 import kotlin.reflect.full.primaryConstructor
40 import kotlin.reflect.jvm.isAccessible
41 import kotlin.reflect.jvm.javaField
42 import kotlin.reflect.jvm.javaType
43 
44 /** Classes annotated with this are eligible for this adapter. */
45 private val KOTLIN_METADATA = Metadata::class.java
46 
47 /**
48  * Placeholder value used when a field is absent from the JSON. Note that this code
49  * distinguishes between absent values and present-but-null values.
50  */
51 private val ABSENT_VALUE = Any()
52 
53 /**
54  * This class encodes Kotlin classes using their properties. It decodes them by first invoking the
55  * constructor, and then by setting any additional properties that exist, if any.
56  */
57 internal class KotlinJsonAdapter<T>(
58   val constructor: KFunction<T>,
59   val allBindings: List<Binding<T, Any?>?>,
60   val nonIgnoredBindings: List<Binding<T, Any?>>,
61   val options: JsonReader.Options
62 ) : JsonAdapter<T>() {
63 
64   override fun fromJson(reader: JsonReader): T {
65     val constructorSize = constructor.parameters.size
66 
67     // Read each value into its slot in the array.
68     val values = Array<Any?>(allBindings.size) { ABSENT_VALUE }
69     reader.beginObject()
70     while (reader.hasNext()) {
71       val index = reader.selectName(options)
72       if (index == -1) {
73         reader.skipName()
74         reader.skipValue()
75         continue
76       }
77       val binding = nonIgnoredBindings[index]
78 
79       val propertyIndex = binding.propertyIndex
80       if (values[propertyIndex] !== ABSENT_VALUE) {
81         throw JsonDataException(
82           "Multiple values for '${binding.property.name}' at ${reader.path}"
83         )
84       }
85 
86       values[propertyIndex] = binding.adapter.fromJson(reader)
87 
88       if (values[propertyIndex] == null && !binding.property.returnType.isMarkedNullable) {
89         throw Util.unexpectedNull(
90           binding.property.name,
91           binding.jsonName,
92           reader
93         )
94       }
95     }
96     reader.endObject()
97 
98     // Confirm all parameters are present, optional, or nullable.
99     var isFullInitialized = allBindings.size == constructorSize
100     for (i in 0 until constructorSize) {
101       if (values[i] === ABSENT_VALUE) {
102         when {
103           constructor.parameters[i].isOptional -> isFullInitialized = false
104           constructor.parameters[i].type.isMarkedNullable -> values[i] = null // Replace absent with null.
105           else -> throw Util.missingProperty(
106             constructor.parameters[i].name,
107             allBindings[i]?.jsonName,
108             reader
109           )
110         }
111       }
112     }
113 
114     // Call the constructor using a Map so that absent optionals get defaults.
115     val result = if (isFullInitialized) {
116       constructor.call(*values)
117     } else {
118       constructor.callBy(IndexedParameterMap(constructor.parameters, values))
119     }
120 
121     // Set remaining properties.
122     for (i in constructorSize until allBindings.size) {
123       val binding = allBindings[i]!!
124       val value = values[i]
125       binding.set(result, value)
126     }
127 
128     return result
129   }
130 
131   override fun toJson(writer: JsonWriter, value: T?) {
132     if (value == null) throw NullPointerException("value == null")
133 
134     writer.beginObject()
135     for (binding in allBindings) {
136       if (binding == null) continue // Skip constructor parameters that aren't properties.
137 
138       writer.name(binding.jsonName)
139       binding.adapter.toJson(writer, binding.get(value))
140     }
141     writer.endObject()
142   }
143 
144   override fun toString() = "KotlinJsonAdapter(${constructor.returnType})"
145 
146   data class Binding<K, P>(
147     val jsonName: String,
148     val adapter: JsonAdapter<P>,
149     val property: KProperty1<K, P>,
150     val parameter: KParameter?,
151     val propertyIndex: Int
152   ) {
153     fun get(value: K) = property.get(value)
154 
155     fun set(result: K, value: P) {
156       if (value !== ABSENT_VALUE) {
157         (property as KMutableProperty1<K, P>).set(result, value)
158       }
159     }
160   }
161 
162   /** A simple [Map] that uses parameter indexes instead of sorting or hashing. */
163   class IndexedParameterMap(
164     private val parameterKeys: List<KParameter>,
165     private val parameterValues: Array<Any?>
166   ) : AbstractMutableMap<KParameter, Any?>() {
167 
168     override fun put(key: KParameter, value: Any?): Any? = null
169 
170     override val entries: MutableSet<MutableMap.MutableEntry<KParameter, Any?>>
171       get() {
172         val allPossibleEntries = parameterKeys.mapIndexed { index, value ->
173           SimpleEntry<KParameter, Any?>(value, parameterValues[index])
174         }
175         return allPossibleEntries.filterTo(mutableSetOf()) {
176           it.value !== ABSENT_VALUE
177         }
178       }
179 
180     override fun containsKey(key: KParameter) = parameterValues[key.index] !== ABSENT_VALUE
181 
182     override fun get(key: KParameter): Any? {
183       val value = parameterValues[key.index]
184       return if (value !== ABSENT_VALUE) value else null
185     }
186   }
187 }
188 
189 public class KotlinJsonAdapterFactory : JsonAdapter.Factory {
createnull190   override fun create(type: Type, annotations: MutableSet<out Annotation>, moshi: Moshi):
191     JsonAdapter<*>? {
192     if (annotations.isNotEmpty()) return null
193 
194     val rawType = type.rawType
195     if (rawType.isInterface) return null
196     if (rawType.isEnum) return null
197     if (!rawType.isAnnotationPresent(KOTLIN_METADATA)) return null
198     if (Util.isPlatformType(rawType)) return null
199     try {
200       val generatedAdapter = generatedAdapter(moshi, type, rawType)
201       if (generatedAdapter != null) {
202         return generatedAdapter
203       }
204     } catch (e: RuntimeException) {
205       if (e.cause !is ClassNotFoundException) {
206         throw e
207       }
208       // Fall back to a reflective adapter when the generated adapter is not found.
209     }
210 
211     require(!rawType.isLocalClass) {
212       "Cannot serialize local class or object expression ${rawType.name}"
213     }
214     val rawTypeKotlin = rawType.kotlin
215     require(!rawTypeKotlin.isAbstract) {
216       "Cannot serialize abstract class ${rawType.name}"
217     }
218     require(!rawTypeKotlin.isInner) {
219       "Cannot serialize inner class ${rawType.name}"
220     }
221     require(rawTypeKotlin.objectInstance == null) {
222       "Cannot serialize object declaration ${rawType.name}"
223     }
224     require(!rawTypeKotlin.isSealed) {
225       "Cannot reflectively serialize sealed class ${rawType.name}. Please register an adapter."
226     }
227 
228     val constructor = rawTypeKotlin.primaryConstructor ?: return null
229     val parametersByName = constructor.parameters.associateBy { it.name }
230     constructor.isAccessible = true
231 
232     val bindingsByName = LinkedHashMap<String, KotlinJsonAdapter.Binding<Any, Any?>>()
233 
234     for (property in rawTypeKotlin.memberProperties) {
235       val parameter = parametersByName[property.name]
236 
237       property.isAccessible = true
238       var jsonAnnotation = property.findAnnotation<Json>()
239       val allAnnotations = property.annotations.toMutableList()
240 
241       if (parameter != null) {
242         allAnnotations += parameter.annotations
243         if (jsonAnnotation == null) {
244           jsonAnnotation = parameter.findAnnotation()
245         }
246       }
247 
248       if (Modifier.isTransient(property.javaField?.modifiers ?: 0)) {
249         require(parameter == null || parameter.isOptional) {
250           "No default value for transient constructor $parameter"
251         }
252         continue
253       } else if (jsonAnnotation?.ignore == true) {
254         require(parameter == null || parameter.isOptional) {
255           "No default value for ignored constructor $parameter"
256         }
257         continue
258       }
259 
260       require(parameter == null || parameter.type == property.returnType) {
261         "'${property.name}' has a constructor parameter of type ${parameter!!.type} but a property of type ${property.returnType}."
262       }
263 
264       if (property !is KMutableProperty1 && parameter == null) continue
265 
266       val jsonName = jsonAnnotation?.name?.takeUnless { it == Json.UNSET_NAME } ?: property.name
267       val propertyType = when (val propertyTypeClassifier = property.returnType.classifier) {
268         is KClass<*> -> {
269           if (propertyTypeClassifier.isValue) {
270             // When it's a value class, we need to resolve the type ourselves because the javaType
271             // function will return its inlined type
272             val rawClassifierType = propertyTypeClassifier.java
273             if (property.returnType.arguments.isEmpty()) {
274               rawClassifierType
275             } else {
276               Types.newParameterizedType(
277                 rawClassifierType,
278                 *property.returnType.arguments.mapNotNull { it.type?.javaType }.toTypedArray()
279               )
280             }
281           } else {
282             // This is safe when it's not a value class!
283             property.returnType.javaType
284           }
285         }
286         is KTypeParameter -> {
287           property.returnType.javaType
288         }
289         else -> error("Not possible!")
290       }
291       val resolvedPropertyType = resolve(type, rawType, propertyType)
292       val adapter = moshi.adapter<Any>(
293         resolvedPropertyType,
294         Util.jsonAnnotations(allAnnotations.toTypedArray()),
295         property.name
296       )
297 
298       @Suppress("UNCHECKED_CAST")
299       bindingsByName[property.name] = KotlinJsonAdapter.Binding(
300         jsonName,
301         adapter,
302         property as KProperty1<Any, Any?>,
303         parameter,
304         parameter?.index ?: -1
305       )
306     }
307 
308     val bindings = ArrayList<KotlinJsonAdapter.Binding<Any, Any?>?>()
309 
310     for (parameter in constructor.parameters) {
311       val binding = bindingsByName.remove(parameter.name)
312       require(binding != null || parameter.isOptional) {
313         "No property for required constructor $parameter"
314       }
315       bindings += binding
316     }
317 
318     var index = bindings.size
319     for (bindingByName in bindingsByName) {
320       bindings += bindingByName.value.copy(propertyIndex = index++)
321     }
322 
323     val nonIgnoredBindings = bindings.filterNotNull()
324     val options = JsonReader.Options.of(*nonIgnoredBindings.map { it.jsonName }.toTypedArray())
325     return KotlinJsonAdapter(constructor, bindings, nonIgnoredBindings, options).nullSafe()
326   }
327 }
328