1 /*
2  * 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.BOOLEAN
19 import com.squareup.kotlinpoet.NameAllocator
20 import com.squareup.kotlinpoet.PropertySpec
21 
22 /** Generates functions to encode and decode a property as JSON. */
23 @InternalMoshiCodegenApi
24 public class PropertyGenerator(
25   public val target: TargetProperty,
26   public val delegateKey: DelegateKey,
27   public val isTransient: Boolean = false
28 ) {
29   public val name: String = target.name
30   public val jsonName: String = target.jsonName ?: target.name
31   public val hasDefault: Boolean = target.hasDefault
32 
33   public lateinit var localName: String
34   public lateinit var localIsPresentName: String
35 
36   public val isRequired: Boolean get() = !delegateKey.nullable && !hasDefault
37 
38   public val hasConstructorParameter: Boolean get() = target.parameterIndex != -1
39 
40   /**
41    * IsPresent is required if the following conditions are met:
42    * - Is not transient
43    * - Has a default
44    * - Is not a constructor parameter (for constructors we use a defaults mask)
45    * - Is nullable (because we differentiate absent from null)
46    *
47    * This is used to indicate that presence should be checked first before possible assigning null
48    * to an absent value
49    */
50   public val hasLocalIsPresentName: Boolean = !isTransient && hasDefault && !hasConstructorParameter && delegateKey.nullable
51   public val hasConstructorDefault: Boolean = hasDefault && hasConstructorParameter
52 
allocateNamesnull53   internal fun allocateNames(nameAllocator: NameAllocator) {
54     localName = nameAllocator.newName(name)
55     localIsPresentName = nameAllocator.newName("${name}Set")
56   }
57 
generateLocalPropertynull58   internal fun generateLocalProperty(): PropertySpec {
59     return PropertySpec.builder(localName, target.type.copy(nullable = true))
60       .mutable(true)
61       .apply {
62         if (hasConstructorDefault) {
63           // We default to the primitive default type, as reflectively invoking the constructor
64           // without this (even though it's a throwaway) will fail argument type resolution in
65           // the reflective invocation.
66           initializer(target.type.defaultPrimitiveValue())
67         } else {
68           initializer("null")
69         }
70       }
71       .build()
72   }
73 
generateLocalIsPresentPropertynull74   internal fun generateLocalIsPresentProperty(): PropertySpec {
75     return PropertySpec.builder(localIsPresentName, BOOLEAN)
76       .mutable(true)
77       .initializer("false")
78       .build()
79   }
80 }
81