1 /*
<lambda>null2 * Copyright (C) 2018 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.codegen.api
17
18 import com.squareup.kotlinpoet.ARRAY
19 import com.squareup.kotlinpoet.AnnotationSpec
20 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
21 import com.squareup.kotlinpoet.CodeBlock
22 import com.squareup.kotlinpoet.FileSpec
23 import com.squareup.kotlinpoet.FunSpec
24 import com.squareup.kotlinpoet.INT
25 import com.squareup.kotlinpoet.KModifier
26 import com.squareup.kotlinpoet.MemberName
27 import com.squareup.kotlinpoet.NameAllocator
28 import com.squareup.kotlinpoet.ParameterSpec
29 import com.squareup.kotlinpoet.ParameterizedTypeName
30 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
31 import com.squareup.kotlinpoet.PropertySpec
32 import com.squareup.kotlinpoet.TypeName
33 import com.squareup.kotlinpoet.TypeSpec
34 import com.squareup.kotlinpoet.TypeVariableName
35 import com.squareup.kotlinpoet.asClassName
36 import com.squareup.kotlinpoet.asTypeName
37 import com.squareup.kotlinpoet.joinToCode
38 import com.squareup.moshi.JsonAdapter
39 import com.squareup.moshi.JsonReader
40 import com.squareup.moshi.JsonWriter
41 import com.squareup.moshi.Moshi
42 import com.squareup.moshi.internal.Util
43 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterOnly
44 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.ParameterProperty
45 import com.squareup.moshi.kotlin.codegen.api.FromJsonComponent.PropertyOnly
46 import java.lang.reflect.Constructor
47 import java.lang.reflect.Type
48 import org.objectweb.asm.Type as AsmType
49
50 private val MOSHI_UTIL = Util::class.asClassName()
51 private const val TO_STRING_PREFIX = "GeneratedJsonAdapter("
52 private const val TO_STRING_SIZE_BASE = TO_STRING_PREFIX.length + 1 // 1 is the closing paren
53
54 /** Generates a JSON adapter for a target type. */
55 @InternalMoshiCodegenApi
56 public class AdapterGenerator(
57 private val target: TargetType,
58 private val propertyList: List<PropertyGenerator>
59 ) {
60
61 private companion object {
62 private val INT_TYPE_BLOCK = CodeBlock.of("%T::class.javaPrimitiveType", INT)
63 private val DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK = CodeBlock.of(
64 "%T.DEFAULT_CONSTRUCTOR_MARKER",
65 Util::class
66 )
67 private val CN_MOSHI = Moshi::class.asClassName()
68 private val CN_TYPE = Type::class.asClassName()
69
70 private val COMMON_SUPPRESS = arrayOf(
71 // https://github.com/square/moshi/issues/1023
72 "DEPRECATION",
73 // Because we look it up reflectively
74 "unused",
75 "UNUSED_PARAMETER",
76 // Because we include underscores
77 "ClassName",
78 // Because we generate redundant `out` variance for some generics and there's no way
79 // for us to know when it's redundant.
80 "REDUNDANT_PROJECTION",
81 // Because we may generate redundant explicit types for local vars with default values.
82 // Example: 'var fooSet: Boolean = false'
83 "RedundantExplicitType",
84 // NameAllocator will just add underscores to differentiate names, which Kotlin doesn't
85 // like for stylistic reasons.
86 "LocalVariableName",
87 // KotlinPoet always generates explicit public modifiers for public members.
88 "RedundantVisibilityModifier",
89 // For LambdaTypeNames we have to import kotlin.functions.* types
90 "PLATFORM_CLASS_MAPPED_TO_KOTLIN",
91 // Cover for calling fromJson() on a Nothing property type. Theoretically nonsensical but we
92 // support it
93 "IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION"
94 ).let { suppressions ->
95 AnnotationSpec.builder(Suppress::class)
96 .useSiteTarget(FILE)
97 .addMember(
98 suppressions.indices.joinToString { "%S" },
99 *suppressions
100 )
101 .build()
102 }
103 }
104
105 private val nonTransientProperties = propertyList.filterNot { it.isTransient }
106 private val className = target.typeName.rawType()
107 private val visibility = target.visibility
108 private val typeVariables = target.typeVariables
109 private val typeVariableResolver = typeVariables.toTypeVariableResolver()
110 private val targetConstructorParams = target.constructor.parameters
111 .mapKeys { (_, param) -> param.index }
112
113 private val nameAllocator = NameAllocator()
114 private val adapterName = "${className.simpleNames.joinToString(separator = "_")}JsonAdapter"
115 private val originalTypeName = target.typeName.stripTypeVarVariance(typeVariableResolver)
116 private val originalRawTypeName = originalTypeName.rawType()
117
118 private val moshiParam = ParameterSpec.builder(
119 nameAllocator.newName("moshi"),
120 CN_MOSHI
121 ).build()
122 private val typesParam = ParameterSpec.builder(
123 nameAllocator.newName("types"),
124 ARRAY.parameterizedBy(CN_TYPE)
125 )
126 .build()
127 private val readerParam = ParameterSpec.builder(
128 nameAllocator.newName("reader"),
129 JsonReader::class
130 )
131 .build()
132 private val writerParam = ParameterSpec.builder(
133 nameAllocator.newName("writer"),
134 JsonWriter::class
135 )
136 .build()
137 private val valueParam = ParameterSpec.builder(
138 nameAllocator.newName("value"),
139 originalTypeName.copy(nullable = true)
140 )
141 .build()
142 private val jsonAdapterTypeName = JsonAdapter::class.asClassName().parameterizedBy(
143 originalTypeName
144 )
145
146 // selectName() API setup
147 private val optionsProperty = PropertySpec.builder(
148 nameAllocator.newName("options"),
149 JsonReader.Options::class.asTypeName(),
150 KModifier.PRIVATE
151 )
152 .initializer(
153 "%T.of(%L)",
154 JsonReader.Options::class.asTypeName(),
155 nonTransientProperties
156 .map { CodeBlock.of("%S", it.jsonName) }
157 .joinToCode(", ")
158 )
159 .build()
160
161 private val constructorProperty = PropertySpec.builder(
162 nameAllocator.newName("constructorRef"),
163 Constructor::class.asClassName().parameterizedBy(originalTypeName).copy(nullable = true),
164 KModifier.PRIVATE
165 )
166 .addAnnotation(Volatile::class)
167 .mutable(true)
168 .initializer("null")
169 .build()
170
171 public fun prepare(generateProguardRules: Boolean, typeHook: (TypeSpec) -> TypeSpec = { it }): PreparedAdapter {
172 val reservedSimpleNames = mutableSetOf<String>()
173 for (property in nonTransientProperties) {
174 // Allocate names for simple property types first to avoid collisions
175 // See https://github.com/square/moshi/issues/1277
176 property.target.type.findRawType()?.simpleName?.let { simpleNameToReserve ->
177 if (reservedSimpleNames.add(simpleNameToReserve)) {
178 nameAllocator.newName(simpleNameToReserve)
179 }
180 }
181 property.allocateNames(nameAllocator)
182 }
183
184 val generatedAdapter = generateType().let(typeHook)
185 val result = FileSpec.builder(className.packageName, adapterName)
186 result.addFileComment("Code generated by moshi-kotlin-codegen. Do not edit.")
187 result.addAnnotation(COMMON_SUPPRESS)
188 result.addType(generatedAdapter)
189 val proguardConfig = if (generateProguardRules) {
190 generatedAdapter.createProguardRule()
191 } else {
192 null
193 }
194 return PreparedAdapter(result.build(), proguardConfig)
195 }
196
197 private fun TypeSpec.createProguardRule(): ProguardConfig {
198 val adapterConstructorParams = when (requireNotNull(primaryConstructor).parameters.size) {
199 1 -> listOf(CN_MOSHI.reflectionName())
200 2 -> listOf(CN_MOSHI.reflectionName(), "${CN_TYPE.reflectionName()}[]")
201 // Should never happen
202 else -> error("Unexpected number of arguments on primary constructor: $primaryConstructor")
203 }
204
205 var hasDefaultProperties = false
206 var parameterTypes = emptyList<String>()
207 target.constructor.signature?.let { constructorSignature ->
208 if (constructorSignature.startsWith("constructor-impl")) {
209 // Inline class, we don't support this yet.
210 // This is a static method with signature like 'constructor-impl(I)I'
211 return@let
212 }
213 hasDefaultProperties = propertyList.any { it.hasDefault }
214 parameterTypes = AsmType.getArgumentTypes(constructorSignature.removePrefix("<init>"))
215 .map { it.toReflectionString() }
216 }
217 return ProguardConfig(
218 targetClass = className,
219 adapterName = adapterName,
220 adapterConstructorParams = adapterConstructorParams,
221 targetConstructorHasDefaults = hasDefaultProperties,
222 targetConstructorParams = parameterTypes,
223 )
224 }
225
226 private fun generateType(): TypeSpec {
227 val result = TypeSpec.classBuilder(adapterName)
228
229 result.superclass(jsonAdapterTypeName)
230
231 if (typeVariables.isNotEmpty()) {
232 result.addTypeVariables(typeVariables.map { it.stripTypeVarVariance(typeVariableResolver) as TypeVariableName })
233 // require(types.size == 1) {
234 // "TypeVariable mismatch: Expecting 1 type(s) for generic type variables [T], but received ${types.size} with values $types"
235 // }
236 result.addInitializerBlock(
237 CodeBlock.builder()
238 .beginControlFlow("require(types.size == %L)", typeVariables.size)
239 .addStatement(
240 "buildString·{·append(%S).append(%L).append(%S).append(%S).append(%S).append(%L)·}",
241 "TypeVariable mismatch: Expecting ",
242 typeVariables.size,
243 " ${if (typeVariables.size == 1) "type" else "types"} for generic type variables [",
244 typeVariables.joinToString(", ") { it.name },
245 "], but received ",
246 "${typesParam.name}.size"
247 )
248 .endControlFlow()
249 .build()
250 )
251 }
252
253 // TODO make this configurable. Right now it just matches the source model
254 if (visibility == KModifier.INTERNAL) {
255 result.addModifiers(KModifier.INTERNAL)
256 }
257
258 result.primaryConstructor(generateConstructor())
259
260 val typeRenderer: TypeRenderer = object : TypeRenderer() {
261 override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock {
262 // Match only by name because equality checks for more things than just the name. For example, a base class
263 // may declare "T" but the subclass declares "T : Number", which is legal but will fail an equals() test.
264 val index = typeVariables.indexOfFirst { it.name == typeVariable.name }
265 check(index != -1) {
266 "Unexpected type variable $typeVariable"
267 }
268 return CodeBlock.of("%N[%L]", typesParam, index)
269 }
270 }
271
272 result.addProperty(optionsProperty)
273 for (uniqueAdapter in nonTransientProperties.distinctBy { it.delegateKey }) {
274 result.addProperty(
275 uniqueAdapter.delegateKey.generateProperty(
276 nameAllocator,
277 typeRenderer,
278 moshiParam,
279 uniqueAdapter.name
280 )
281 )
282 }
283
284 result.addFunction(generateToStringFun())
285 result.addFunction(generateFromJsonFun(result))
286 result.addFunction(generateToJsonFun())
287
288 return result.build()
289 }
290
291 private fun generateConstructor(): FunSpec {
292 val result = FunSpec.constructorBuilder()
293 result.addParameter(moshiParam)
294
295 if (typeVariables.isNotEmpty()) {
296 result.addParameter(typesParam)
297 }
298
299 return result.build()
300 }
301
302 private fun generateToStringFun(): FunSpec {
303 val name = originalRawTypeName.simpleNames.joinToString(".")
304 val size = TO_STRING_SIZE_BASE + name.length
305 return FunSpec.builder("toString")
306 .addModifiers(KModifier.OVERRIDE)
307 .returns(String::class)
308 .addStatement(
309 "return %M(%L)·{ append(%S).append(%S).append('%L') }",
310 MemberName("kotlin.text", "buildString"),
311 size,
312 TO_STRING_PREFIX,
313 name,
314 ")"
315 )
316 .build()
317 }
318
319 private fun generateFromJsonFun(classBuilder: TypeSpec.Builder): FunSpec {
320 val result = FunSpec.builder("fromJson")
321 .addModifiers(KModifier.OVERRIDE)
322 .addParameter(readerParam)
323 .returns(originalTypeName)
324
325 for (property in nonTransientProperties) {
326 result.addCode("%L", property.generateLocalProperty())
327 if (property.hasLocalIsPresentName) {
328 result.addCode("%L", property.generateLocalIsPresentProperty())
329 }
330 }
331
332 val propertiesByIndex = propertyList.asSequence()
333 .filter { it.hasConstructorParameter }
334 .associateBy { it.target.parameterIndex }
335 val components = mutableListOf<FromJsonComponent>()
336
337 // Add parameters (± properties) first, their index matters
338 for ((index, parameter) in targetConstructorParams) {
339 val property = propertiesByIndex[index]
340 if (property == null) {
341 components += ParameterOnly(parameter)
342 } else {
343 components += ParameterProperty(parameter, property)
344 }
345 }
346
347 // Now add the remaining properties that aren't parameters
348 for (property in propertyList) {
349 if (property.target.parameterIndex in targetConstructorParams) {
350 continue // Already handled
351 }
352 if (property.isTransient) {
353 continue // We don't care about these outside of constructor parameters
354 }
355 components += PropertyOnly(property)
356 }
357
358 // Calculate how many masks we'll need. Round up if it's not evenly divisible by 32
359 val propertyCount = targetConstructorParams.size
360 val maskCount = if (propertyCount == 0) {
361 0
362 } else {
363 (propertyCount + 31) / 32
364 }
365 // Allocate mask names
366 val maskNames = Array(maskCount) { index ->
367 nameAllocator.newName("mask$index")
368 }
369 val maskAllSetValues = Array(maskCount) { -1 }
370 val useDefaultsConstructor = components.filterIsInstance<ParameterComponent>()
371 .any { it.parameter.hasDefault }
372 if (useDefaultsConstructor) {
373 // Initialize all our masks, defaulting to fully unset (-1)
374 for (maskName in maskNames) {
375 result.addStatement("var %L = -1", maskName)
376 }
377 }
378 result.addStatement("%N.beginObject()", readerParam)
379 result.beginControlFlow("while (%N.hasNext())", readerParam)
380 result.beginControlFlow("when (%N.selectName(%N))", readerParam, optionsProperty)
381
382 // We track property index and mask index separately, because mask index is based on _all_
383 // constructor arguments, while property index is only based on the index passed into
384 // JsonReader.Options.
385 var propertyIndex = 0
386 val constructorPropertyTypes = mutableListOf<CodeBlock>()
387
388 //
389 // Track important indices for masks. Masks generally increment with each parameter (including
390 // transient).
391 //
392 // Mask name index is an index into the maskNames array we initialized above.
393 //
394 // Once the maskIndex reaches 32, we've filled up that mask and have to move to the next mask
395 // name. Reset the maskIndex relative to here and continue incrementing.
396 //
397 var maskIndex = 0
398 var maskNameIndex = 0
399 val updateMaskIndexes = {
400 maskIndex++
401 if (maskIndex == 32) {
402 // Move to the next mask
403 maskIndex = 0
404 maskNameIndex++
405 }
406 }
407
408 for (input in components) {
409 if (input is ParameterOnly ||
410 (input is ParameterProperty && input.property.isTransient)
411 ) {
412 updateMaskIndexes()
413 constructorPropertyTypes += input.type.asTypeBlock()
414 continue
415 } else if (input is PropertyOnly && input.property.isTransient) {
416 continue
417 }
418
419 // We've removed all parameter-only types by this point
420 val property = (input as PropertyComponent).property
421
422 // Proceed as usual
423 if (property.hasLocalIsPresentName || property.hasConstructorDefault) {
424 result.beginControlFlow("%L ->", propertyIndex)
425 if (property.delegateKey.nullable) {
426 result.addStatement(
427 "%N = %N.fromJson(%N)",
428 property.localName,
429 nameAllocator[property.delegateKey],
430 readerParam
431 )
432 } else {
433 val exception = unexpectedNull(property, readerParam)
434 result.addStatement(
435 "%N = %N.fromJson(%N) ?: throw·%L",
436 property.localName,
437 nameAllocator[property.delegateKey],
438 readerParam,
439 exception
440 )
441 }
442 if (property.hasConstructorDefault) {
443 val inverted = (1 shl maskIndex).inv()
444 if (input is ParameterComponent && input.parameter.hasDefault) {
445 maskAllSetValues[maskNameIndex] = maskAllSetValues[maskNameIndex] and inverted
446 }
447 result.addComment("\$mask = \$mask and (1 shl %L).inv()", maskIndex)
448 result.addStatement(
449 "%1L = %1L and 0x%2L.toInt()",
450 maskNames[maskNameIndex],
451 Integer.toHexString(inverted)
452 )
453 } else {
454 // Presence tracker for a mutable property
455 result.addStatement("%N = true", property.localIsPresentName)
456 }
457 result.endControlFlow()
458 } else {
459 if (property.delegateKey.nullable) {
460 result.addStatement(
461 "%L -> %N = %N.fromJson(%N)",
462 propertyIndex,
463 property.localName,
464 nameAllocator[property.delegateKey],
465 readerParam
466 )
467 } else {
468 val exception = unexpectedNull(property, readerParam)
469 result.addStatement(
470 "%L -> %N = %N.fromJson(%N) ?: throw·%L",
471 propertyIndex,
472 property.localName,
473 nameAllocator[property.delegateKey],
474 readerParam,
475 exception
476 )
477 }
478 }
479 if (property.hasConstructorParameter) {
480 constructorPropertyTypes += property.target.type.asTypeBlock()
481 }
482 propertyIndex++
483 updateMaskIndexes()
484 }
485
486 result.beginControlFlow("-1 ->")
487 result.addComment("Unknown name, skip it.")
488 result.addStatement("%N.skipName()", readerParam)
489 result.addStatement("%N.skipValue()", readerParam)
490 result.endControlFlow()
491
492 result.endControlFlow() // when
493 result.endControlFlow() // while
494 result.addStatement("%N.endObject()", readerParam)
495
496 var separator = "\n"
497
498 val resultName = nameAllocator.newName("result")
499 val hasNonConstructorProperties = nonTransientProperties.any { !it.hasConstructorParameter }
500 val returnOrResultAssignment = if (hasNonConstructorProperties) {
501 // Save the result var for reuse
502 result.addStatement("val %N: %T", resultName, originalTypeName)
503 CodeBlock.of("%N = ", resultName)
504 } else {
505 CodeBlock.of("return·")
506 }
507
508 // Used to indicate we're in an if-block that's assigning our result value and
509 // needs to be closed with endControlFlow
510 var closeNextControlFlowInAssignment = false
511
512 if (useDefaultsConstructor) {
513 // Happy path - all parameters with defaults are set
514 val allMasksAreSetBlock = maskNames.withIndex()
515 .map { (index, maskName) ->
516 CodeBlock.of("$maskName·== 0x${Integer.toHexString(maskAllSetValues[index])}.toInt()")
517 }
518 .joinToCode("·&& ")
519 result.beginControlFlow("if (%L)", allMasksAreSetBlock)
520 result.addComment("All parameters with defaults are set, invoke the constructor directly")
521 result.addCode("«%L·%T(", returnOrResultAssignment, originalTypeName)
522 var localSeparator = "\n"
523 val paramsToSet = components.filterIsInstance<ParameterProperty>()
524 .filterNot { it.property.isTransient }
525
526 // Set all non-transient property parameters
527 for (input in paramsToSet) {
528 result.addCode(localSeparator)
529 val property = input.property
530 result.addCode("%N = %N", property.name, property.localName)
531 if (property.isRequired) {
532 result.addMissingPropertyCheck(property, readerParam)
533 } else if (!input.type.isNullable) {
534 // Unfortunately incurs an intrinsic null-check even though we know it's set, but
535 // maybe in the future we can use contracts to omit them.
536 result.addCode("·as·%T", input.type)
537 }
538 localSeparator = ",\n"
539 }
540 result.addCode("\n»)\n")
541 result.nextControlFlow("else")
542 closeNextControlFlowInAssignment = true
543
544 classBuilder.addProperty(constructorProperty)
545 result.addComment("Reflectively invoke the synthetic defaults constructor")
546 // Dynamic default constructor call
547 val nonNullConstructorType = constructorProperty.type.copy(nullable = false)
548 val args = constructorPropertyTypes
549 .plus(0.until(maskCount).map { INT_TYPE_BLOCK }) // Masks, one every 32 params
550 .plus(DEFAULT_CONSTRUCTOR_MARKER_TYPE_BLOCK) // Default constructor marker is always last
551 .joinToCode(", ")
552 val coreLookupBlock = CodeBlock.of(
553 "%T::class.java.getDeclaredConstructor(%L)",
554 originalRawTypeName,
555 args
556 )
557 val lookupBlock = if (originalTypeName is ParameterizedTypeName) {
558 CodeBlock.of("(%L·as·%T)", coreLookupBlock, nonNullConstructorType)
559 } else {
560 coreLookupBlock
561 }
562 val initializerBlock = CodeBlock.of(
563 "this.%1N·?: %2L.also·{ this.%1N·= it }",
564 constructorProperty,
565 lookupBlock
566 )
567 val localConstructorProperty = PropertySpec.builder(
568 nameAllocator.newName("localConstructor"),
569 nonNullConstructorType
570 )
571 .addAnnotation(
572 AnnotationSpec.builder(Suppress::class)
573 .addMember("%S", "UNCHECKED_CAST")
574 .build()
575 )
576 .initializer(initializerBlock)
577 .build()
578 result.addCode("%L", localConstructorProperty)
579 result.addCode(
580 "«%L%N.newInstance(",
581 returnOrResultAssignment,
582 localConstructorProperty
583 )
584 } else {
585 // Standard constructor call. Don't omit generics for parameterized types even if they can be
586 // inferred, as calculating the right condition for inference exceeds the value gained from
587 // being less pedantic.
588 result.addCode("«%L%T(", returnOrResultAssignment, originalTypeName)
589 }
590
591 for (input in components.filterIsInstance<ParameterComponent>()) {
592 result.addCode(separator)
593 if (useDefaultsConstructor) {
594 if (input is ParameterOnly || (input is ParameterProperty && input.property.isTransient)) {
595 // We have to use the default primitive for the available type in order for
596 // invokeDefaultConstructor to properly invoke it. Just using "null" isn't safe because
597 // the transient type may be a primitive type.
598 // Inline a little comment for readability indicating which parameter is it's referring to
599 result.addCode(
600 "/*·%L·*/·%L",
601 input.parameter.name,
602 input.type.rawType().defaultPrimitiveValue()
603 )
604 } else {
605 result.addCode("%N", (input as ParameterProperty).property.localName)
606 }
607 } else if (input !is ParameterOnly) {
608 val property = (input as ParameterProperty).property
609 result.addCode("%N = %N", property.name, property.localName)
610 }
611 if (input is PropertyComponent) {
612 val property = input.property
613 if (!property.isTransient && property.isRequired) {
614 result.addMissingPropertyCheck(property, readerParam)
615 }
616 }
617 separator = ",\n"
618 }
619
620 if (useDefaultsConstructor) {
621 // Add the masks and a null instance for the trailing default marker instance
622 result.addCode(",\n%L,\n/*·DefaultConstructorMarker·*/·null", maskNames.map { CodeBlock.of("%L", it) }.joinToCode(", "))
623 }
624
625 result.addCode("\n»)\n")
626
627 // Close the result assignment control flow, if any
628 if (closeNextControlFlowInAssignment) {
629 result.endControlFlow()
630 }
631
632 // Assign properties not present in the constructor.
633 for (property in nonTransientProperties) {
634 if (property.hasConstructorParameter) {
635 continue // Property already handled.
636 }
637 if (property.hasLocalIsPresentName) {
638 result.beginControlFlow("if (%N)", property.localIsPresentName)
639 result.addStatement(
640 "%N.%N = %N",
641 resultName,
642 property.name,
643 property.localName
644 )
645 result.endControlFlow()
646 } else {
647 result.addStatement(
648 "%1N.%2N = %3N ?: %1N.%2N",
649 resultName,
650 property.name,
651 property.localName
652 )
653 }
654 }
655
656 if (hasNonConstructorProperties) {
657 result.addStatement("return·%1N", resultName)
658 }
659 return result.build()
660 }
661
662 private fun unexpectedNull(property: PropertyGenerator, reader: ParameterSpec): CodeBlock {
663 return CodeBlock.of(
664 "%T.unexpectedNull(%S, %S, %N)",
665 MOSHI_UTIL,
666 property.localName,
667 property.jsonName,
668 reader
669 )
670 }
671
672 private fun generateToJsonFun(): FunSpec {
673 val result = FunSpec.builder("toJson")
674 .addModifiers(KModifier.OVERRIDE)
675 .addParameter(writerParam)
676 .addParameter(valueParam)
677
678 result.beginControlFlow("if (%N == null)", valueParam)
679 result.addStatement(
680 "throw·%T(%S)",
681 NullPointerException::class,
682 "${valueParam.name} was null! Wrap in .nullSafe() to write nullable values."
683 )
684 result.endControlFlow()
685
686 result.addStatement("%N.beginObject()", writerParam)
687 nonTransientProperties.forEach { property ->
688 // We manually put in quotes because we know the jsonName is already escaped
689 result.addStatement("%N.name(%S)", writerParam, property.jsonName)
690 result.addStatement(
691 "%N.toJson(%N, %N.%N)",
692 nameAllocator[property.delegateKey],
693 writerParam,
694 valueParam,
695 property.name
696 )
697 }
698 result.addStatement("%N.endObject()", writerParam)
699
700 return result.build()
701 }
702 }
703
FunSpecnull704 private fun FunSpec.Builder.addMissingPropertyCheck(property: PropertyGenerator, readerParam: ParameterSpec) {
705 val missingPropertyBlock =
706 CodeBlock.of(
707 "%T.missingProperty(%S, %S, %N)",
708 MOSHI_UTIL,
709 property.localName,
710 property.jsonName,
711 readerParam
712 )
713 addCode(" ?: throw·%L", missingPropertyBlock)
714 }
715
716 /** Represents a prepared adapter with its [spec] and optional associated [proguardConfig]. */
717 @InternalMoshiCodegenApi
718 public data class PreparedAdapter(val spec: FileSpec, val proguardConfig: ProguardConfig?)
719
toReflectionStringnull720 private fun AsmType.toReflectionString(): String {
721 return when (this) {
722 AsmType.VOID_TYPE -> "void"
723 AsmType.BOOLEAN_TYPE -> "boolean"
724 AsmType.CHAR_TYPE -> "char"
725 AsmType.BYTE_TYPE -> "byte"
726 AsmType.SHORT_TYPE -> "short"
727 AsmType.INT_TYPE -> "int"
728 AsmType.FLOAT_TYPE -> "float"
729 AsmType.LONG_TYPE -> "long"
730 AsmType.DOUBLE_TYPE -> "double"
731 else -> when (sort) {
732 AsmType.ARRAY -> "${elementType.toReflectionString()}[]"
733 // Object type
734 else -> className
735 }
736 }
737 }
738
739 private interface PropertyComponent {
740 val property: PropertyGenerator
741 val type: TypeName
742 }
743
744 private interface ParameterComponent {
745 val parameter: TargetParameter
746 val type: TypeName
747 }
748
749 /**
750 * Type hierarchy for describing fromJson() components. Specifically - parameters, properties, and
751 * parameter properties. All three of these scenarios participate in fromJson() parsing.
752 */
753 private sealed class FromJsonComponent {
754
755 abstract val type: TypeName
756
757 data class ParameterOnly(
758 override val parameter: TargetParameter
759 ) : FromJsonComponent(), ParameterComponent {
760 override val type: TypeName = parameter.type
761 }
762
763 data class PropertyOnly(
764 override val property: PropertyGenerator
765 ) : FromJsonComponent(), PropertyComponent {
766 override val type: TypeName = property.target.type
767 }
768
769 data class ParameterProperty(
770 override val parameter: TargetParameter,
771 override val property: PropertyGenerator
772 ) : FromJsonComponent(), ParameterComponent, PropertyComponent {
773 override val type: TypeName = parameter.type
774 }
775 }
776