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