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