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.core
18 
19 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies
20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_ARRAY
21 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_LIST
22 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_SINGULAR
23 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST
24 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_LIST
25 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR
26 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR
27 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.SUPPORTED_TYPES_STRING
28 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.isSupportedType
29 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.toAppFunctionDatatype
30 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation
31 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionContextClass
32 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSchemaDefinitionAnnotation
33 import androidx.appfunctions.metadata.AppFunctionAllOfTypeMetadata
34 import androidx.appfunctions.metadata.AppFunctionArrayTypeMetadata
35 import androidx.appfunctions.metadata.AppFunctionComponentsMetadata
36 import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata
37 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata
38 import androidx.appfunctions.metadata.AppFunctionParameterMetadata
39 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata
40 import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata
41 import androidx.appfunctions.metadata.AppFunctionResponseMetadata
42 import androidx.appfunctions.metadata.AppFunctionSchemaMetadata
43 import androidx.appfunctions.metadata.CompileTimeAppFunctionMetadata
44 import com.google.devtools.ksp.getDeclaredProperties
45 import com.google.devtools.ksp.symbol.KSAnnotated
46 import com.google.devtools.ksp.symbol.KSClassDeclaration
47 import com.google.devtools.ksp.symbol.KSFile
48 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
49 import com.google.devtools.ksp.symbol.KSTypeReference
50 import com.google.devtools.ksp.validate
51 import com.squareup.kotlinpoet.ClassName
52 
53 /**
54  * Represents a collection of functions within a specific class that are annotated as app functions.
55  */
56 data class AnnotatedAppFunctions(
57     /** The [KSClassDeclaration] of the class that contains the annotated app functions. */
58     val classDeclaration: KSClassDeclaration,
59     /** The list of [KSFunctionDeclaration] that are annotated as app function. */
60     val appFunctionDeclarations: List<KSFunctionDeclaration>,
61 ) {
62     /** Gets all annotated nodes. */
63     fun getAllAnnotated(): List<KSAnnotated> {
64         return buildList {
65             // Only functions are annotated.
66             for (appFunctionDeclaration in appFunctionDeclarations) {
67                 add(appFunctionDeclaration)
68             }
69         }
70     }
71 
72     /**
73      * Validates if the AppFunction implementation is valid.
74      *
75      * @throws SymbolNotReadyException if any related nodes are not ready for processing yet.
76      */
77     fun validate(): AnnotatedAppFunctions {
78         if (!classDeclaration.validate()) {
79             throw SymbolNotReadyException(
80                 "AppFunction enclosing class not ready for processing yet",
81                 classDeclaration,
82             )
83         }
84         for (appFunction in appFunctionDeclarations) {
85             if (!appFunction.validate()) {
86                 throw SymbolNotReadyException(
87                     "AppFunction method not ready for processing yet",
88                     appFunction,
89                 )
90             }
91         }
92         validateFirstParameter()
93         validateParameterTypes()
94         return this
95     }
96 
97     private fun validateFirstParameter() {
98         for (appFunctionDeclaration in appFunctionDeclarations) {
99             val firstParam = appFunctionDeclaration.parameters.firstOrNull()
100             if (firstParam == null) {
101                 throw ProcessingException(
102                     "The first parameter of an app function must be " +
103                         "${AppFunctionContextClass.CLASS_NAME}",
104                     appFunctionDeclaration,
105                 )
106             }
107             if (!firstParam.type.isOfType(AppFunctionContextClass.CLASS_NAME)) {
108                 throw ProcessingException(
109                     "The first parameter of an app function must be " +
110                         "${AppFunctionContextClass.CLASS_NAME}",
111                     firstParam,
112                 )
113             }
114         }
115     }
116 
117     private fun validateParameterTypes() {
118         for (appFunctionDeclaration in appFunctionDeclarations) {
119             for ((paramIndex, ksValueParameter) in appFunctionDeclaration.parameters.withIndex()) {
120                 if (paramIndex == 0) {
121                     // Skip the first parameter which is always the `AppFunctionContext`.
122                     continue
123                 }
124 
125                 if (!isSupportedType(ksValueParameter.type)) {
126                     throw ProcessingException(
127                         "App function parameters must be a supported type, or a type " +
128                             "annotated as @AppFunctionSerializable. See list of supported types:\n" +
129                             "${
130                                 SUPPORTED_TYPES_STRING
131                             }\n" +
132                             "but found ${
133                                 AppFunctionTypeReference(ksValueParameter.type)
134                                     .selfOrItemTypeReference.ensureQualifiedTypeName()
135                                     .asString()
136                             }",
137                         ksValueParameter,
138                     )
139                 }
140             }
141         }
142     }
143 
144     /**
145      * Gets the identifier of an app functions.
146      *
147      * The format of the identifier is `packageName.className#methodName`.
148      */
149     fun getAppFunctionIdentifier(functionDeclaration: KSFunctionDeclaration): String {
150         val packageName = classDeclaration.packageName.asString()
151         val className = classDeclaration.simpleName.asString()
152         val methodName = functionDeclaration.simpleName.asString()
153         return "${packageName}.${className}#${methodName}"
154     }
155 
156     /**
157      * Returns the set of files that need to be processed to obtain the complete information about
158      * the app functions defined in this class.
159      *
160      * This includes the class file containing the function declarations, the class file containing
161      * the schema definitions, and the class files containing the AppFunctionSerializable classes
162      * used in the function parameters.
163      */
164     fun getSourceFiles(): Set<KSFile> {
165         val sourceFileSet: MutableSet<KSFile> = mutableSetOf()
166 
167         // Add the class file containing the function declarations
168         classDeclaration.containingFile?.let { sourceFileSet.add(it) }
169 
170         for (functionDeclaration in appFunctionDeclarations) {
171             // Add the class file containing the schema definitions
172             val rootAppFunctionSchemaInterface =
173                 findRootAppFunctionSchemaInterface(functionDeclaration)
174             rootAppFunctionSchemaInterface?.containingFile?.let { sourceFileSet.add(it) }
175 
176             // Traverse each functions parameter to obtain the relevant AppFunctionSerializable
177             // class files
178             for ((paramIndex, ksValueParameter) in functionDeclaration.parameters.withIndex()) {
179                 if (paramIndex == 0) {
180                     // Skip the first parameter which is always the `AppFunctionContext`.
181                     continue
182                 }
183                 val parameterTypeReference = AppFunctionTypeReference(ksValueParameter.type)
184                 if (parameterTypeReference.typeOrItemTypeIsAppFunctionSerializable()) {
185                     sourceFileSet.addAll(
186                         getAnnotatedAppFunctionSerializable(parameterTypeReference)
187                             .getTransitiveSerializableSourceFiles()
188                     )
189                 }
190             }
191 
192             val returnTypeReference =
193                 AppFunctionTypeReference(checkNotNull(functionDeclaration.returnType))
194             if (returnTypeReference.typeOrItemTypeIsAppFunctionSerializable()) {
195                 sourceFileSet.addAll(
196                     getAnnotatedAppFunctionSerializable(returnTypeReference)
197                         .getTransitiveSerializableSourceFiles()
198                 )
199             }
200         }
201         return sourceFileSet
202     }
203 
204     /** Gets the [classDeclaration]'s [ClassName]. */
205     fun getEnclosingClassName(): ClassName {
206         return ClassName(
207             classDeclaration.packageName.asString(),
208             classDeclaration.simpleName.asString(),
209         )
210     }
211 
212     /**
213      * Creates a list of [CompileTimeAppFunctionMetadata]] instances for each of the app functions
214      * defined in this class.
215      */
216     fun createAppFunctionMetadataList(
217         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
218     ): List<CompileTimeAppFunctionMetadata> {
219         return appFunctionDeclarations.map { functionDeclaration ->
220             // Defining the shared types locally for this iteration is to isolate the components
221             // used per function. This is done with the expectation that they can be globally
222             // merged without encountering mismatching datatype metadata for the same object key.
223             val sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata> = mutableMapOf()
224             val seenDataTypeQualifiers: MutableSet<String> = mutableSetOf()
225 
226             val appFunctionAnnotationProperties =
227                 computeAppFunctionAnnotationProperties(functionDeclaration)
228             val parameterTypeMetadataList =
229                 functionDeclaration.buildParameterTypeMetadataList(
230                     sharedDataTypeMap,
231                     seenDataTypeQualifiers,
232                     resolvedAnnotatedSerializableProxies
233                 )
234             val responseTypeMetadata =
235                 checkNotNull(functionDeclaration.returnType)
236                     .toAppFunctionDataTypeMetadata(
237                         sharedDataTypeMap,
238                         seenDataTypeQualifiers,
239                         resolvedAnnotatedSerializableProxies
240                     )
241 
242             CompileTimeAppFunctionMetadata(
243                 id = getAppFunctionIdentifier(functionDeclaration),
244                 isEnabledByDefault = appFunctionAnnotationProperties.isEnabledByDefault,
245                 schema = appFunctionAnnotationProperties.toAppFunctionSchemaMetadata(),
246                 parameters = parameterTypeMetadataList,
247                 response = AppFunctionResponseMetadata(valueType = responseTypeMetadata),
248                 components = AppFunctionComponentsMetadata(dataTypes = sharedDataTypeMap),
249             )
250         }
251     }
252 
253     /** Builds a list of [AppFunctionParameterMetadata] for the parameters of an app function. */
254     private fun KSFunctionDeclaration.buildParameterTypeMetadataList(
255         sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>,
256         seenDataTypeQualifiers: MutableSet<String>,
257         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
258     ): List<AppFunctionParameterMetadata> = buildList {
259         for (ksValueParameter in parameters) {
260             if (ksValueParameter.type.isOfType(AppFunctionContextClass.CLASS_NAME)) {
261                 // Skip the first parameter which is always the `AppFunctionContext`.
262                 continue
263             }
264 
265             val parameterName = checkNotNull(ksValueParameter.name).asString()
266             val dataTypeMetadata =
267                 ksValueParameter.type.toAppFunctionDataTypeMetadata(
268                     sharedDataTypeMap,
269                     seenDataTypeQualifiers,
270                     resolvedAnnotatedSerializableProxies
271                 )
272 
273             add(
274                 AppFunctionParameterMetadata(
275                     name = parameterName,
276                     // TODO(b/394553462): Parse required state from annotation.
277                     isRequired = true,
278                     dataType = dataTypeMetadata,
279                 )
280             )
281         }
282     }
283 
284     private fun KSTypeReference.toAppFunctionDataTypeMetadata(
285         sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>,
286         seenDataTypeQualifiers: MutableSet<String>,
287         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
288     ): AppFunctionDataTypeMetadata {
289         val appFunctionTypeReference = AppFunctionTypeReference(this)
290         return when (appFunctionTypeReference.typeCategory) {
291             PRIMITIVE_SINGULAR ->
292                 AppFunctionPrimitiveTypeMetadata(
293                     type = appFunctionTypeReference.toAppFunctionDataType(),
294                     isNullable = appFunctionTypeReference.isNullable,
295                 )
296             PRIMITIVE_ARRAY ->
297                 AppFunctionArrayTypeMetadata(
298                     itemType =
299                         AppFunctionPrimitiveTypeMetadata(
300                             type = appFunctionTypeReference.determineArrayItemType(),
301                             isNullable = false,
302                         ),
303                     isNullable = appFunctionTypeReference.isNullable,
304                 )
305             PRIMITIVE_LIST ->
306                 AppFunctionArrayTypeMetadata(
307                     itemType =
308                         AppFunctionPrimitiveTypeMetadata(
309                             type = appFunctionTypeReference.determineArrayItemType(),
310                             isNullable =
311                                 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference)
312                                     .isNullable,
313                         ),
314                     isNullable = appFunctionTypeReference.isNullable,
315                 )
316             SERIALIZABLE_SINGULAR -> {
317                 val annotatedAppFunctionSerializable =
318                     getAnnotatedAppFunctionSerializable(appFunctionTypeReference)
319                 addSerializableTypeMetadataToSharedDataTypeMap(
320                     annotatedAppFunctionSerializable,
321                     annotatedAppFunctionSerializable
322                         .getProperties()
323                         .associateBy { checkNotNull(it.name).toString() }
324                         .toMutableMap(),
325                     sharedDataTypeMap,
326                     seenDataTypeQualifiers,
327                     resolvedAnnotatedSerializableProxies
328                 )
329                 AppFunctionReferenceTypeMetadata(
330                     referenceDataType =
331                         appFunctionTypeReference.selfTypeReference
332                             .toTypeName()
333                             .ignoreNullable()
334                             .toString(),
335                     isNullable = appFunctionTypeReference.isNullable,
336                 )
337             }
338             SERIALIZABLE_LIST -> {
339                 val annotatedAppFunctionSerializable =
340                     getAnnotatedAppFunctionSerializable(appFunctionTypeReference)
341                 addSerializableTypeMetadataToSharedDataTypeMap(
342                     annotatedAppFunctionSerializable,
343                     annotatedAppFunctionSerializable
344                         .getProperties()
345                         .associateBy { checkNotNull(it.name).toString() }
346                         .toMutableMap(),
347                     sharedDataTypeMap,
348                     seenDataTypeQualifiers,
349                     resolvedAnnotatedSerializableProxies
350                 )
351                 AppFunctionArrayTypeMetadata(
352                     itemType =
353                         AppFunctionReferenceTypeMetadata(
354                             referenceDataType =
355                                 appFunctionTypeReference.itemTypeReference
356                                     .toTypeName()
357                                     .ignoreNullable()
358                                     .toString(),
359                             isNullable =
360                                 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference)
361                                     .isNullable,
362                         ),
363                     isNullable = appFunctionTypeReference.isNullable,
364                 )
365             }
366             SERIALIZABLE_PROXY_SINGULAR -> {
367                 val targetSerializableProxy =
368                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
369                         appFunctionTypeReference
370                     )
371                 addSerializableTypeMetadataToSharedDataTypeMap(
372                     targetSerializableProxy,
373                     targetSerializableProxy
374                         .getProperties()
375                         .associateBy { checkNotNull(it.name).toString() }
376                         .toMutableMap(),
377                     sharedDataTypeMap,
378                     seenDataTypeQualifiers,
379                     resolvedAnnotatedSerializableProxies
380                 )
381                 AppFunctionReferenceTypeMetadata(
382                     referenceDataType =
383                         appFunctionTypeReference.selfTypeReference
384                             .toTypeName()
385                             .ignoreNullable()
386                             .toString(),
387                     isNullable = appFunctionTypeReference.isNullable,
388                 )
389             }
390             SERIALIZABLE_PROXY_LIST -> {
391                 val targetSerializableProxy =
392                     resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference(
393                         appFunctionTypeReference
394                     )
395                 addSerializableTypeMetadataToSharedDataTypeMap(
396                     targetSerializableProxy,
397                     targetSerializableProxy
398                         .getProperties()
399                         .associateBy { checkNotNull(it.name).toString() }
400                         .toMutableMap(),
401                     sharedDataTypeMap,
402                     seenDataTypeQualifiers,
403                     resolvedAnnotatedSerializableProxies
404                 )
405                 AppFunctionArrayTypeMetadata(
406                     itemType =
407                         AppFunctionReferenceTypeMetadata(
408                             referenceDataType =
409                                 appFunctionTypeReference.itemTypeReference
410                                     .toTypeName()
411                                     .ignoreNullable()
412                                     .toString(),
413                             isNullable =
414                                 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference)
415                                     .isNullable,
416                         ),
417                     isNullable = appFunctionTypeReference.isNullable,
418                 )
419             }
420         }
421     }
422 
423     /**
424      * Adds the [AppFunctionDataTypeMetadata] for a serializable/capability type to the shared data
425      * type map.
426      *
427      * @param appFunctionSerializableType the [AnnotatedAppFunctionSerializable] for the
428      *   serializable or capability type being processed.
429      * @param unvisitedSerializableProperties a map of unvisited serializable properties. This map
430      *   is used to track the properties that have not yet been visited. The map is updated as the
431      *   properties are visited.
432      * @param sharedDataTypeMap a map of shared data types. This map is used to store the
433      *   [AppFunctionDataTypeMetadata] for all serializable/capability types that are used in an app
434      *   function. This map is used to avoid duplicating the metadata for the same serializable
435      *   type.
436      * @param seenDataTypeQualifiers a set of seen data type qualifiers. This set is used to avoid
437      *   processing the same serializable type multiple times.
438      * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies.
439      */
440     // TODO: Document traversal rules.
441     private fun addSerializableTypeMetadataToSharedDataTypeMap(
442         appFunctionSerializableType: AnnotatedAppFunctionSerializable,
443         unvisitedSerializableProperties: MutableMap<String, AppFunctionPropertyDeclaration>,
444         sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>,
445         seenDataTypeQualifiers: MutableSet<String>,
446         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
447     ) {
448         val serializableTypeQualifiedName =
449             if (appFunctionSerializableType is AnnotatedAppFunctionSerializableProxy) {
450                 checkNotNull(appFunctionSerializableType.targetClassDeclaration.qualifiedName)
451                     .asString()
452             } else {
453                 appFunctionSerializableType.qualifiedName
454             }
455         // This type has already been added to the sharedDataMap.
456         if (seenDataTypeQualifiers.contains(serializableTypeQualifiedName)) {
457             return
458         }
459         seenDataTypeQualifiers.add(serializableTypeQualifiedName)
460 
461         val superTypesWithSerializableAnnotation =
462             appFunctionSerializableType.findSuperTypesWithSerializableAnnotation()
463         val superTypesWithCapabilityAnnotation =
464             appFunctionSerializableType.findSuperTypesWithCapabilityAnnotation()
465         if (
466             superTypesWithSerializableAnnotation.isEmpty() &&
467                 superTypesWithCapabilityAnnotation.isEmpty()
468         ) {
469             // If there is no super type, then this is a base serializable object.
470             sharedDataTypeMap.put(
471                 serializableTypeQualifiedName,
472                 buildObjectTypeMetadataForObjectParameters(
473                     serializableTypeQualifiedName,
474                     appFunctionSerializableType.getProperties(),
475                     unvisitedSerializableProperties,
476                     sharedDataTypeMap,
477                     seenDataTypeQualifiers,
478                     resolvedAnnotatedSerializableProxies
479                 )
480             )
481         } else {
482             // If there are superTypes, we first need to build the list of superTypes for this
483             // serializable to match.
484             val matchAllSuperTypesList: List<AppFunctionDataTypeMetadata> = buildList {
485                 for (serializableSuperType in superTypesWithSerializableAnnotation) {
486                     addSerializableTypeMetadataToSharedDataTypeMap(
487                         AnnotatedAppFunctionSerializable(serializableSuperType),
488                         unvisitedSerializableProperties,
489                         sharedDataTypeMap,
490                         seenDataTypeQualifiers,
491                         resolvedAnnotatedSerializableProxies
492                     )
493                     add(
494                         AppFunctionReferenceTypeMetadata(
495                             referenceDataType =
496                                 checkNotNull(serializableSuperType.toClassName().canonicalName),
497                             // Shared type should be the most permissive version (i.e. nullable) by
498                             // default. This is because the outer AllOfType to this shared type
499                             // can add further constraint (i.e. non-null) if required.
500                             isNullable = true
501                         )
502                     )
503                 }
504 
505                 for (capabilitySuperType in superTypesWithCapabilityAnnotation) {
506                     add(
507                         buildObjectTypeMetadataForObjectParameters(
508                             checkNotNull(capabilitySuperType.toClassName().canonicalName),
509                             capabilitySuperType
510                                 .getDeclaredProperties()
511                                 .map { AppFunctionPropertyDeclaration(it) }
512                                 .toList(),
513                             unvisitedSerializableProperties,
514                             sharedDataTypeMap,
515                             seenDataTypeQualifiers,
516                             resolvedAnnotatedSerializableProxies
517                         )
518                     )
519                 }
520 
521                 if (unvisitedSerializableProperties.isNotEmpty()) {
522                     // Since all superTypes have been visited, then the remaining parameters in the
523                     // unvisitedSerializableParameters map belong to the subclass directly.
524                     add(
525                         buildObjectTypeMetadataForObjectParameters(
526                             serializableTypeQualifiedName,
527                             unvisitedSerializableProperties.values.toList(),
528                             unvisitedSerializableProperties,
529                             sharedDataTypeMap,
530                             seenDataTypeQualifiers,
531                             resolvedAnnotatedSerializableProxies
532                         )
533                     )
534                 }
535             }
536 
537             // Finally add allOf the datatypes required to build this composed objects to the
538             // components map
539             sharedDataTypeMap.put(
540                 serializableTypeQualifiedName,
541                 AppFunctionAllOfTypeMetadata(
542                     qualifiedName = serializableTypeQualifiedName,
543                     matchAll = matchAllSuperTypesList,
544                     // Shared type should be the most permissive version (i.e. nullable) by
545                     // default. This is because the outer ReferenceType to this shared type
546                     // can add further constraint (i.e. non-null) if required.
547                     isNullable = true
548                 )
549             )
550         }
551     }
552 
553     /**
554      * Builds an [AppFunctionObjectTypeMetadata] for a serializable/capability type.
555      *
556      * @param typeQualifiedName the qualified name of the serializable/capability type being
557      *   processed. This is the qualified name of the class that is annotated with
558      *   [androidx.appfunctions.AppFunctionSerializable] or
559      *   [androidx.appfunctions.AppFunctionSchemaCapability].
560      * @param currentPropertiesList the list of properties from the serializable/capability class
561      *   that is being processed.
562      * @param unvisitedSerializableProperties a map of unvisited serializable properties. This map
563      *   is used to track the properties that have not yet been visited. The map is updated as the
564      *   properties are visited. The map should be a superset of the [currentPropertiesList] as it
565      *   can contain other properties belonging to a subclass of the current [typeQualifiedName]
566      *   class being processed.
567      * @param sharedDataTypeMap a map of shared data types. This map is used to store the
568      *   [AppFunctionDataTypeMetadata] for all serializable types that are used in an app function.
569      *   This map is used to avoid duplicating the metadata for the same serializable type.
570      * @param seenDataTypeQualifiers a set of seen data type qualifiers. This set is used to avoid
571      *   processing the same serializable type multiple times.
572      * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies.
573      * @return an [AppFunctionObjectTypeMetadata] for the serializable type.
574      */
575     private fun buildObjectTypeMetadataForObjectParameters(
576         typeQualifiedName: String,
577         currentPropertiesList: List<AppFunctionPropertyDeclaration>,
578         unvisitedSerializableProperties: MutableMap<String, AppFunctionPropertyDeclaration>,
579         sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>,
580         seenDataTypeQualifiers: MutableSet<String>,
581         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
582     ): AppFunctionObjectTypeMetadata {
583         val currentSerializableProperties: List<AppFunctionPropertyDeclaration> = buildList {
584             for (property in currentPropertiesList) {
585                 // This property has now been visited. Remove it from the
586                 // unvisitedSerializableProperties map so that we don't visit it again when
587                 // processing the rest of a sub-class that implements this superclass.
588                 // This is because before processing a subclass we process its superclass first
589                 // so the unvisitedSerializableProperties could still contain properties not
590                 // directly included in the current class being processed.
591                 add(checkNotNull(unvisitedSerializableProperties.remove(property.name)))
592             }
593         }
594         return buildObjectTypeMetadataForObjectProperty(
595             typeQualifiedName,
596             currentSerializableProperties,
597             sharedDataTypeMap,
598             seenDataTypeQualifiers,
599             resolvedAnnotatedSerializableProxies
600         )
601     }
602 
603     private fun buildObjectTypeMetadataForObjectProperty(
604         serializableTypeQualifiedName: String,
605         currentSerializableProperties: List<AppFunctionPropertyDeclaration>,
606         sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>,
607         seenDataTypeQualifiers: MutableSet<String>,
608         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
609     ): AppFunctionObjectTypeMetadata {
610         val requiredPropertiesList: MutableList<String> = mutableListOf()
611         val appFunctionSerializablePropertiesMap: Map<String, AppFunctionDataTypeMetadata> =
612             buildMap {
613                 for (property in currentSerializableProperties) {
614                     val innerAppFunctionDataTypeMetadata =
615                         property.type.toAppFunctionDataTypeMetadata(
616                             sharedDataTypeMap,
617                             seenDataTypeQualifiers,
618                             resolvedAnnotatedSerializableProxies
619                         )
620                     put(property.name, innerAppFunctionDataTypeMetadata)
621                     // TODO(b/394553462): Parse required state from annotation.
622                     requiredPropertiesList.add(property.name)
623                 }
624             }
625         return AppFunctionObjectTypeMetadata(
626             properties = appFunctionSerializablePropertiesMap,
627             required = requiredPropertiesList,
628             qualifiedName = serializableTypeQualifiedName,
629             // Shared type should be the most permissive version (i.e. nullable) by default.
630             // This is because the outer ReferenceType to this shared type can add further
631             // constraint (i.e. non-null) if required.
632             isNullable = true,
633         )
634     }
635 
636     private fun AppFunctionTypeReference.toAppFunctionDataType(): Int {
637         return when (this.typeCategory) {
638             PRIMITIVE_SINGULAR -> selfTypeReference.toAppFunctionDatatype()
639             SERIALIZABLE_PROXY_SINGULAR,
640             SERIALIZABLE_SINGULAR -> AppFunctionObjectTypeMetadata.TYPE
641             PRIMITIVE_ARRAY,
642             PRIMITIVE_LIST,
643             SERIALIZABLE_PROXY_LIST,
644             SERIALIZABLE_LIST -> AppFunctionArrayTypeMetadata.TYPE
645         }
646     }
647 
648     private fun AppFunctionTypeReference.determineArrayItemType(): Int {
649         return when (this.typeCategory) {
650             SERIALIZABLE_LIST -> AppFunctionObjectTypeMetadata.TYPE
651             PRIMITIVE_ARRAY -> selfTypeReference.toAppFunctionDatatype()
652             PRIMITIVE_LIST -> itemTypeReference.toAppFunctionDatatype()
653             SERIALIZABLE_PROXY_LIST -> itemTypeReference.toAppFunctionDatatype()
654             PRIMITIVE_SINGULAR,
655             SERIALIZABLE_PROXY_SINGULAR,
656             SERIALIZABLE_SINGULAR ->
657                 throw ProcessingException(
658                     "Not a supported array type " +
659                         selfTypeReference.ensureQualifiedTypeName().asString(),
660                     selfTypeReference,
661                 )
662         }
663     }
664 
665     private fun computeAppFunctionAnnotationProperties(
666         functionDeclaration: KSFunctionDeclaration
667     ): AppFunctionAnnotationProperties {
668         val appFunctionAnnotation =
669             functionDeclaration.annotations.findAnnotation(AppFunctionAnnotation.CLASS_NAME)
670                 ?: throw ProcessingException(
671                     "Function not annotated with @AppFunction.",
672                     functionDeclaration,
673                 )
674         val enabled =
675             appFunctionAnnotation.requirePropertyValueOfType(
676                 AppFunctionAnnotation.PROPERTY_IS_ENABLED,
677                 Boolean::class,
678             )
679 
680         val rootInterfaceWithAppFunctionSchemaDefinition =
681             findRootAppFunctionSchemaInterface(functionDeclaration)
682 
683         val schemaFunctionAnnotation =
684             rootInterfaceWithAppFunctionSchemaDefinition
685                 ?.annotations
686                 ?.findAnnotation(AppFunctionSchemaDefinitionAnnotation.CLASS_NAME)
687         val schemaCategory =
688             schemaFunctionAnnotation?.requirePropertyValueOfType(
689                 AppFunctionSchemaDefinitionAnnotation.PROPERTY_CATEGORY,
690                 String::class,
691             )
692         val schemaName =
693             schemaFunctionAnnotation?.requirePropertyValueOfType(
694                 AppFunctionSchemaDefinitionAnnotation.PROPERTY_NAME,
695                 String::class,
696             )
697         val schemaVersion =
698             schemaFunctionAnnotation
699                 ?.requirePropertyValueOfType(
700                     AppFunctionSchemaDefinitionAnnotation.PROPERTY_VERSION,
701                     Int::class,
702                 )
703                 ?.toLong()
704 
705         return AppFunctionAnnotationProperties(enabled, schemaName, schemaVersion, schemaCategory)
706     }
707 
708     private fun findRootAppFunctionSchemaInterface(
709         function: KSFunctionDeclaration
710     ): KSClassDeclaration? {
711         val parentDeclaration = function.parentDeclaration as? KSClassDeclaration ?: return null
712 
713         // Check if the enclosing class has the @AppFunctionSchemaDefinition
714         val annotation =
715             parentDeclaration.annotations.findAnnotation(
716                 AppFunctionSchemaDefinitionAnnotation.CLASS_NAME
717             )
718         if (annotation != null) {
719             return parentDeclaration
720         }
721 
722         val superClassFunction = (function.findOverridee() as? KSFunctionDeclaration) ?: return null
723         return findRootAppFunctionSchemaInterface(superClassFunction)
724     }
725 
726     private fun getAnnotatedAppFunctionSerializable(
727         appFunctionTypeReference: AppFunctionTypeReference
728     ): AnnotatedAppFunctionSerializable {
729         val appFunctionSerializableClassDeclaration =
730             appFunctionTypeReference.selfOrItemTypeReference.resolve().declaration
731                 as KSClassDeclaration
732         return AnnotatedAppFunctionSerializable(
733                 appFunctionSerializableClassDeclaration,
734             )
735             .parameterizedBy(appFunctionTypeReference.selfOrItemTypeReference.resolve().arguments)
736             .validate()
737     }
738 
739     private fun AppFunctionAnnotationProperties.toAppFunctionSchemaMetadata():
740         AppFunctionSchemaMetadata? {
741         return if (this.schemaName != null) {
742             AppFunctionSchemaMetadata(
743                 category = checkNotNull(this.schemaCategory),
744                 name = this.schemaName,
745                 version = checkNotNull(this.schemaVersion),
746             )
747         } else {
748             null
749         }
750     }
751 
752     private fun AppFunctionTypeReference.typeOrItemTypeIsAppFunctionSerializable(): Boolean {
753         return this.isOfTypeCategory(SERIALIZABLE_SINGULAR) ||
754             this.isOfTypeCategory(SERIALIZABLE_LIST)
755     }
756 
757     private data class AppFunctionAnnotationProperties(
758         val isEnabledByDefault: Boolean,
759         val schemaName: String?,
760         val schemaVersion: Long?,
761         val schemaCategory: String?,
762     )
763 }
764