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.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST
20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR
21 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR
22 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableAnnotation
23 import com.google.devtools.ksp.symbol.KSClassDeclaration
24 import com.google.devtools.ksp.symbol.KSFile
25 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
26 import com.google.devtools.ksp.symbol.KSNode
27 import com.google.devtools.ksp.symbol.KSTypeArgument
28 import com.google.devtools.ksp.symbol.KSTypeParameter
29 import com.google.devtools.ksp.symbol.KSTypeReference
30 import com.google.devtools.ksp.symbol.Modifier
31 import com.squareup.kotlinpoet.ClassName
32 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
33 import com.squareup.kotlinpoet.TypeName
34 
35 /** Represents a class annotated with [androidx.appfunctions.AppFunctionSerializable]. */
36 open class AnnotatedAppFunctionSerializable(
37     private val appFunctionSerializableClass: KSClassDeclaration,
38 ) {
39     /** The qualified name of the class being annotated with AppFunctionSerializable. */
40     open val qualifiedName: String by lazy {
41         appFunctionSerializableClass.toClassName().canonicalName
42     }
43 
44     /** The super type of the class being annotated with AppFunctionSerializable */
45     val superTypes: Sequence<KSTypeReference> by lazy { appFunctionSerializableClass.superTypes }
46 
47     /** The modifier of the class being annotated with AppFunctionSerializable. */
48     val modifiers: Set<Modifier> by lazy { appFunctionSerializableClass.modifiers }
49 
50     /** The primary constructor if available. */
51     val primaryConstructor: KSFunctionDeclaration? by lazy {
52         appFunctionSerializableClass.primaryConstructor
53     }
54 
55     /** The [KSNode] to which the processing error is attributed. */
56     val attributeNode: KSNode by lazy { appFunctionSerializableClass }
57 
58     /** The list of [KSTypeParameter] of the AppFunctionSerializable */
59     val typeParameters: List<KSTypeParameter> by lazy {
60         appFunctionSerializableClass.typeParameters
61     }
62 
63     /** The original [ClassName] of the AppFunctionSerializable. */
64     val originalClassName: ClassName by lazy {
65         ClassName(
66             appFunctionSerializableClass.packageName.asString(),
67             appFunctionSerializableClass.simpleName.asString()
68         )
69     }
70 
71     /** The [TypeName] of the AppFunctionSerializable. */
72     val typeName: TypeName by lazy {
73         if (typeParameters.isEmpty()) {
74             originalClassName
75         } else {
76             originalClassName.parameterizedBy(
77                 typeParameters.map(KSTypeParameter::toTypeVariableName)
78             )
79         }
80     }
81 
82     /**
83      * Parameterize [AnnotatedAppFunctionSerializable] with [arguments].
84      *
85      * If [arguments] is empty, the original [AnnotatedAppFunctionSerializable] would be returned
86      * directly.
87      */
88     fun parameterizedBy(arguments: List<KSTypeArgument>): AnnotatedAppFunctionSerializable {
89         if (arguments.isEmpty()) {
90             return this
91         }
92         return AnnotatedParameterizedAppFunctionSerializable(
93             appFunctionSerializableClass,
94             arguments
95         )
96     }
97 
98     // TODO(b/392587953): throw an error if a property has the same name as one of the factory
99     //  method parameters
100     /**
101      * Validates that the class annotated with AppFunctionSerializable follows app function's spec.
102      *
103      * @throws ProcessingException if the class does not adhere to the requirements
104      */
105     open fun validate(): AnnotatedAppFunctionSerializable {
106         val validateHelper = AppFunctionSerializableValidateHelper(this)
107         validateHelper.validatePrimaryConstructor()
108         validateHelper.validateParameters()
109         return this
110     }
111 
112     /**
113      * Finds all super types of the serializable [appFunctionSerializableClass] that are annotated
114      * with the [androidx.appfunctions.AppFunctionSchemaCapability] annotation.
115      *
116      * For example, consider the following classes:
117      * ```
118      * @AppFunctionSchemaCapability
119      * public interface AppFunctionOpenable {
120      *     public val intentToOpen: PendingIntent
121      * }
122      *
123      * public interface OpenableResponse : AppFunctionOpenable {
124      *     override val intentToOpen: PendingIntent
125      * }
126      *
127      * @AppFunctionSerializable
128      * class MySerializableClass(
129      *   override val intentToOpen: PendingIntent
130      * ) : OpenableResponse
131      * ```
132      *
133      * This method will return the [KSClassDeclaration] of `AppFunctionOpenable` since it is a super
134      * type of `MySerializableClass` and is annotated with the
135      * [androidx.appfunctions.AppFunctionSchemaCapability] annotation.
136      *
137      * @return a set of [KSClassDeclaration] for all super types of the
138      *   [appFunctionSerializableClass] that are annotated with
139      *   [androidx.appfunctions.AppFunctionSchemaCapability].
140      */
141     fun findSuperTypesWithCapabilityAnnotation(): Set<KSClassDeclaration> {
142         return buildSet {
143             val unvisitedSuperTypes: MutableList<KSTypeReference> =
144                 appFunctionSerializableClass.superTypes.toMutableList()
145 
146             while (!unvisitedSuperTypes.isEmpty()) {
147                 val superTypeClassDeclaration =
148                     unvisitedSuperTypes.removeLast().resolve().declaration as KSClassDeclaration
149                 if (
150                     superTypeClassDeclaration.annotations.findAnnotation(
151                         IntrospectionHelper.AppFunctionSchemaCapability.CLASS_NAME
152                     ) != null
153                 ) {
154                     add(superTypeClassDeclaration)
155                 }
156                 if (
157                     superTypeClassDeclaration.annotations.findAnnotation(
158                         IntrospectionHelper.AppFunctionSerializableAnnotation.CLASS_NAME
159                     ) == null
160                 ) {
161                     // Only consider non serializable super types since serializable super types
162                     // are already handled separately
163                     unvisitedSuperTypes.addAll(superTypeClassDeclaration.superTypes)
164                 }
165             }
166         }
167     }
168 
169     /**
170      * Finds all super types of the serializable [appFunctionSerializableClass] that are annotated
171      * with the [androidx.appfunctions.AppFunctionSerializable] annotation.
172      *
173      * For example, consider the following classes:
174      * ```
175      * @AppFunctionSerializable
176      * open class Address (
177      *     open val street: String,
178      *     open val city: String,
179      *     open val state: String,
180      *     open val zipCode: String,
181      * )
182      *
183      * @AppFunctionSerializable
184      * class MySerializableClass(
185      *     override val street: String,
186      *     override val city: String,
187      *     override val state: String,
188      *     override val zipCode: String,
189      * ) : Address
190      * ```
191      *
192      * This method will return the [KSClassDeclaration] of `Address` since it is a super type of
193      * `MySerializableClass` and is annotated with the
194      * [androidx.appfunctions.AppFunctionSerializable] annotation.
195      *
196      * @return a set of [KSClassDeclaration] for all super types of the
197      *   [appFunctionSerializableClass] that are annotated with
198      *   [androidx.appfunctions.AppFunctionSerializable].
199      */
200     fun findSuperTypesWithSerializableAnnotation(): Set<KSClassDeclaration> {
201         return appFunctionSerializableClass.superTypes
202             .map { it.resolve().declaration as KSClassDeclaration }
203             .filter {
204                 it.annotations.findAnnotation(AppFunctionSerializableAnnotation.CLASS_NAME) != null
205             }
206             .toSet()
207     }
208 
209     /** Returns the annotated class's properties as defined in its primary constructor. */
210     open fun getProperties(): List<AppFunctionPropertyDeclaration> {
211         return checkNotNull(appFunctionSerializableClass.primaryConstructor).parameters.map {
212             valueParameter ->
213             AppFunctionPropertyDeclaration(valueParameter)
214         }
215     }
216 
217     /** Returns the properties that have @AppFunctionSerializable class types. */
218     fun getSerializablePropertyTypeReferences(): Set<AppFunctionTypeReference> {
219         return getProperties()
220             .filterNot { it.isGenericType }
221             .map { property -> AppFunctionTypeReference(property.type) }
222             .filter { afType ->
223                 afType.isOfTypeCategory(SERIALIZABLE_SINGULAR) ||
224                     afType.isOfTypeCategory(SERIALIZABLE_LIST)
225             }
226             .toSet()
227     }
228 
229     /** Returns the properties that have @AppFunctionSerializableProxy class types. */
230     fun getSerializableProxyPropertyTypeReferences(): Set<AppFunctionTypeReference> {
231         return getProperties()
232             .filterNot { it.isGenericType }
233             .map { it -> AppFunctionTypeReference(it.type) }
234             .filter { afType -> afType.isOfTypeCategory(SERIALIZABLE_PROXY_SINGULAR) }
235             .toSet()
236     }
237 
238     /**
239      * Returns the set of source files that contain the definition of [appFunctionSerializableClass]
240      * and all @AppFunctionSerializable classes directly reachable through its fields. This method
241      * differs from [getTransitiveSerializableSourceFiles] by excluding transitively
242      * nested @AppFunctionSerializable classes.
243      */
244     fun getSerializableSourceFiles(): Set<KSFile> {
245         val sourceFileSet: MutableSet<KSFile> = mutableSetOf()
246         appFunctionSerializableClass.containingFile?.let { sourceFileSet.add(it) }
247         for (serializableAfType in getSerializablePropertyTypeReferences()) {
248             val appFunctionSerializableDefinition =
249                 serializableAfType.selfOrItemTypeReference.resolve().declaration
250                     as KSClassDeclaration
251             appFunctionSerializableDefinition.containingFile?.let { sourceFileSet.add(it) }
252         }
253         return sourceFileSet
254     }
255 
256     /**
257      * Returns the set of source files that contain the definition of [appFunctionSerializableClass]
258      * and all @AppFunctionSerializable classes transitively reachable through its fields or nested
259      * classes.
260      */
261     fun getTransitiveSerializableSourceFiles(): Set<KSFile> {
262         val sourceFileSet: MutableSet<KSFile> = mutableSetOf()
263         val visitedSerializableSet: MutableSet<ClassName> = mutableSetOf()
264 
265         // Add the file containing the AppFunctionSerializable class definition immediately it's
266         // seen
267         appFunctionSerializableClass.containingFile?.let { sourceFileSet.add(it) }
268         visitedSerializableSet.add(originalClassName)
269         traverseSerializableClassSourceFiles(sourceFileSet, visitedSerializableSet)
270         return sourceFileSet
271     }
272 
273     private fun traverseSerializableClassSourceFiles(
274         sourceFileSet: MutableSet<KSFile>,
275         visitedSerializableSet: MutableSet<ClassName>
276     ) {
277         for (serializableAfType in getSerializablePropertyTypeReferences()) {
278             val appFunctionSerializableDefinition =
279                 serializableAfType.selfOrItemTypeReference.resolve().declaration
280                     as KSClassDeclaration
281             // Skip serializable that have been seen before
282             if (visitedSerializableSet.contains(originalClassName)) {
283                 continue
284             }
285             // Process newly found serializable
286             sourceFileSet.addAll(
287                 AnnotatedAppFunctionSerializable(appFunctionSerializableDefinition)
288                     .parameterizedBy(serializableAfType.selfOrItemTypeReference.resolve().arguments)
289                     .getTransitiveSerializableSourceFiles()
290             )
291         }
292     }
293 }
294