1 /*
<lambda>null2  * Copyright 2019 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.navigation.safe.args.generator.kotlin
18 
19 import androidx.navigation.safe.args.generator.BoolArrayType
20 import androidx.navigation.safe.args.generator.BoolType
21 import androidx.navigation.safe.args.generator.BooleanValue
22 import androidx.navigation.safe.args.generator.EnumValue
23 import androidx.navigation.safe.args.generator.FloatArrayType
24 import androidx.navigation.safe.args.generator.FloatType
25 import androidx.navigation.safe.args.generator.FloatValue
26 import androidx.navigation.safe.args.generator.IntArrayType
27 import androidx.navigation.safe.args.generator.IntType
28 import androidx.navigation.safe.args.generator.IntValue
29 import androidx.navigation.safe.args.generator.LongArrayType
30 import androidx.navigation.safe.args.generator.LongType
31 import androidx.navigation.safe.args.generator.LongValue
32 import androidx.navigation.safe.args.generator.NavType
33 import androidx.navigation.safe.args.generator.NullValue
34 import androidx.navigation.safe.args.generator.ObjectArrayType
35 import androidx.navigation.safe.args.generator.ObjectType
36 import androidx.navigation.safe.args.generator.ReferenceArrayType
37 import androidx.navigation.safe.args.generator.ReferenceType
38 import androidx.navigation.safe.args.generator.ReferenceValue
39 import androidx.navigation.safe.args.generator.StringArrayType
40 import androidx.navigation.safe.args.generator.StringType
41 import androidx.navigation.safe.args.generator.StringValue
42 import androidx.navigation.safe.args.generator.WritableValue
43 import androidx.navigation.safe.args.generator.ext.toClassNameParts
44 import androidx.navigation.safe.args.generator.models.Argument
45 import androidx.navigation.safe.args.generator.models.ResReference
46 import com.squareup.kotlinpoet.ARRAY
47 import com.squareup.kotlinpoet.BOOLEAN
48 import com.squareup.kotlinpoet.ClassName
49 import com.squareup.kotlinpoet.CodeBlock
50 import com.squareup.kotlinpoet.FLOAT
51 import com.squareup.kotlinpoet.FunSpec
52 import com.squareup.kotlinpoet.INT
53 import com.squareup.kotlinpoet.LONG
54 import com.squareup.kotlinpoet.ParameterizedTypeName
55 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
56 import com.squareup.kotlinpoet.TypeName
57 import com.squareup.kotlinpoet.asTypeName
58 import java.lang.UnsupportedOperationException
59 
60 internal val NAV_DIRECTION_CLASSNAME: ClassName = ClassName("androidx.navigation", "NavDirections")
61 internal val ACTION_ONLY_NAV_DIRECTION_CLASSNAME: ClassName =
62     ClassName("androidx.navigation", "ActionOnlyNavDirections")
63 internal val NAV_ARGS_CLASSNAME: ClassName = ClassName("androidx.navigation", "NavArgs")
64 internal val BUNDLE_CLASSNAME: ClassName = ClassName("android.os", "Bundle")
65 internal val SAVED_STATE_HANDLE_CLASSNAME: ClassName =
66     ClassName("androidx.lifecycle", "SavedStateHandle")
67 
68 internal val PARCELABLE_CLASSNAME = ClassName("android.os", "Parcelable")
69 internal val SERIALIZABLE_CLASSNAME = ClassName("java.io", "Serializable")
70 
71 internal fun NavType.addBundleGetStatement(
72     builder: FunSpec.Builder,
73     arg: Argument,
74     lValue: String,
75     bundle: String
76 ): FunSpec.Builder =
77     when (this) {
78         is ObjectType ->
79             builder.apply {
80                 beginControlFlow(
81                     "if (%T::class.java.isAssignableFrom(%T::class.java) " +
82                         "|| %T::class.java.isAssignableFrom(%T::class.java))",
83                     PARCELABLE_CLASSNAME,
84                     arg.type.typeName(),
85                     SERIALIZABLE_CLASSNAME,
86                     arg.type.typeName()
87                 )
88                 addStatement(
89                     "%L = %L.%L(%S)·as·%T",
90                     lValue,
91                     bundle,
92                     "get",
93                     arg.name,
94                     arg.type.typeName().copy(nullable = true)
95                 )
96                 nextControlFlow("else")
97                 addStatement(
98                     "throw·%T(%T::class.java.name + %S)",
99                     UnsupportedOperationException::class.asTypeName(),
100                     arg.type.typeName(),
101                     " must implement Parcelable or Serializable or must be an Enum."
102                 )
103                 endControlFlow()
104             }
105         is ObjectArrayType ->
106             builder.apply {
107                 val baseType = (arg.type.typeName() as ParameterizedTypeName).typeArguments.first()
108                 addStatement(
109                     "%L = %L.%L(%S)?.map { it as %T }?.toTypedArray()",
110                     lValue,
111                     bundle,
112                     bundleGetMethod(),
113                     arg.name,
114                     baseType
115                 )
116             }
117         else -> builder.addStatement("%L = %L.%L(%S)", lValue, bundle, bundleGetMethod(), arg.name)
118     }
119 
addBundlePutStatementnull120 internal fun NavType.addBundlePutStatement(
121     builder: FunSpec.Builder,
122     arg: Argument,
123     bundle: String,
124     argValue: String
125 ): FunSpec.Builder =
126     when (this) {
127         is ObjectType ->
128             builder.apply {
129                 beginControlFlow(
130                     "if (%T::class.java.isAssignableFrom(%T::class.java))",
131                     PARCELABLE_CLASSNAME,
132                     arg.type.typeName()
133                 )
134                 addStatement(
135                     "%L.%L(%S, %L as %T)",
136                     bundle,
137                     "putParcelable",
138                     arg.name,
139                     argValue,
140                     PARCELABLE_CLASSNAME.copy(nullable = arg.isNullable)
141                 )
142                 nextControlFlow(
143                     "else if (%T::class.java.isAssignableFrom(%T::class.java))",
144                     SERIALIZABLE_CLASSNAME,
145                     arg.type.typeName()
146                 )
147                 addStatement(
148                     "%L.%L(%S, %L as %T)",
149                     bundle,
150                     "putSerializable",
151                     arg.name,
152                     argValue,
153                     SERIALIZABLE_CLASSNAME.copy(nullable = arg.isNullable)
154                 )
155                 if (!arg.isOptional()) {
156                     nextControlFlow("else")
157                     addStatement(
158                         "throw·%T(%T::class.java.name + %S)",
159                         UnsupportedOperationException::class.asTypeName(),
160                         arg.type.typeName(),
161                         " must implement Parcelable or Serializable or must be an Enum."
162                     )
163                 }
164                 endControlFlow()
165             }
166         else -> builder.addStatement("%L.%L(%S, %L)", bundle, bundlePutMethod(), arg.name, argValue)
167     }
168 
addSavedStateGetStatementnull169 internal fun NavType.addSavedStateGetStatement(
170     builder: FunSpec.Builder,
171     arg: Argument,
172     lValue: String,
173     savedStateHandle: String
174 ): FunSpec.Builder =
175     when (this) {
176         is ObjectType ->
177             builder.apply {
178                 beginControlFlow(
179                     "if (%T::class.java.isAssignableFrom(%T::class.java) " +
180                         "|| %T::class.java.isAssignableFrom(%T::class.java))",
181                     PARCELABLE_CLASSNAME,
182                     arg.type.typeName(),
183                     SERIALIZABLE_CLASSNAME,
184                     arg.type.typeName()
185                 )
186                 addStatement(
187                     "%L = %L.get<%T>(%S)",
188                     lValue,
189                     savedStateHandle,
190                     arg.type.typeName().copy(nullable = true),
191                     arg.name
192                 )
193                 nextControlFlow("else")
194                 addStatement(
195                     "throw·%T(%T::class.java.name + %S)",
196                     UnsupportedOperationException::class.asTypeName(),
197                     arg.type.typeName(),
198                     " must implement Parcelable or Serializable or must be an Enum."
199                 )
200                 endControlFlow()
201             }
202         is ObjectArrayType ->
203             builder.apply {
204                 val baseType = (arg.type.typeName() as ParameterizedTypeName).typeArguments.first()
205                 addStatement(
206                     "%L = %L.get<Array<%T>>(%S)?.map·{ it as %T }?.toTypedArray()",
207                     lValue,
208                     savedStateHandle,
209                     PARCELABLE_CLASSNAME,
210                     arg.name,
211                     baseType
212                 )
213             }
214         else -> builder.addStatement("%L = %L[%S]", lValue, savedStateHandle, arg.name)
215     }
216 
addSavedStateSetStatementnull217 internal fun NavType.addSavedStateSetStatement(
218     builder: FunSpec.Builder,
219     arg: Argument,
220     savedStateHandle: String,
221     argValue: String
222 ): FunSpec.Builder =
223     when (this) {
224         is ObjectType ->
225             builder.apply {
226                 beginControlFlow(
227                     "if (%T::class.java.isAssignableFrom(%T::class.java))",
228                     PARCELABLE_CLASSNAME,
229                     arg.type.typeName()
230                 )
231                 addStatement(
232                     "%L.set(%S, %L as %T)",
233                     savedStateHandle,
234                     arg.name,
235                     argValue,
236                     PARCELABLE_CLASSNAME.copy(nullable = arg.isNullable)
237                 )
238                 nextControlFlow(
239                     "else if (%T::class.java.isAssignableFrom(%T::class.java))",
240                     SERIALIZABLE_CLASSNAME,
241                     arg.type.typeName()
242                 )
243                 addStatement(
244                     "%L.set(%S, %L as %T)",
245                     savedStateHandle,
246                     arg.name,
247                     argValue,
248                     SERIALIZABLE_CLASSNAME.copy(nullable = arg.isNullable)
249                 )
250                 if (!arg.isOptional()) {
251                     nextControlFlow("else")
252                     addStatement(
253                         "throw·%T(%T::class.java.name + %S)",
254                         UnsupportedOperationException::class.asTypeName(),
255                         arg.type.typeName(),
256                         " must implement Parcelable or Serializable or must be an Enum."
257                     )
258                 }
259                 endControlFlow()
260             }
261         else -> builder.addStatement("%L.set(%S, %L)", savedStateHandle, arg.name, argValue)
262     }
263 
typeNamenull264 internal fun NavType.typeName(): TypeName =
265     when (this) {
266         IntType -> INT
267         IntArrayType -> IntArray::class.asTypeName()
268         LongType -> LONG
269         LongArrayType -> LongArray::class.asTypeName()
270         FloatType -> FLOAT
271         FloatArrayType -> FloatArray::class.asTypeName()
272         StringType -> String::class.asTypeName()
273         StringArrayType -> ARRAY.parameterizedBy(String::class.asTypeName())
274         BoolType -> BOOLEAN
275         BoolArrayType -> BooleanArray::class.asTypeName()
276         ReferenceType -> INT
277         ReferenceArrayType -> IntArray::class.asTypeName()
278         is ObjectType ->
279             canonicalName.toClassNameParts().let { (packageName, simpleName, innerNames) ->
280                 ClassName(packageName, simpleName, *innerNames)
281             }
282         is ObjectArrayType ->
283             ARRAY.parameterizedBy(
284                 canonicalName.toClassNameParts().let { (packageName, simpleName, innerNames) ->
285                     ClassName(packageName, simpleName, *innerNames)
286                 }
287             )
288         else -> throw IllegalStateException("Unknown type: $this")
289     }
290 
writenull291 internal fun WritableValue.write(): CodeBlock {
292     return when (this) {
293         is ReferenceValue -> resReference.accessor()
294         is StringValue -> CodeBlock.of("%S", value)
295         is IntValue -> CodeBlock.of(value)
296         is LongValue -> CodeBlock.of(value)
297         is FloatValue -> CodeBlock.of("${value}F")
298         is BooleanValue -> CodeBlock.of(value)
299         is NullValue -> CodeBlock.of("null")
300         is EnumValue -> CodeBlock.of("%T.%N", type.typeName(), value)
301         else -> throw IllegalStateException("Unknown value: $this")
302     }
303 }
304 
accessornull305 internal fun ResReference?.accessor() =
306     this?.let { CodeBlock.of("%T.%N", ClassName(packageName, "R", resType), javaIdentifier) }
307         ?: CodeBlock.of("0")
308