1 /*
2  * 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.compiler.AppFunctionCompiler
20 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies
21 import androidx.appfunctions.compiler.core.AnnotatedAppFunctions
22 import androidx.appfunctions.compiler.core.AppFunctionComponentRegistryGenerator
23 import androidx.appfunctions.compiler.core.AppFunctionComponentRegistryGenerator.AppFunctionComponent
24 import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
25 import androidx.appfunctions.compiler.core.IntrospectionHelper
26 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionComponentRegistryAnnotation
27 import androidx.appfunctions.compiler.core.ProcessingException
28 import androidx.appfunctions.metadata.AppFunctionAllOfTypeMetadata
29 import androidx.appfunctions.metadata.AppFunctionArrayTypeMetadata
30 import androidx.appfunctions.metadata.AppFunctionComponentsMetadata
31 import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata
32 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata
33 import androidx.appfunctions.metadata.AppFunctionParameterMetadata
34 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata
35 import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata
36 import androidx.appfunctions.metadata.AppFunctionResponseMetadata
37 import androidx.appfunctions.metadata.AppFunctionSchemaMetadata
38 import androidx.appfunctions.metadata.CompileTimeAppFunctionMetadata
39 import com.google.devtools.ksp.KspExperimental
40 import com.google.devtools.ksp.processing.CodeGenerator
41 import com.google.devtools.ksp.processing.Dependencies
42 import com.google.devtools.ksp.processing.Resolver
43 import com.google.devtools.ksp.processing.SymbolProcessor
44 import com.google.devtools.ksp.symbol.KSAnnotated
45 import com.squareup.kotlinpoet.CodeBlock
46 import com.squareup.kotlinpoet.FileSpec
47 import com.squareup.kotlinpoet.KModifier
48 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
49 import com.squareup.kotlinpoet.PropertySpec
50 import com.squareup.kotlinpoet.TypeSpec
51 import com.squareup.kotlinpoet.asClassName
52 import com.squareup.kotlinpoet.buildCodeBlock
53 
54 /**
55  * Generates implementations for the AppFunctionInventory interface.
56  *
57  * It resolves all functions in a class annotated with `@AppFunction`, and generates the
58  * corresponding metadata for those functions.
59  *
60  * **Important:** [androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor] will
61  * process exactly once for each compilation unit to generate a single registry for looking up all
62  * generated inventories within the compilation unit.
63  */
64 class AppFunctionInventoryProcessor(
65     private val codeGenerator: CodeGenerator,
66 ) : SymbolProcessor {
67 
68     private var hasProcessed = false
69 
70     @OptIn(KspExperimental::class)
processnull71     override fun process(resolver: Resolver): List<KSAnnotated> {
72         if (hasProcessed) return emptyList()
73         hasProcessed = true
74 
75         val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver)
76         val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions()
77         val resolvedAnnotatedSerializableProxies =
78             ResolvedAnnotatedSerializableProxies(
79                 appFunctionSymbolResolver.resolveAllAnnotatedSerializableProxiesFromModule()
80             )
81         val generatedInventoryComponents =
82             buildList<AppFunctionComponent> {
83                 for (appFunctionClass in appFunctionClasses) {
84                     val inventoryQualifiedName =
85                         generateAppFunctionInventoryClass(
86                             appFunctionClass,
87                             resolvedAnnotatedSerializableProxies
88                         )
89                     add(
90                         AppFunctionComponent(
91                             qualifiedName = inventoryQualifiedName,
92                             sourceFiles = appFunctionClass.getSourceFiles(),
93                         )
94                     )
95                 }
96             }
97 
98         AppFunctionComponentRegistryGenerator(codeGenerator)
99             .generateRegistry(
100                 resolver.getModuleName().asString(),
101                 AppFunctionComponentRegistryAnnotation.Category.INVENTORY,
102                 generatedInventoryComponents,
103             )
104         return resolvedAnnotatedSerializableProxies.resolvedAnnotatedSerializableProxies.map {
105             it.appFunctionSerializableProxyClass
106         }
107     }
108 
109     /**
110      * Generates an implementation of AppFunctionInventory for [appFunctionClass].
111      *
112      * @return fully qualified name of the generated inventory implementation class.
113      */
generateAppFunctionInventoryClassnull114     private fun generateAppFunctionInventoryClass(
115         appFunctionClass: AnnotatedAppFunctions,
116         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
117     ): String {
118         val originalPackageName = appFunctionClass.classDeclaration.packageName.asString()
119         val originalClassName = appFunctionClass.classDeclaration.simpleName.asString()
120 
121         val inventoryClassName = getAppFunctionInventoryClassName(originalClassName)
122         val inventoryClassBuilder = TypeSpec.classBuilder(inventoryClassName)
123         inventoryClassBuilder.addSuperinterface(IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS)
124         inventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
125         inventoryClassBuilder.addKdoc(buildSourceFilesKdoc(appFunctionClass))
126         addFunctionMetadataProperties(
127             inventoryClassBuilder,
128             appFunctionClass,
129             resolvedAnnotatedSerializableProxies
130         )
131 
132         val fileSpec =
133             FileSpec.builder(originalPackageName, inventoryClassName)
134                 .addType(inventoryClassBuilder.build())
135                 .build()
136         codeGenerator
137             .createNewFile(
138                 Dependencies(aggregating = true, *appFunctionClass.getSourceFiles().toTypedArray()),
139                 originalPackageName,
140                 inventoryClassName
141             )
142             .bufferedWriter()
143             .use { fileSpec.writeTo(it) }
144 
145         return "${originalPackageName}.$inventoryClassName"
146     }
147 
148     /**
149      * Adds properties to the `AppFunctionInventory` class for each function in the class.
150      *
151      * @param inventoryClassBuilder The builder for the `AppFunctionInventory` class.
152      * @param appFunctionClass The class annotated with `@AppFunction`.
153      * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies.
154      */
addFunctionMetadataPropertiesnull155     private fun addFunctionMetadataProperties(
156         inventoryClassBuilder: TypeSpec.Builder,
157         appFunctionClass: AnnotatedAppFunctions,
158         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
159     ) {
160         val appFunctionMetadataList =
161             appFunctionClass.createAppFunctionMetadataList(resolvedAnnotatedSerializableProxies)
162 
163         for (functionMetadata in appFunctionMetadataList) {
164             val functionMetadataObjectClassBuilder =
165                 TypeSpec.objectBuilder(getFunctionMetadataObjectClassName(functionMetadata.id))
166                     .addModifiers(KModifier.PRIVATE)
167             addSchemaMetadataPropertyForFunction(
168                 functionMetadataObjectClassBuilder,
169                 functionMetadata.schema
170             )
171             addPropertiesForParameterMetadataList(
172                 functionMetadataObjectClassBuilder,
173                 functionMetadata.parameters
174             )
175             addPropertyForResponseMetadata(
176                 functionMetadataObjectClassBuilder,
177                 functionMetadata.response
178             )
179             addPropertyForComponentsMetadata(
180                 functionMetadataObjectClassBuilder,
181                 functionMetadata.components
182             )
183             addPropertyForAppFunctionMetadata(functionMetadataObjectClassBuilder, functionMetadata)
184             inventoryClassBuilder.addType(functionMetadataObjectClassBuilder.build())
185         }
186         addFunctionIdToMetadataMapProperty(inventoryClassBuilder, appFunctionMetadataList)
187     }
188 
addPropertyForAppFunctionMetadatanull189     private fun addPropertyForAppFunctionMetadata(
190         functionMetadataObjectClassBuilder: TypeSpec.Builder,
191         functionMetadata: CompileTimeAppFunctionMetadata
192     ) {
193         functionMetadataObjectClassBuilder.addProperty(
194             PropertySpec.builder(
195                     APP_FUNCTION_METADATA_PROPERTY_NAME,
196                     IntrospectionHelper.APP_FUNCTION_METADATA_CLASS
197                 )
198                 .addModifiers(KModifier.PUBLIC)
199                 .initializer(
200                     buildCodeBlock {
201                         addStatement(
202                             """
203                             %T(
204                                 id = %S,
205                                 isEnabledByDefault = %L,
206                                 schema =  %L,
207                                 parameters = %L,
208                                 response = %L,
209                                 components = %L
210                             )
211                             """
212                                 .trimIndent(),
213                             IntrospectionHelper.APP_FUNCTION_METADATA_CLASS,
214                             functionMetadata.id,
215                             functionMetadata.isEnabledByDefault,
216                             SCHEMA_METADATA_PROPERTY_NAME,
217                             PARAMETER_METADATA_LIST_PROPERTY_NAME,
218                             RESPONSE_METADATA_PROPERTY_NAME,
219                             COMPONENT_METADATA_PROPERTY_NAME
220                         )
221                     }
222                 )
223                 .build()
224         )
225     }
226 
addPropertyForComponentsMetadatanull227     private fun addPropertyForComponentsMetadata(
228         functionMetadataObjectClassBuilder: TypeSpec.Builder,
229         appFunctionComponentsMetadata: AppFunctionComponentsMetadata
230     ) {
231         val componentDataTypesPropertyName = COMPONENT_METADATA_PROPERTY_NAME + "_DATA_TYPES_MAP"
232         addPropertyForComponentsDataTypes(
233             componentDataTypesPropertyName,
234             functionMetadataObjectClassBuilder,
235             appFunctionComponentsMetadata.dataTypes
236         )
237         functionMetadataObjectClassBuilder.addProperty(
238             PropertySpec.builder(
239                     COMPONENT_METADATA_PROPERTY_NAME,
240                     IntrospectionHelper.APP_FUNCTION_COMPONENTS_METADATA_CLASS
241                 )
242                 .addModifiers(KModifier.PRIVATE)
243                 .initializer(
244                     buildCodeBlock {
245                         addStatement(
246                             """
247                             %T(
248                                 dataTypes = %L
249                             )
250                             """
251                                 .trimIndent(),
252                             IntrospectionHelper.APP_FUNCTION_COMPONENTS_METADATA_CLASS,
253                             componentDataTypesPropertyName
254                         )
255                     }
256                 )
257                 .build()
258         )
259     }
260 
addPropertyForComponentsDataTypesnull261     private fun addPropertyForComponentsDataTypes(
262         propertyName: String,
263         functionMetadataObjectClassBuilder: TypeSpec.Builder,
264         dataTypes: Map<String, AppFunctionDataTypeMetadata>
265     ) {
266         functionMetadataObjectClassBuilder.addProperty(
267             PropertySpec.builder(
268                     propertyName,
269                     Map::class.asClassName()
270                         .parameterizedBy(
271                             String::class.asClassName(),
272                             IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA
273                         ),
274                 )
275                 .addModifiers(KModifier.PRIVATE)
276                 .initializer(
277                     buildCodeBlock {
278                         addStatement("mapOf(")
279                         indent()
280                         for ((componentReferenceKey, componentReferenceTypeMetadata) in dataTypes) {
281                             val datatypeVariableName =
282                                 when (componentReferenceTypeMetadata) {
283                                     is AppFunctionObjectTypeMetadata -> {
284                                         val objectTypeMetadataPropertyName =
285                                             getObjectTypeMetadataPropertyNameForComponent(
286                                                 componentReferenceKey
287                                             )
288                                         addPropertyForObjectTypeMetadata(
289                                             objectTypeMetadataPropertyName,
290                                             functionMetadataObjectClassBuilder,
291                                             componentReferenceTypeMetadata
292                                         )
293                                         objectTypeMetadataPropertyName
294                                     }
295                                     is AppFunctionAllOfTypeMetadata -> {
296                                         val allOfTypeMetadataPropertyName =
297                                             getAllOfTypeMetadataPropertyNameForComponent(
298                                                 componentReferenceKey
299                                             )
300                                         addPropertyForAllOfTypeMetadata(
301                                             allOfTypeMetadataPropertyName,
302                                             functionMetadataObjectClassBuilder,
303                                             componentReferenceTypeMetadata
304                                         )
305                                         allOfTypeMetadataPropertyName
306                                     }
307                                     else -> {
308                                         // TODO provide KSNode to improve error message
309                                         throw ProcessingException(
310                                             "Component types contains unsupported datatype: " +
311                                                 componentReferenceTypeMetadata,
312                                             null
313                                         )
314                                     }
315                                 }
316                             addStatement(
317                                 """
318                                 %S to %L,
319                                 """
320                                     .trimIndent(),
321                                 componentReferenceKey,
322                                 datatypeVariableName
323                             )
324                         }
325                         addStatement(")")
326                         unindent()
327                     }
328                 )
329                 .build()
330         )
331     }
332 
addPropertyForResponseMetadatanull333     private fun addPropertyForResponseMetadata(
334         functionMetadataObjectClassBuilder: TypeSpec.Builder,
335         appFunctionResponseMetadata: AppFunctionResponseMetadata
336     ) {
337         val responseMetadataValueTypeName =
338             when (val castDataType = appFunctionResponseMetadata.valueType) {
339                 is AppFunctionPrimitiveTypeMetadata -> {
340                     val primitiveReturnTypeMetadataPropertyName = "PRIMITIVE_RESPONSE_VALUE_TYPE"
341                     addPropertyForPrimitiveTypeMetadata(
342                         primitiveReturnTypeMetadataPropertyName,
343                         functionMetadataObjectClassBuilder,
344                         castDataType
345                     )
346                     primitiveReturnTypeMetadataPropertyName
347                 }
348                 is AppFunctionArrayTypeMetadata -> {
349                     val arrayReturnTypeMetadataPropertyName = "ARRAY_RESPONSE_VALUE_TYPE"
350                     addPropertyForArrayTypeMetadata(
351                         arrayReturnTypeMetadataPropertyName,
352                         functionMetadataObjectClassBuilder,
353                         castDataType
354                     )
355                     arrayReturnTypeMetadataPropertyName
356                 }
357                 is AppFunctionObjectTypeMetadata -> {
358                     val objectReturnTypeMetadataPropertyName = "OBJECT_RESPONSE_VALUE_TYPE"
359                     addPropertyForObjectTypeMetadata(
360                         objectReturnTypeMetadataPropertyName,
361                         functionMetadataObjectClassBuilder,
362                         castDataType
363                     )
364                     objectReturnTypeMetadataPropertyName
365                 }
366                 is AppFunctionReferenceTypeMetadata -> {
367                     val referenceReturnTypeMetadataPropertyName = "REFERENCE_RESPONSE_VALUE_TYPE"
368                     addPropertyForReferenceTypeMetadata(
369                         referenceReturnTypeMetadataPropertyName,
370                         functionMetadataObjectClassBuilder,
371                         castDataType
372                     )
373                     referenceReturnTypeMetadataPropertyName
374                 }
375                 else -> {
376                     // TODO provide KSNode to improve error message
377                     throw ProcessingException(
378                         "Unable to build parameter metadata for unknown datatype: $castDataType",
379                         null
380                     )
381                 }
382             }
383         functionMetadataObjectClassBuilder.addProperty(
384             PropertySpec.builder(
385                     RESPONSE_METADATA_PROPERTY_NAME,
386                     IntrospectionHelper.APP_FUNCTION_RESPONSE_METADATA_CLASS
387                 )
388                 .addModifiers(KModifier.PRIVATE)
389                 .initializer(
390                     buildCodeBlock {
391                         addStatement(
392                             """
393                             %T(
394                                 valueType = %L
395                             )
396                             """
397                                 .trimIndent(),
398                             IntrospectionHelper.APP_FUNCTION_RESPONSE_METADATA_CLASS,
399                             responseMetadataValueTypeName
400                         )
401                     }
402                 )
403                 .build()
404         )
405     }
406 
addPropertiesForParameterMetadataListnull407     private fun addPropertiesForParameterMetadataList(
408         functionMetadataObjectClassBuilder: TypeSpec.Builder,
409         parameterMetadataList: List<AppFunctionParameterMetadata>
410     ) {
411         functionMetadataObjectClassBuilder.addProperty(
412             PropertySpec.builder(
413                     PARAMETER_METADATA_LIST_PROPERTY_NAME,
414                     List::class.asClassName()
415                         .parameterizedBy(IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS)
416                 )
417                 .addModifiers(KModifier.PRIVATE)
418                 .initializer(
419                     buildCodeBlock {
420                         addStatement("listOf(")
421                         indent()
422                         for (parameterMetadata in parameterMetadataList) {
423                             addPropertiesForParameterMetadata(
424                                 parameterMetadata,
425                                 functionMetadataObjectClassBuilder
426                             )
427                             addStatement(
428                                 "%L,",
429                                 "${parameterMetadata.name.uppercase()}_PARAMETER_METADATA"
430                             )
431                         }
432                         unindent()
433                         addStatement(")")
434                     }
435                 )
436                 .build()
437         )
438     }
439 
addPropertiesForParameterMetadatanull440     private fun addPropertiesForParameterMetadata(
441         parameterMetadata: AppFunctionParameterMetadata,
442         functionMetadataObjectClassBuilder: TypeSpec.Builder,
443     ) {
444         val parameterMetadataPropertyName =
445             "${parameterMetadata.name.uppercase()}_PARAMETER_METADATA"
446         val datatypeVariableName =
447             when (val castDataType = parameterMetadata.dataType) {
448                 is AppFunctionPrimitiveTypeMetadata -> {
449                     val primitiveTypeMetadataPropertyName =
450                         getPrimitiveTypeMetadataPropertyNameForParameter(parameterMetadata)
451                     addPropertyForPrimitiveTypeMetadata(
452                         primitiveTypeMetadataPropertyName,
453                         functionMetadataObjectClassBuilder,
454                         castDataType
455                     )
456                     primitiveTypeMetadataPropertyName
457                 }
458                 is AppFunctionArrayTypeMetadata -> {
459                     val arrayTypeMetadataPropertyName =
460                         getArrayTypeMetadataPropertyNameForParameter(parameterMetadata)
461                     addPropertyForArrayTypeMetadata(
462                         arrayTypeMetadataPropertyName,
463                         functionMetadataObjectClassBuilder,
464                         castDataType
465                     )
466                     arrayTypeMetadataPropertyName
467                 }
468                 is AppFunctionObjectTypeMetadata -> {
469                     val objectTypeMetadataPropertyName =
470                         getObjectTypeMetadataPropertyNameForParameter(parameterMetadata)
471                     addPropertyForObjectTypeMetadata(
472                         objectTypeMetadataPropertyName,
473                         functionMetadataObjectClassBuilder,
474                         castDataType
475                     )
476                     objectTypeMetadataPropertyName
477                 }
478                 is AppFunctionReferenceTypeMetadata -> {
479                     val referenceTypeMetadataPropertyName =
480                         getReferenceTypeMetadataPropertyNameForParameter(parameterMetadata)
481                     addPropertyForReferenceTypeMetadata(
482                         referenceTypeMetadataPropertyName,
483                         functionMetadataObjectClassBuilder,
484                         castDataType
485                     )
486                     referenceTypeMetadataPropertyName
487                 }
488                 else -> {
489                     // TODO provide KSNode to improve error message
490                     throw ProcessingException(
491                         "Unable to build parameter metadata for unknown datatype: $castDataType",
492                         null
493                     )
494                 }
495             }
496         functionMetadataObjectClassBuilder.addProperty(
497             PropertySpec.builder(
498                     parameterMetadataPropertyName,
499                     IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS
500                 )
501                 .addModifiers(KModifier.PRIVATE)
502                 .initializer(
503                     buildCodeBlock {
504                         addStatement(
505                             """
506                             %T(
507                                 name = %S,
508                                 isRequired = %L,
509                                 dataType = %L
510                             )
511                             """
512                                 .trimIndent(),
513                             IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS,
514                             parameterMetadata.name,
515                             parameterMetadata.isRequired,
516                             datatypeVariableName
517                         )
518                     }
519                 )
520                 .build()
521         )
522     }
523 
addPropertyForPrimitiveTypeMetadatanull524     private fun addPropertyForPrimitiveTypeMetadata(
525         propertyName: String,
526         functionMetadataObjectClassBuilder: TypeSpec.Builder,
527         primitiveTypeMetadata: AppFunctionPrimitiveTypeMetadata
528     ) {
529         functionMetadataObjectClassBuilder.addProperty(
530             PropertySpec.builder(
531                     propertyName,
532                     IntrospectionHelper.APP_FUNCTION_PRIMITIVE_TYPE_METADATA_CLASS
533                 )
534                 .addModifiers(KModifier.PRIVATE)
535                 .initializer(
536                     buildCodeBlock {
537                         addStatement(
538                             """
539                             %T(
540                                 type = %L,
541                                 isNullable = %L
542                             )
543                             """
544                                 .trimIndent(),
545                             IntrospectionHelper.APP_FUNCTION_PRIMITIVE_TYPE_METADATA_CLASS,
546                             primitiveTypeMetadata.type,
547                             primitiveTypeMetadata.isNullable
548                         )
549                     }
550                 )
551                 .build()
552         )
553     }
554 
addPropertyForArrayTypeMetadatanull555     private fun addPropertyForArrayTypeMetadata(
556         propertyName: String,
557         functionMetadataObjectClassBuilder: TypeSpec.Builder,
558         arrayTypeMetadata: AppFunctionArrayTypeMetadata
559     ) {
560         val itemTypeVariableName =
561             when (val castItemType = arrayTypeMetadata.itemType) {
562                 is AppFunctionPrimitiveTypeMetadata -> {
563                     val primitiveItemTypeVariableName = propertyName + "_PRIMITIVE_ITEM_TYPE"
564                     addPropertyForPrimitiveTypeMetadata(
565                         primitiveItemTypeVariableName,
566                         functionMetadataObjectClassBuilder,
567                         castItemType
568                     )
569                     primitiveItemTypeVariableName
570                 }
571                 is AppFunctionObjectTypeMetadata -> {
572                     val objectItemTypeVariableName = propertyName + "_OBJECT_ITEM_TYPE"
573                     addPropertyForObjectTypeMetadata(
574                         objectItemTypeVariableName,
575                         functionMetadataObjectClassBuilder,
576                         castItemType
577                     )
578                     objectItemTypeVariableName
579                 }
580                 is AppFunctionReferenceTypeMetadata -> {
581                     val referenceItemTypeVariableName = propertyName + "_REFERENCE_ITEM_TYPE"
582                     addPropertyForReferenceTypeMetadata(
583                         referenceItemTypeVariableName,
584                         functionMetadataObjectClassBuilder,
585                         castItemType
586                     )
587                     referenceItemTypeVariableName
588                 }
589                 else -> {
590                     // TODO provide KSNode to improve error message
591                     throw ProcessingException(
592                         "Unable to build parameter item type metadata for unknown itemType: " +
593                             "$castItemType",
594                         null
595                     )
596                 }
597             }
598         functionMetadataObjectClassBuilder.addProperty(
599             PropertySpec.builder(
600                     propertyName,
601                     IntrospectionHelper.APP_FUNCTION_ARRAY_TYPE_METADATA_CLASS
602                 )
603                 .addModifiers(KModifier.PRIVATE)
604                 .initializer(
605                     buildCodeBlock {
606                         addStatement(
607                             """
608                             %T(
609                                 itemType = %L,
610                                 isNullable = %L
611                             )
612                             """
613                                 .trimIndent(),
614                             IntrospectionHelper.APP_FUNCTION_ARRAY_TYPE_METADATA_CLASS,
615                             itemTypeVariableName,
616                             arrayTypeMetadata.isNullable
617                         )
618                     }
619                 )
620                 .build()
621         )
622     }
623 
addPropertyForReferenceTypeMetadatanull624     private fun addPropertyForReferenceTypeMetadata(
625         propertyName: String,
626         functionMetadataObjectClassBuilder: TypeSpec.Builder,
627         referenceTypeMetadata: AppFunctionReferenceTypeMetadata,
628     ) {
629         functionMetadataObjectClassBuilder.addProperty(
630             PropertySpec.builder(
631                     propertyName,
632                     IntrospectionHelper.APP_FUNCTION_REFERENCE_TYPE_METADATA_CLASS
633                 )
634                 .addModifiers(KModifier.PRIVATE)
635                 .initializer(
636                     buildCodeBlock {
637                         addStatement(
638                             """
639                             %T(
640                                 referenceDataType = %S,
641                                 isNullable = %L
642                             )
643                             """
644                                 .trimIndent(),
645                             IntrospectionHelper.APP_FUNCTION_REFERENCE_TYPE_METADATA_CLASS,
646                             referenceTypeMetadata.referenceDataType,
647                             referenceTypeMetadata.isNullable
648                         )
649                     }
650                 )
651                 .build()
652         )
653     }
654 
addPropertyForObjectTypeMetadatanull655     private fun addPropertyForObjectTypeMetadata(
656         propertyName: String,
657         functionMetadataObjectClassBuilder: TypeSpec.Builder,
658         objectTypeMetadata: AppFunctionObjectTypeMetadata,
659     ) {
660         val objectPropertiesMapPropertyName = propertyName + "_PROPERTIES_MAP"
661         addPropertyForObjectPropertiesMap(
662             objectPropertiesMapPropertyName,
663             functionMetadataObjectClassBuilder,
664             objectTypeMetadata.properties
665         )
666         val requiredPropertiesListPropertyName = propertyName + "_REQUIRED_PROPERTIES_LIST"
667         addPropertyForListOfRequiredObjectProperties(
668             requiredPropertiesListPropertyName,
669             functionMetadataObjectClassBuilder,
670             objectTypeMetadata.required
671         )
672         functionMetadataObjectClassBuilder.addProperty(
673             PropertySpec.builder(
674                     propertyName,
675                     IntrospectionHelper.APP_FUNCTION_OBJECT_TYPE_METADATA_CLASS
676                 )
677                 .addModifiers(KModifier.PRIVATE)
678                 .initializer(
679                     buildCodeBlock {
680                         addStatement(
681                             """
682                             %T(
683                                 properties = %L,
684                                 required = %L,
685                                 qualifiedName = %S,
686                                 isNullable = %L
687                             )
688                             """
689                                 .trimIndent(),
690                             IntrospectionHelper.APP_FUNCTION_OBJECT_TYPE_METADATA_CLASS,
691                             objectPropertiesMapPropertyName,
692                             requiredPropertiesListPropertyName,
693                             objectTypeMetadata.qualifiedName,
694                             objectTypeMetadata.isNullable,
695                         )
696                     }
697                 )
698                 .build()
699         )
700     }
701 
addPropertyForAllOfTypeMetadatanull702     private fun addPropertyForAllOfTypeMetadata(
703         propertyName: String,
704         functionMetadataObjectClassBuilder: TypeSpec.Builder,
705         allOfTypeMetadata: AppFunctionAllOfTypeMetadata,
706     ) {
707         val matchAllListPropertyName = propertyName + "_MATCH_ALL_LIST"
708         addPropertyForMatchAllList(
709             matchAllListPropertyName,
710             functionMetadataObjectClassBuilder,
711             allOfTypeMetadata.matchAll
712         )
713         functionMetadataObjectClassBuilder.addProperty(
714             PropertySpec.builder(
715                     propertyName,
716                     IntrospectionHelper.APP_FUNCTION_ALL_OF_TYPE_METADATA_CLASS
717                 )
718                 .addModifiers(KModifier.PRIVATE)
719                 .initializer(
720                     buildCodeBlock {
721                         addStatement(
722                             """
723                             %T(
724                                 matchAll = %L,
725                                 qualifiedName = %S,
726                                 isNullable = %L
727                             )
728                             """
729                                 .trimIndent(),
730                             IntrospectionHelper.APP_FUNCTION_ALL_OF_TYPE_METADATA_CLASS,
731                             matchAllListPropertyName,
732                             allOfTypeMetadata.qualifiedName,
733                             allOfTypeMetadata.isNullable
734                         )
735                     }
736                 )
737                 .build()
738         )
739     }
740 
addPropertyForListOfRequiredObjectPropertiesnull741     private fun addPropertyForListOfRequiredObjectProperties(
742         propertyName: String,
743         functionMetadataObjectClassBuilder: TypeSpec.Builder,
744         requiredProperties: List<String>
745     ) {
746         functionMetadataObjectClassBuilder.addProperty(
747             PropertySpec.builder(
748                     propertyName,
749                     List::class.asClassName().parameterizedBy(String::class.asClassName())
750                 )
751                 .addModifiers(KModifier.PRIVATE)
752                 .initializer(
753                     buildCodeBlock {
754                         addStatement("listOf(")
755                         indent()
756                         for (requiredProperty in requiredProperties) {
757                             addStatement("%S,", requiredProperty)
758                         }
759                         unindent()
760                         addStatement(")")
761                     }
762                 )
763                 .build()
764         )
765     }
766 
addPropertyForObjectPropertiesMapnull767     private fun addPropertyForObjectPropertiesMap(
768         propertyName: String,
769         functionMetadataObjectClassBuilder: TypeSpec.Builder,
770         propertiesMap: Map<String, AppFunctionDataTypeMetadata>,
771     ) {
772         functionMetadataObjectClassBuilder.addProperty(
773             PropertySpec.builder(
774                     propertyName,
775                     Map::class.asClassName()
776                         .parameterizedBy(
777                             String::class.asClassName(),
778                             IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA
779                         ),
780                 )
781                 .addModifiers(KModifier.PRIVATE)
782                 .initializer(
783                     buildCodeBlock {
784                         addStatement("mapOf(")
785                         indent()
786                         for ((objectPropertyName, objectPropertyTypeMetadata) in propertiesMap) {
787                             val dataTypeVariableName =
788                                 propertyName + "_${objectPropertyName.uppercase()}"
789                             when (objectPropertyTypeMetadata) {
790                                 is AppFunctionPrimitiveTypeMetadata ->
791                                     addPropertyForPrimitiveTypeMetadata(
792                                         dataTypeVariableName,
793                                         functionMetadataObjectClassBuilder,
794                                         objectPropertyTypeMetadata
795                                     )
796                                 is AppFunctionArrayTypeMetadata ->
797                                     addPropertyForArrayTypeMetadata(
798                                         dataTypeVariableName,
799                                         functionMetadataObjectClassBuilder,
800                                         objectPropertyTypeMetadata
801                                     )
802                                 is AppFunctionObjectTypeMetadata ->
803                                     addPropertyForObjectTypeMetadata(
804                                         dataTypeVariableName,
805                                         functionMetadataObjectClassBuilder,
806                                         objectPropertyTypeMetadata
807                                     )
808                                 is AppFunctionReferenceTypeMetadata ->
809                                     addPropertyForReferenceTypeMetadata(
810                                         dataTypeVariableName,
811                                         functionMetadataObjectClassBuilder,
812                                         objectPropertyTypeMetadata
813                                     )
814                                 else -> {
815                                     // TODO provide KSNode to improve error message
816                                     throw ProcessingException(
817                                         "Unable to build metadata for unknown object property " +
818                                             "datatype: $objectPropertyTypeMetadata",
819                                         null
820                                     )
821                                 }
822                             }
823                             addStatement(
824                                 """
825                                 %S to %L,
826                                 """
827                                     .trimIndent(),
828                                 objectPropertyName,
829                                 dataTypeVariableName
830                             )
831                         }
832                         unindent()
833                         addStatement(")")
834                     }
835                 )
836                 .build()
837         )
838     }
839 
addPropertyForMatchAllListnull840     private fun addPropertyForMatchAllList(
841         propertyName: String,
842         functionMetadataObjectClassBuilder: TypeSpec.Builder,
843         matchAllList: List<AppFunctionDataTypeMetadata>,
844     ) {
845         functionMetadataObjectClassBuilder.addProperty(
846             PropertySpec.builder(
847                     propertyName,
848                     List::class.asClassName()
849                         .parameterizedBy(IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA)
850                 )
851                 .addModifiers(KModifier.PRIVATE)
852                 .initializer(
853                     buildCodeBlock {
854                         addStatement("listOf(")
855                         indent()
856                         for ((index, dataTypeToMatch) in matchAllList.withIndex()) {
857                             val dataTypeToMatchPropertyName = propertyName + "_ITEM_${index}"
858                             addPropertyForDataTypeToMatch(
859                                 dataTypeToMatchPropertyName,
860                                 functionMetadataObjectClassBuilder,
861                                 dataTypeToMatch
862                             )
863                             addStatement("%L,", dataTypeToMatchPropertyName)
864                         }
865                         unindent()
866                         addStatement(")")
867                     }
868                 )
869                 .build()
870         )
871     }
872 
addPropertyForDataTypeToMatchnull873     private fun addPropertyForDataTypeToMatch(
874         propertyName: String,
875         functionMetadataObjectClassBuilder: TypeSpec.Builder,
876         dataTypeToMatch: AppFunctionDataTypeMetadata
877     ) {
878         when (dataTypeToMatch) {
879             is AppFunctionReferenceTypeMetadata ->
880                 addPropertyForReferenceTypeMetadata(
881                     propertyName,
882                     functionMetadataObjectClassBuilder,
883                     dataTypeToMatch
884                 )
885             is AppFunctionObjectTypeMetadata ->
886                 addPropertyForObjectTypeMetadata(
887                     propertyName,
888                     functionMetadataObjectClassBuilder,
889                     dataTypeToMatch
890                 )
891             else ->
892                 // TODO provide KSNode to improve error message
893                 throw ProcessingException(
894                     "Invalid datatype metadata to match in allOf type. Only object and reference " +
895                         "types are supported: $dataTypeToMatch",
896                     null
897                 )
898         }
899     }
900 
901     /** Creates the `functionIdToMetadataMap` property of the `AppFunctionInventory`. */
addFunctionIdToMetadataMapPropertynull902     private fun addFunctionIdToMetadataMapProperty(
903         inventoryClassBuilder: TypeSpec.Builder,
904         appFunctionMetadataList: List<CompileTimeAppFunctionMetadata>
905     ) {
906         inventoryClassBuilder.addProperty(
907             PropertySpec.builder(
908                     FUNCTION_ID_TO_METADATA_MAP_PROPERTY_NAME,
909                     Map::class.asClassName()
910                         .parameterizedBy(
911                             String::class.asClassName(),
912                             IntrospectionHelper.APP_FUNCTION_METADATA_CLASS
913                         ),
914                 )
915                 .addModifiers(KModifier.OVERRIDE)
916                 .initializer(
917                     buildCodeBlock {
918                         addStatement("mapOf(")
919                         indent()
920                         for (appFunctionMetadata in appFunctionMetadataList) {
921                             addStatement(
922                                 """
923                                 %S to %L.%L,
924                                 """
925                                     .trimIndent(),
926                                 appFunctionMetadata.id,
927                                 getFunctionMetadataObjectClassName(appFunctionMetadata.id),
928                                 APP_FUNCTION_METADATA_PROPERTY_NAME
929                             )
930                         }
931                         unindent()
932                         addStatement(")")
933                     }
934                 )
935                 .build()
936         )
937     }
938 
addSchemaMetadataPropertyForFunctionnull939     private fun addSchemaMetadataPropertyForFunction(
940         functionMetadataObjectClassBuilder: TypeSpec.Builder,
941         schemaMetadata: AppFunctionSchemaMetadata?
942     ) {
943         functionMetadataObjectClassBuilder.addProperty(
944             PropertySpec.builder(
945                     SCHEMA_METADATA_PROPERTY_NAME,
946                     IntrospectionHelper.APP_FUNCTION_SCHEMA_METADATA_CLASS.copy(nullable = true)
947                 )
948                 .addModifiers(KModifier.PRIVATE)
949                 .initializer(
950                     buildCodeBlock {
951                         if (schemaMetadata == null) {
952                             addStatement("%L", null)
953                         } else {
954                             addStatement(
955                                 "%T(category= %S, name=%S, version=%L)",
956                                 IntrospectionHelper.APP_FUNCTION_SCHEMA_METADATA_CLASS,
957                                 schemaMetadata.category,
958                                 schemaMetadata.name,
959                                 schemaMetadata.version
960                             )
961                         }
962                     }
963                 )
964                 .build()
965         )
966     }
967 
buildSourceFilesKdocnull968     private fun buildSourceFilesKdoc(appFunctionClass: AnnotatedAppFunctions): CodeBlock {
969         return buildCodeBlock {
970             addStatement("Source Files:")
971             for (file in appFunctionClass.getSourceFiles()) {
972                 addStatement(file.fileName)
973             }
974         }
975     }
976 
getAppFunctionInventoryClassNamenull977     private fun getAppFunctionInventoryClassName(functionClassName: String): String {
978         return "$%s_AppFunctionInventory".format(functionClassName)
979     }
980 
981     /**
982      * Generates the name of the class for the metadata object of a function.
983      *
984      * @param functionId The ID of the function.
985      * @return The name of the class.
986      */
getFunctionMetadataObjectClassNamenull987     private fun getFunctionMetadataObjectClassName(functionId: String): String {
988         return functionId.replace("[^A-Za-z0-9]".toRegex(), "_").split("_").joinToString("") {
989             it.replaceFirstChar { it.uppercase() }
990         } + "MetadataObject"
991     }
992 
993     /**
994      * Generates the name of the property for the primitive type metadata of a parameter.
995      *
996      * @param parameterMetadata The metadata of the parameter.
997      * @return The name of the property.
998      */
getPrimitiveTypeMetadataPropertyNameForParameternull999     private fun getPrimitiveTypeMetadataPropertyNameForParameter(
1000         parameterMetadata: AppFunctionParameterMetadata
1001     ): String {
1002         return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_PRIMITIVE_DATA_TYPE"
1003     }
1004 
1005     /**
1006      * Generates the name of the property for the array type metadata of a parameter.
1007      *
1008      * @param parameterMetadata The metadata of the parameter.
1009      * @return The name of the property.
1010      */
getArrayTypeMetadataPropertyNameForParameternull1011     private fun getArrayTypeMetadataPropertyNameForParameter(
1012         parameterMetadata: AppFunctionParameterMetadata
1013     ): String {
1014         return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_ARRAY_DATA_TYPE"
1015     }
1016 
1017     /**
1018      * Generates the name of the property for the object type metadata of a parameter.
1019      *
1020      * @param parameterMetadata The metadata of the parameter.
1021      * @return The name of the property.
1022      */
getObjectTypeMetadataPropertyNameForParameternull1023     private fun getObjectTypeMetadataPropertyNameForParameter(
1024         parameterMetadata: AppFunctionParameterMetadata
1025     ): String {
1026         return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_OBJECT_DATA_TYPE"
1027     }
1028 
1029     /**
1030      * Generates the name of the property for the reference type metadata of a parameter.
1031      *
1032      * @param parameterMetadata The metadata of the parameter.
1033      * @return The name of the property.
1034      */
getReferenceTypeMetadataPropertyNameForParameternull1035     private fun getReferenceTypeMetadataPropertyNameForParameter(
1036         parameterMetadata: AppFunctionParameterMetadata
1037     ): String {
1038         return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_REFERENCE_DATA_TYPE"
1039     }
1040 
1041     /**
1042      * Generates the name of the property for the object type metadata of a component.
1043      *
1044      * @param componentName The name of the component.
1045      * @return The name of the property.
1046      */
getObjectTypeMetadataPropertyNameForComponentnull1047     private fun getObjectTypeMetadataPropertyNameForComponent(componentName: String): String {
1048         return "${componentName.uppercase().replace(Regex("[.<>]"), "_").replace("?", "_NULLABLE")}_OBJECT_DATA_TYPE"
1049     }
1050 
1051     /**
1052      * Generates the name of the property for the all of type metadata of a component.
1053      *
1054      * @param componentName The name of the component.
1055      * @return The name of the property.
1056      */
getAllOfTypeMetadataPropertyNameForComponentnull1057     private fun getAllOfTypeMetadataPropertyNameForComponent(componentName: String): String {
1058         return "${componentName.uppercase().replace(".", "_")}_ALL_OF_DATA_TYPE"
1059     }
1060 
1061     companion object {
1062         const val APP_FUNCTION_METADATA_PROPERTY_NAME = "APP_FUNCTION_METADATA"
1063         const val SCHEMA_METADATA_PROPERTY_NAME = "SCHEMA_METADATA"
1064         const val PARAMETER_METADATA_LIST_PROPERTY_NAME = "PARAMETER_METADATA_LIST"
1065         const val RESPONSE_METADATA_PROPERTY_NAME = "RESPONSE_METADATA"
1066         const val COMPONENT_METADATA_PROPERTY_NAME = "COMPONENTS_METADATA"
1067         const val FUNCTION_ID_TO_METADATA_MAP_PROPERTY_NAME = "functionIdToMetadataMap"
1068     }
1069 }
1070