1 /*
<lambda>null2  * Copyright 2025 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.appfunctions.compiler.processors
18 
19 import androidx.appfunctions.AppFunctionData
20 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializable
21 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy
22 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies
23 import androidx.appfunctions.compiler.core.AnnotatedParameterizedAppFunctionSerializable
24 import androidx.appfunctions.compiler.core.AppFunctionPropertyDeclaration
25 import androidx.appfunctions.compiler.core.AppFunctionTypeReference
26 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_ARRAY
27 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_LIST
28 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_SINGULAR
29 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST
30 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_LIST
31 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR
32 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR
33 import androidx.appfunctions.compiler.core.IntrospectionHelper
34 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod
35 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod.APP_FUNCTION_DATA_PARAM_NAME
36 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.ToAppFunctionDataMethod.APP_FUNCTION_SERIALIZABLE_PARAM_NAME
37 import androidx.appfunctions.compiler.core.ProcessingException
38 import androidx.appfunctions.compiler.core.ensureQualifiedTypeName
39 import androidx.appfunctions.compiler.core.ignoreNullable
40 import androidx.appfunctions.compiler.core.isOfType
41 import androidx.appfunctions.compiler.core.toPascalCase
42 import androidx.appfunctions.compiler.core.toTypeName
43 import com.google.devtools.ksp.symbol.KSClassDeclaration
44 import com.google.devtools.ksp.symbol.KSTypeParameter
45 import com.google.devtools.ksp.symbol.KSTypeReference
46 import com.squareup.kotlinpoet.ClassName
47 import com.squareup.kotlinpoet.CodeBlock
48 import com.squareup.kotlinpoet.LIST
49 import com.squareup.kotlinpoet.buildCodeBlock
50 
51 /**
52  * Wraps methods to build the [CodeBlock]s that make up the method bodies of the generated
53  * AppFunctionSerializableFactory.
54  */
55 // TODO(b/392587953): extract common format maps
56 class AppFunctionSerializableFactoryCodeBuilder(
57     val annotatedClass: AnnotatedAppFunctionSerializable,
58     val resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
59 ) {
60     /**
61      * Generates the method body of fromAppFunctionData for a non proxy serializable.
62      *
63      * This method uses [appendFromAppFunctionDataMethodBodyCommon] to generate the common code for
64      * iterating through all the properties of a target serializable and extracting its
65      * corresponding value from an [AppFunctionData]. It then returns the serializable itself.
66      *
67      * For example, given the following non proxy serializable class:
68      * ```
69      * @AppFunctionSerializable
70      * class SampleSerializable(
71      *     val longParam: Long,
72      *     val doubleParam: Double,
73      * )
74      * ```
75      *
76      * The generated `fromAppFunctionData` method would look like:
77      * ```
78      * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : SampleSerializable {
79      *     val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam"))
80      *     val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam"))
81      *     val resultSampleSerializable = SampleSerializable(longParam, doubleParam)
82      *     return resultSampleSerializable
83      * }
84      * ```
85      */
86     fun appendFromAppFunctionDataMethodBody(): CodeBlock {
87         return buildCodeBlock {
88             val getterResultName = getResultParamName(annotatedClass)
89             add(appendFromAppFunctionDataMethodBodyCommon(getterResultName))
90             addStatement(
91                 """
92                 return %L
93                 """
94                     .trimIndent(),
95                 getterResultName
96             )
97         }
98     }
99 
100     /**
101      * Generates the method body of fromAppFunctionData for a proxy serializable.
102      *
103      * This method is similar to [appendFromAppFunctionDataMethodBody]. It uses
104      * [appendFromAppFunctionDataMethodBodyCommon] to generate the common code for iterating through
105      * all the properties of a target serializable and extracting its corresponding value from an
106      * [AppFunctionData]. However, It then returns a proxy serializable target class instead of the
107      * serializable itself.
108      *
109      * For example, given the following proxy serializable class:
110      * ```
111      * @AppFunctionSerializableProxy(targetClass = LocalDateTime::class)
112      * class SampleSerializableProxy(
113      *     val longParam: Long,
114      *     val doubleParam: Double,
115      * ) {
116      *     public fun toLocalDateTime(): LocalDateTime {
117      *         return LocalDateTime.of(...)
118      *     }
119      *
120      *     public companion object {
121      *         public fun fromLocalDateTime(localDateTIme: LocalDateTime) : SampleSerializableProxy
122      *         {
123      *             return SampleSerializableProxy(...)
124      *         }
125      *     }
126      * }
127      * ```
128      *
129      * The generated `fromAppFunctionData` method would look like:
130      * ```
131      * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : LocalDateTime {
132      *     val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam"))
133      *     val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam"))
134      *     val resultSampleSerializableProxy = SampleSerializableProxy(longParam, doubleParam)
135      *     return resultSampleSerializableProxy.toLocalDateTime()
136      * }
137      * ```
138      */
139     fun appendFromAppFunctionDataMethodBodyForProxy(): CodeBlock {
140         if (annotatedClass !is AnnotatedAppFunctionSerializableProxy) {
141             throw ProcessingException(
142                 "Attempting to generate proxy getter for non proxy serializable.",
143                 annotatedClass.attributeNode
144             )
145         }
146         return buildCodeBlock {
147             val getterResultName = getResultParamName(annotatedClass)
148             add(appendFromAppFunctionDataMethodBodyCommon(getterResultName))
149             addStatement(
150                 """
151                 return %L.%L()
152                 """
153                     .trimIndent(),
154                 getterResultName,
155                 annotatedClass.toTargetClassMethodName
156             )
157         }
158     }
159 
160     /**
161      * Generates common factory code for iterating through all the properties of a target
162      * serializable and extracting its corresponding value from an [AppFunctionData].
163      *
164      * This function is used to build the `FromAppFunctionData` method of the generated
165      * AppFunctionSerializableFactory.
166      *
167      * For example, given the following serializable class:
168      * ```
169      * @AppFunctionSerializable
170      * class SampleSerializable(
171      *     val longParam: Long,
172      *     val doubleParam: Double,
173      * )
174      * ```
175      *
176      * The generated `fromAppFunctionData` method would look like:
177      * ```
178      * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : SampleSerializable {
179      *     val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam"))
180      *     val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam"))
181      *     val resultSampleSerializable = SampleSerializable(longParam, doubleParam)
182      * }
183      * ```
184      *
185      * Note that this method does not actually populate the value to be returned. It will only
186      * handle extracting the relevant properties from the provided [AppFunctionData] to construct
187      * the relevant [androidx.appfunctions.AppFunctionSerializable] data class. The caller will
188      * append the actual return statement which could return the dataclass itself or a proxy target
189      * class.
190      */
191     private fun appendFromAppFunctionDataMethodBodyCommon(getterResultName: String): CodeBlock {
192         return buildCodeBlock {
193             add(factoryInitStatements)
194             for ((paramName, paramType) in annotatedClass.getProperties()) {
195                 val declaration = paramType.resolve().declaration
196                 if (declaration is KSTypeParameter) {
197                     appendGenericGetterStatement(paramName, declaration)
198                 } else {
199                     appendGetterStatement(paramName, paramType)
200                 }
201             }
202             appendGetterResultConstructorCallStatement(
203                 annotatedClass.originalClassName,
204                 annotatedClass.getProperties(),
205                 getterResultName
206             )
207             add("\n")
208         }
209     }
210 
211     /**
212      * Generates the method body of toAppFunctionData for a non proxy serializable.
213      *
214      * This method uses [appendToAppFunctionDataMethodBodyCommon] to generate the common code for
215      * iterating through all the properties of a target serializable and extracting its single
216      * property values. It then returns an [AppFunctionData] instance with the extracted values.
217      *
218      * For example, given the following non proxy serializable class:
219      * ```
220      * @AppFunctionSerializable
221      * class SampleSerializable(
222      *     val longParam: Long,
223      *     val doubleParam: Double,
224      * )
225      * ```
226      *
227      * The generated `toAppFunctionData` method would look like:
228      * ```
229      * override fun toAppFunctionData(appFunctionSerializable: SampleSerializable) : AppFunctionData {
230      *     val sampleSerializable_appFunctionSerializable = appFunctionSerializable
231      *     val longParam = sampleSerializable_appFunctionSerializable.longParam
232      *     val doubleParam = sampleSerializable_appFunctionSerializable.doubleParam
233      *     val builder = AppFunctionData.Builder("...")
234      *     builder.setLong("longParam", longParam)
235      *     builder.setDouble("doubleParam", doubleParam)
236      *     return builder.build()
237      * }
238      * ```
239      */
240     fun appendToAppFunctionDataMethodBody(): CodeBlock {
241         return buildCodeBlock {
242             addStatement(
243                 """
244                 val %L = %L
245                 """
246                     .trimIndent(),
247                 getSerializableParamName(annotatedClass),
248                 APP_FUNCTION_SERIALIZABLE_PARAM_NAME
249             )
250             add(appendToAppFunctionDataMethodBodyCommon())
251         }
252     }
253 
254     /**
255      * Generates the method body of toAppFunctionData for a proxy serializable.
256      *
257      * This method is similar to [appendToAppFunctionDataMethodBody]. It uses
258      * [appendToAppFunctionDataMethodBodyCommon] to generate the common code for iterating through
259      * all the properties of a target serializable and extracting its single property values. It
260      * then returns an [AppFunctionData] instance with the extracted values.
261      *
262      * The key difference from [appendToAppFunctionDataMethodBody] is the `toAppFunctionData`
263      * factory method accepts the target class instead of an AppFunctionSerializable type directly.
264      * The serializable type is obtained using the mandatory factory from the
265      * [AnnotatedAppFunctionSerializableProxy].
266      *
267      * For example, given the following proxy serializable class:
268      * ```
269      * @AppFunctionSerializableProxy(targetClass = LocalDateTime::class)
270      * class SampleSerializableProxy(
271      *     val longParam: Long,
272      *     val doubleParam: Double,
273      * ) {
274      *     public fun toLocalDateTime(): LocalDateTime {
275      *         return LocalDateTime.of(...)
276      *     }
277      *
278      *     public companion object {
279      *         public fun fromLocalDateTime(localDateTIme: LocalDateTime) : SampleSerializableProxy
280      *         {
281      *             return SampleSerializableProxy(...)
282      *         }
283      *     }
284      * }
285      * ```
286      *
287      * The generated `toAppFunctionData` method would look like:
288      * ```
289      * override fun toAppFunctionData(appFunctionSerializable: LocalDateTime) : AppFunctionData {
290      *     val localDateTime_appFunctionSerializable =
291      *         SampleSerializableProxy.fromLocalDateTime(appFunctionSerializable)
292      *     val longParam = localDateTime_appFunctionSerializable.longParam
293      *     val doubleParam = localDateTime_appFunctionSerializable.doubleParam
294      *     val builder = AppFunctionData.Builder("...")
295      *     builder.setLong("longParam", longParam)
296      *     builder.setDouble("doubleParam", doubleParam)
297      *     return builder.build()
298      * }
299      * ```
300      */
301     fun appendToAppFunctionDataMethodBodyForProxy(): CodeBlock {
302         if (annotatedClass !is AnnotatedAppFunctionSerializableProxy) {
303             throw ProcessingException(
304                 "Attempting to generate proxy setter for non proxy serializable.",
305                 annotatedClass.attributeNode
306             )
307         }
308         return buildCodeBlock {
309             addStatement(
310                 """
311                 val %L = %T.%L(%L)
312                 """
313                     .trimIndent(),
314                 getSerializableParamName(annotatedClass),
315                 annotatedClass.originalClassName,
316                 annotatedClass.fromTargetClassMethodName,
317                 APP_FUNCTION_SERIALIZABLE_PARAM_NAME
318             )
319             add(appendToAppFunctionDataMethodBodyCommon())
320         }
321     }
322 
323     /**
324      * Generates common factory code for iterating through all the properties of an
325      * [androidx.appfunctions.AppFunctionSerializable] to populate an [AppFunctionData] instance.
326      *
327      * This function is used to build the `toAppFunctionData` method of the generated
328      * AppFunctionSerializableFactory.
329      *
330      * For example, given the following serializable class:
331      * ```
332      * @AppFunctionSerializable
333      * class SampleSerializable(
334      *     val longParam: Long,
335      *     val doubleParam: Double,
336      * )
337      * ```
338      *
339      * The generated `toAppFunctionData` method would look like:
340      * ```
341      * override fun toAppFunctionData(sampleSerializable: SampleSerializable) : AppFunctionData {
342      *     val longParam = sampleSerializable.longParam
343      *     val doubleParam = sampleSerializable.doubleParam
344      *     val builder = AppFunctionData.Builder("androidx.appfunctions.compiler.processors.SampleSerializable")
345      *     builder.setLong("longParam", longParam)
346      *     builder.setDouble("doubleParam", doubleParam)
347      *     return builder.build()
348      * }
349      * ```
350      *
351      * Note that this method works directly with an [androidx.appfunctions.AppFunctionSerializable]
352      * class. In a case where the factory is for a proxy, the caller is expected to add the
353      * serializable representation of the proxy to the code block before calling this method.
354      */
355     private fun appendToAppFunctionDataMethodBodyCommon(): CodeBlock {
356         return buildCodeBlock {
357             add(factoryInitStatements)
358             val qualifiedClassName = annotatedClass.qualifiedName
359             addStatement("val builder = %T(%S)", AppFunctionData.Builder::class, qualifiedClassName)
360             for (property in annotatedClass.getProperties()) {
361                 val formatStringMap =
362                     mapOf<String, Any>(
363                         "param_name" to property.name,
364                         "annotated_class_instance" to getSerializableParamName(annotatedClass)
365                     )
366                 addNamed(
367                     "val %param_name:L = %annotated_class_instance:L.%param_name:L\n",
368                     formatStringMap
369                 )
370                 val resolvedType = property.type.resolve()
371                 val declaration = resolvedType.declaration
372                 if (declaration is KSTypeParameter) {
373                     appendGenericSetterStatement(property.name, declaration)
374                 } else {
375                     if (resolvedType.isMarkedNullable) {
376                         appendNullableSetterStatement(property.name, property.type)
377                     } else {
378                         appendSetterStatement(property.name, property.type)
379                     }
380                 }
381             }
382             add("\nreturn builder.build()")
383         }
384     }
385 
386     private fun CodeBlock.Builder.appendGenericGetterStatement(
387         paramName: String,
388         paramTypeParameter: KSTypeParameter
389     ): CodeBlock.Builder {
390         val formatStringMap =
391             mapOf<String, Any>(
392                 "param_name" to paramName,
393                 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME,
394                 "type_parameter_property_name" to getTypeParameterPropertyName(paramTypeParameter),
395                 "property_item_clazz_name" to
396                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
397                         .ListTypeParameterClass
398                         .PROPERTY_ITEM_CLAZZ_NAME,
399                 "property_clazz_name" to
400                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
401                         .PrimitiveTypeParameterClass
402                         .PROPERTY_CLAZZ_NAME,
403             )
404         addNamed("val %param_name:L = when (%type_parameter_property_name:L) {\n", formatStringMap)
405         indent()
406         add(
407             "is %T<*, *> -> {\n",
408             IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
409                 .ListTypeParameterClass
410                 .CLASS_NAME
411         )
412         indent()
413         addNamed(
414             "%app_function_data_param_name:L.getGenericListField(\"%param_name:L\", %type_parameter_property_name:L.%property_item_clazz_name:L)\n",
415             formatStringMap
416         )
417         unindent()
418         add("}\n")
419         add(
420             "is %T -> {\n",
421             IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
422                 .PrimitiveTypeParameterClass
423                 .CLASS_NAME
424         )
425         indent()
426         addNamed(
427             "%app_function_data_param_name:L.getGenericField(\"%param_name:L\", %type_parameter_property_name:L.%property_clazz_name:L)\n",
428             formatStringMap
429         )
430         unindent()
431         add("}\n")
432         unindent()
433         add("}\n")
434         return this
435     }
436 
437     private fun CodeBlock.Builder.appendGetterStatement(
438         paramName: String,
439         paramType: KSTypeReference
440     ): CodeBlock.Builder {
441         val afType = AppFunctionTypeReference(paramType)
442         return when (afType.typeCategory) {
443             PRIMITIVE_SINGULAR,
444             PRIMITIVE_ARRAY,
445             PRIMITIVE_LIST -> appendPrimitiveGetterStatement(paramName, afType)
446             SERIALIZABLE_SINGULAR -> appendSerializableGetterStatement(paramName, afType)
447             SERIALIZABLE_LIST ->
448                 appendSerializableListGetterStatement(
449                     paramName,
450                     afType,
451                     afType.itemTypeReference.getTypeShortName()
452                 )
453             SERIALIZABLE_PROXY_SINGULAR -> {
454                 val targetSerializableProxy =
455                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
456                         afType
457                     )
458                 appendSerializableGetterStatement(
459                     paramName,
460                     AppFunctionTypeReference(targetSerializableProxy.serializableReferenceType)
461                 )
462             }
463             SERIALIZABLE_PROXY_LIST -> {
464                 val targetSerializableProxy =
465                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
466                         afType
467                     )
468                 appendSerializableListGetterStatement(
469                     paramName,
470                     afType,
471                     targetSerializableProxy.serializableReferenceType.getTypeShortName()
472                 )
473             }
474         }
475     }
476 
477     private fun CodeBlock.Builder.appendPrimitiveGetterStatement(
478         paramName: String,
479         afType: AppFunctionTypeReference
480     ): CodeBlock.Builder {
481         val formatStringMap =
482             mapOf<String, Any>(
483                 "param_name" to paramName,
484                 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME,
485                 "getter_name" to getAppFunctionDataGetterName(afType),
486                 "default_value_postfix" to getGetterDefaultValuePostfix(afType)
487             )
488         if (afType.isNullable) {
489             addNamed(
490                 "val %param_name:L = %app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L\n",
491                 formatStringMap
492             )
493         } else {
494             addNamed(
495                 "val %param_name:L = checkNotNull(%app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L)\n",
496                 formatStringMap
497             )
498         }
499         return this
500     }
501 
502     private fun CodeBlock.Builder.appendSerializableGetterStatement(
503         paramName: String,
504         afType: AppFunctionTypeReference
505     ): CodeBlock.Builder {
506         val annotatedSerializable = getAnnotatedSerializable(afType)
507         val formatStringMap =
508             mapOf<String, Any>(
509                 "param_name" to paramName,
510                 "param_type" to afType.selfTypeReference.toTypeName(),
511                 "factory_name" to getSerializableFactoryVariableName(annotatedSerializable),
512                 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME,
513                 "getter_name" to getAppFunctionDataGetterName(afType),
514                 "from_app_function_data_method_name" to FromAppFunctionDataMethod.METHOD_NAME,
515                 "serializable_data_val_name" to "${paramName}Data"
516             )
517 
518         if (afType.isNullable) {
519             addNamed(
520                 "val %serializable_data_val_name:L = %app_function_data_param_name:L.%getter_name:L(%param_name:S)\n",
521                 formatStringMap
522             )
523             return addNamed("var %param_name:L: %param_type:T = null\n", formatStringMap)
524                 .addNamed("if (%serializable_data_val_name:L != null) {\n", formatStringMap)
525                 .indent()
526                 .addNamed(
527                     "%param_name:L = %factory_name:L.%from_app_function_data_method_name:L(%serializable_data_val_name:L)\n",
528                     formatStringMap
529                 )
530                 .unindent()
531                 .addStatement("}")
532         } else {
533             addNamed(
534                 "val %serializable_data_val_name:L = checkNotNull(%app_function_data_param_name:L.%getter_name:L(%param_name:S))\n",
535                 formatStringMap
536             )
537             addNamed(
538                 "val %param_name:L = %factory_name:L.%from_app_function_data_method_name:L(%serializable_data_val_name:L)\n",
539                 formatStringMap
540             )
541         }
542         return this
543     }
544 
545     private fun CodeBlock.Builder.appendSerializableListGetterStatement(
546         paramName: String,
547         afType: AppFunctionTypeReference,
548         parametrizedItemTypeName: String
549     ): CodeBlock.Builder {
550         val factoryName = parametrizedItemTypeName + "Factory"
551         val factoryInstanceName = factoryName.lowerFirstChar()
552         val formatStringMap =
553             mapOf<String, Any>(
554                 "param_name" to paramName,
555                 "temp_list_name" to "${paramName}Data",
556                 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME,
557                 "factory_instance_name" to factoryInstanceName,
558                 "getter_name" to getAppFunctionDataGetterName(afType),
559                 "default_value_postfix" to getGetterDefaultValuePostfix(afType),
560                 "null_safe_op" to if (afType.isNullable) "?" else ""
561             )
562 
563         addNamed(
564                 "val %temp_list_name:L = %app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L\n",
565                 formatStringMap
566             )
567             .addNamed(
568                 "val %param_name:L = %temp_list_name:L%null_safe_op:L.map { data ->\n",
569                 formatStringMap
570             )
571             .indent()
572             .addNamed("%factory_instance_name:L.fromAppFunctionData(data)\n", formatStringMap)
573             .unindent()
574             .addStatement("}")
575         return this
576     }
577 
578     private fun CodeBlock.Builder.appendGetterResultConstructorCallStatement(
579         originalClassName: ClassName,
580         properties: List<AppFunctionPropertyDeclaration>,
581         getterResultName: String
582     ): CodeBlock.Builder {
583         val formatStringMap =
584             mapOf<String, Any>(
585                 "original_class_name" to originalClassName,
586                 "params_list" to properties.joinToString(", ") { it.name },
587                 "getter_result_name" to getterResultName
588             )
589 
590         addNamed(
591             "\nval %getter_result_name:L = %original_class_name:T(%params_list:L)",
592             formatStringMap
593         )
594         return this
595     }
596 
597     private fun CodeBlock.Builder.appendGenericSetterStatement(
598         paramName: String,
599         paramTypeParameter: KSTypeParameter
600     ): CodeBlock.Builder {
601         val formatStringMap =
602             mapOf<String, Any>(
603                 "param_name" to paramName,
604                 "type_parameter_property_name" to getTypeParameterPropertyName(paramTypeParameter),
605                 "property_item_clazz_name" to
606                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
607                         .ListTypeParameterClass
608                         .PROPERTY_ITEM_CLAZZ_NAME,
609                 "property_clazz_name" to
610                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
611                         .PrimitiveTypeParameterClass
612                         .PROPERTY_CLAZZ_NAME,
613             )
614         addNamed("when (%type_parameter_property_name:L) {\n", formatStringMap)
615         indent()
616         add(
617             "is %T<*, *> -> {\n",
618             IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
619                 .ListTypeParameterClass
620                 .CLASS_NAME
621         )
622         indent()
623         addNamed(
624             "builder.setGenericListField(\"%param_name:L\", %param_name:L as List<*>?, %type_parameter_property_name:L.%property_item_clazz_name:L)\n",
625             formatStringMap
626         )
627         unindent()
628         add("}\n")
629         add(
630             "is %T -> {\n",
631             IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
632                 .PrimitiveTypeParameterClass
633                 .CLASS_NAME
634         )
635         indent()
636         addNamed(
637             "builder.setGenericField(\"%param_name:L\", %param_name:L, %type_parameter_property_name:L.%property_clazz_name:L)\n",
638             formatStringMap
639         )
640         unindent()
641         add("}\n")
642         unindent()
643         add("}\n")
644         return this
645     }
646 
647     private fun CodeBlock.Builder.appendNullableSetterStatement(
648         paramName: String,
649         typeReference: KSTypeReference,
650     ): CodeBlock.Builder {
651         val formatStringMap =
652             mapOf<String, Any>(
653                 "param_name" to paramName,
654             )
655 
656         return addNamed("if (%param_name:L != null) {\n", formatStringMap)
657             .indent()
658             .appendSetterStatement(paramName, typeReference)
659             .unindent()
660             .addStatement("}")
661     }
662 
663     private fun CodeBlock.Builder.appendSetterStatement(
664         paramName: String,
665         typeReference: KSTypeReference,
666     ): CodeBlock.Builder {
667         val afType = AppFunctionTypeReference(typeReference)
668         return when (afType.typeCategory) {
669             PRIMITIVE_SINGULAR,
670             PRIMITIVE_ARRAY,
671             PRIMITIVE_LIST -> appendPrimitiveSetterStatement(paramName, afType)
672             SERIALIZABLE_SINGULAR -> appendSerializableSetterStatement(paramName, afType)
673             SERIALIZABLE_LIST ->
674                 appendSerializableListSetterStatement(
675                     paramName,
676                     afType,
677                     afType.itemTypeReference.getTypeShortName()
678                 )
679             SERIALIZABLE_PROXY_SINGULAR -> {
680                 val targetSerializableProxy =
681                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
682                         afType
683                     )
684                 appendSerializableSetterStatement(
685                     paramName,
686                     AppFunctionTypeReference(targetSerializableProxy.serializableReferenceType)
687                 )
688             }
689             SERIALIZABLE_PROXY_LIST -> {
690                 val targetSerializableProxy =
691                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
692                         afType
693                     )
694                 appendSerializableListSetterStatement(
695                     paramName,
696                     afType,
697                     targetSerializableProxy.serializableReferenceType.getTypeShortName()
698                 )
699             }
700         }
701     }
702 
703     private fun CodeBlock.Builder.appendPrimitiveSetterStatement(
704         paramName: String,
705         afType: AppFunctionTypeReference
706     ): CodeBlock.Builder {
707         val formatStringMap =
708             mapOf<String, Any>(
709                 "param_name" to paramName,
710                 "setter_name" to getAppFunctionDataSetterName(afType),
711             )
712         addNamed("builder.%setter_name:L(\"%param_name:L\", %param_name:L)\n", formatStringMap)
713         return this
714     }
715 
716     private fun CodeBlock.Builder.appendSerializableSetterStatement(
717         paramName: String,
718         afType: AppFunctionTypeReference
719     ): CodeBlock.Builder {
720         val annotatedSerializable = getAnnotatedSerializable(afType)
721         val formatStringMap =
722             mapOf<String, Any>(
723                 "param_name" to paramName,
724                 "factory_name" to getSerializableFactoryVariableName(annotatedSerializable),
725                 "setter_name" to getAppFunctionDataSetterName(afType),
726             )
727 
728         addNamed(
729             "builder.%setter_name:L(\"%param_name:L\", %factory_name:L.toAppFunctionData(%param_name:L))\n",
730             formatStringMap
731         )
732         return this
733     }
734 
735     private fun CodeBlock.Builder.appendSerializableListSetterStatement(
736         paramName: String,
737         afType: AppFunctionTypeReference,
738         parametrizedItemTypeName: String
739     ): CodeBlock.Builder {
740 
741         val formatStringMap =
742             mapOf<String, Any>(
743                 "param_name" to paramName,
744                 "factory_name" to "${parametrizedItemTypeName}Factory".lowerFirstChar(),
745                 "setter_name" to getAppFunctionDataSetterName(afType),
746                 "lambda_param_name" to parametrizedItemTypeName.lowerFirstChar()
747             )
748 
749         addNamed(
750                 "builder.%setter_name:L(\"%param_name:L\", " +
751                     "%param_name:L" +
752                     ".map{ %lambda_param_name:L ->\n",
753                 formatStringMap
754             )
755             .indent()
756             .addNamed("%factory_name:L.toAppFunctionData(%lambda_param_name:L)\n", formatStringMap)
757             .unindent()
758             .addStatement("})")
759         return this
760     }
761 
762     private fun getAppFunctionDataGetterName(afType: AppFunctionTypeReference): String {
763         val shortTypeName = afType.selfOrItemTypeReference.getTypeShortName()
764         return when (afType.typeCategory) {
765             PRIMITIVE_SINGULAR -> "get${shortTypeName}OrNull"
766             PRIMITIVE_ARRAY -> "get$shortTypeName"
767             SERIALIZABLE_PROXY_SINGULAR,
768             SERIALIZABLE_SINGULAR -> "getAppFunctionData"
769             SERIALIZABLE_PROXY_LIST,
770             SERIALIZABLE_LIST -> "getAppFunctionDataList"
771             PRIMITIVE_LIST -> "get${shortTypeName}List"
772         }
773     }
774 
775     // Missing list/array types default to an empty list/array; missing singular properties throw an
776     // error; all nullable properties default to null.
777     private fun getGetterDefaultValuePostfix(afType: AppFunctionTypeReference): String {
778         return when (afType.typeCategory) {
779             PRIMITIVE_SINGULAR,
780             SERIALIZABLE_PROXY_SINGULAR,
781             SERIALIZABLE_SINGULAR -> ""
782             PRIMITIVE_ARRAY ->
783                 if (afType.isNullable) {
784                     ""
785                 } else {
786                     " ?: ${afType.selfOrItemTypeReference.getTypeShortName()}(0)"
787                 }
788             PRIMITIVE_LIST,
789             SERIALIZABLE_PROXY_LIST,
790             SERIALIZABLE_LIST -> if (afType.isNullable) "" else " ?: emptyList()"
791         }
792     }
793 
794     private fun getAppFunctionDataSetterName(afType: AppFunctionTypeReference): String {
795         return when (afType.typeCategory) {
796             PRIMITIVE_SINGULAR,
797             PRIMITIVE_ARRAY -> "set${afType.selfOrItemTypeReference.getTypeShortName()}"
798             PRIMITIVE_LIST -> "set${afType.selfOrItemTypeReference.getTypeShortName()}List"
799             SERIALIZABLE_SINGULAR,
800             SERIALIZABLE_PROXY_SINGULAR -> "setAppFunctionData"
801             SERIALIZABLE_PROXY_LIST,
802             SERIALIZABLE_LIST -> "setAppFunctionDataList"
803         }
804     }
805 
806     private val factoryInitStatements = buildCodeBlock {
807         val factoryInstanceNameToAnnotatedClassMap: Map<String, AnnotatedAppFunctionSerializable> =
808             buildMap {
809                 for (serializableTypeReference in
810                     annotatedClass.getSerializablePropertyTypeReferences()) {
811                     val annotatedSerializable = getAnnotatedSerializable(serializableTypeReference)
812                     put(
813                         getSerializableFactoryVariableName(annotatedSerializable),
814                         annotatedSerializable,
815                     )
816                 }
817 
818                 for (proxyTypeReference in
819                     annotatedClass.getSerializableProxyPropertyTypeReferences()) {
820                     val targetSerializableProxy =
821                         resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
822                             proxyTypeReference
823                         )
824                     put(
825                         getSerializableFactoryVariableName(targetSerializableProxy),
826                         targetSerializableProxy
827                     )
828                 }
829             }
830         for ((paramName, annotatedSerializable) in factoryInstanceNameToAnnotatedClassMap) {
831             when (annotatedSerializable) {
832                 is AnnotatedAppFunctionSerializableProxy -> {
833                     addStatement(
834                         "val %L = %T()",
835                         paramName,
836                         ClassName(
837                             annotatedSerializable.originalClassName.packageName,
838                             "$${annotatedSerializable.targetClassDeclaration.simpleName.asString()}Factory"
839                         )
840                     )
841                 }
842                 is AnnotatedParameterizedAppFunctionSerializable -> {
843                     addParameterizedFactoryInitStatement(paramName, annotatedSerializable)
844                 }
845                 else -> {
846                     addStatement(
847                         "val %L = %T()",
848                         paramName,
849                         ClassName(
850                             annotatedSerializable.originalClassName.packageName,
851                             "$${annotatedSerializable.originalClassName.simpleName}Factory"
852                         )
853                     )
854                 }
855             }
856         }
857         add("\n")
858     }
859 
860     /**
861      * Adds an Serializable factory initialize statement for [annotatedSerializable]
862      *
863      * For example, if a serializable has a parameterized parameters `val title: SetField<String?>`,
864      * it would add a statement of
865      *
866      * ```
867      * val setFieldStringNullableFactory = `$SetFieldFactory`<String?>`(
868      *   TypeParameter.PrimitiveTypeParameter(String::class.java as Class<String?>)
869      * )
870      * ```
871      */
872     private fun CodeBlock.Builder.addParameterizedFactoryInitStatement(
873         paramName: String,
874         annotatedSerializable: AnnotatedParameterizedAppFunctionSerializable,
875     ) {
876         add(
877             "val %L = %T",
878             paramName,
879             ClassName(
880                 annotatedSerializable.originalClassName.packageName,
881                 "$${annotatedSerializable.originalClassName.simpleName}Factory"
882             )
883         )
884         add("<")
885         for ((index, typeArgumentReference) in
886             annotatedSerializable.typeParameterMap.values.withIndex()) {
887             add("%T", typeArgumentReference.toTypeName())
888             if (index != annotatedSerializable.typeParameterMap.size - 1) {
889                 add(",")
890             }
891         }
892         addStatement(">(")
893         indent()
894         for (typeArgumentReference in annotatedSerializable.typeParameterMap.values) {
895             val typeArgument = typeArgumentReference.resolve()
896             val typeParameterTypeName =
897                 if (typeArgumentReference.isOfType(LIST)) {
898                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
899                         .ListTypeParameterClass
900                         .CLASS_NAME
901                 } else {
902                     IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass
903                         .PrimitiveTypeParameterClass
904                         .CLASS_NAME
905                 }
906             val typeParameterArg =
907                 if (typeArgumentReference.isOfType(LIST)) {
908                     checkNotNull(typeArgument.arguments.first().type).toTypeName().ignoreNullable()
909                 } else {
910                     typeArgumentReference.toTypeName().ignoreNullable()
911                 }
912 
913             if (typeArgument.isMarkedNullable) {
914                 addStatement("@Suppress(\"UNCHECKED_CAST\")")
915                 addStatement(
916                     "%1T(%2T::class.java as Class<%3T>),",
917                     typeParameterTypeName,
918                     typeParameterArg,
919                     typeArgumentReference.toTypeName(),
920                 )
921             } else {
922                 addStatement(
923                     "%1T(%2T::class.java),",
924                     typeParameterTypeName,
925                     typeParameterArg,
926                 )
927             }
928         }
929         unindent()
930         addStatement(")")
931     }
932 
933     private fun KSTypeReference.getTypeShortName(): String {
934         return this.ensureQualifiedTypeName().getShortName()
935     }
936 
937     private fun String.lowerFirstChar(): String {
938         return replaceFirstChar { it -> it.lowercase() }
939     }
940 
941     private fun getResultParamName(annotatedClass: AnnotatedAppFunctionSerializable): String {
942         return "result${annotatedClass.originalClassName.simpleName}"
943     }
944 
945     private fun getSerializableParamName(annotatedClass: AnnotatedAppFunctionSerializable): String {
946         return "${annotatedClass.originalClassName.simpleName.replaceFirstChar {
947                 it -> it.lowercase() }}_appFunctionSerializable"
948     }
949 
950     private fun getAnnotatedSerializable(
951         typeReference: AppFunctionTypeReference
952     ): AnnotatedAppFunctionSerializable {
953         val serializableType = typeReference.selfOrItemTypeReference.resolve()
954         val serializableDeclaration = serializableType.declaration as KSClassDeclaration
955         return AnnotatedAppFunctionSerializable(serializableDeclaration)
956             .parameterizedBy(serializableType.arguments)
957     }
958 
959     private fun getSerializableFactoryVariableName(
960         annotatedSerializable: AnnotatedAppFunctionSerializable
961     ): String {
962         return when (annotatedSerializable) {
963             is AnnotatedParameterizedAppFunctionSerializable -> {
964                 val typeArgumentSuffix =
965                     annotatedSerializable.typeParameterMap.values.joinToString { typeArgument ->
966                         typeArgument
967                             .toTypeName()
968                             .toString()
969                             .replace(Regex("[_<>]"), "_")
970                             .replace("?", "_Nullable")
971                             .toPascalCase()
972                     }
973                 "${annotatedSerializable.originalClassName.simpleName.lowerFirstChar()}${typeArgumentSuffix}Factory"
974             }
975             else -> {
976                 "${annotatedSerializable.originalClassName.simpleName.lowerFirstChar()}Factory"
977             }
978         }
979     }
980 
981     companion object {
982         /** Gets the TypeParameter property name used by generic AppFunctionSerializableFactory */
983         fun getTypeParameterPropertyName(typeParameter: KSTypeParameter): String {
984             return "${typeParameter.name.asString().uppercase().replaceFirstChar { it.lowercase() }}TypeParameter"
985         }
986     }
987 }
988