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.annotation.VisibleForTesting
20 import androidx.appfunctions.AppFunctionData
21 import androidx.appfunctions.compiler.AppFunctionCompiler
22 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializable
23 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy
24 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies
25 import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver
26 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass
27 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod.APP_FUNCTION_DATA_PARAM_NAME
28 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.ToAppFunctionDataMethod.APP_FUNCTION_SERIALIZABLE_PARAM_NAME
29 import androidx.appfunctions.compiler.core.ProcessingException
30 import androidx.appfunctions.compiler.core.logException
31 import androidx.appfunctions.compiler.core.toClassName
32 import androidx.appfunctions.compiler.processors.AppFunctionSerializableFactoryCodeBuilder.Companion.getTypeParameterPropertyName
33 import com.google.devtools.ksp.processing.CodeGenerator
34 import com.google.devtools.ksp.processing.Dependencies
35 import com.google.devtools.ksp.processing.KSPLogger
36 import com.google.devtools.ksp.processing.Resolver
37 import com.google.devtools.ksp.processing.SymbolProcessor
38 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
39 import com.google.devtools.ksp.processing.SymbolProcessorProvider
40 import com.google.devtools.ksp.symbol.KSAnnotated
41 import com.google.devtools.ksp.symbol.KSTypeParameter
42 import com.google.devtools.ksp.symbol.Modifier
43 import com.squareup.kotlinpoet.FileSpec
44 import com.squareup.kotlinpoet.FunSpec
45 import com.squareup.kotlinpoet.KModifier
46 import com.squareup.kotlinpoet.ParameterSpec
47 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
48 import com.squareup.kotlinpoet.PropertySpec
49 import com.squareup.kotlinpoet.TypeSpec
50 import com.squareup.kotlinpoet.TypeVariableName
51 import com.squareup.kotlinpoet.asTypeName
52 
53 /**
54  * Generates a factory class with methods to convert classes annotated with
55  * [androidx.appfunctions.AppFunctionSerializable] or
56  * [androidx.appfunctions.AppFunctionSerializableProxy] to [androidx.appfunctions.AppFunctionData],
57  * and vice-versa.
58  *
59  * **Example:**
60  *
61  * ```
62  * @AppFunctionSerializable
63  * class Location(val latitude: Double, val longitude: Double)
64  * ```
65  *
66  * A corresponding `LocationFactory` class will be generated:
67  * ```
68  * @Generated("androidx.appfunctions.compiler.AppFunctionCompiler")
69  * public class LocationFactory : AppFunctionSerializableFactory<Location> {
70  *   override fun fromAppFunctionData(appFunctionData: AppFunctionData): Location {
71  *     val latitude = appFunctionData.getDouble("latitude")
72  *     val longitude = appFunctionData.getDouble("longitude")
73  *
74  *     return Location(latitude, longitude)
75  *   }
76  *
77  *   override fun toAppFunctionData(appFunctionSerializable: Location): AppFunctionData {
78  *     val builder = AppFunctionData.Builder("")
79  *
80  *     builder.setDouble("latitude", location.latitude)
81  *     builder.setDouble("longitude", location.longitude)
82  *
83  *     return builder.build()
84  *   }
85  * }
86  * ```
87  */
88 class AppFunctionSerializableProcessor(
89     private val codeGenerator: CodeGenerator,
90     private val logger: KSPLogger,
91 ) : SymbolProcessor {
92     private var hasProcessed = false
93 
processnull94     override fun process(resolver: Resolver): List<KSAnnotated> {
95         if (hasProcessed) return emptyList()
96         hasProcessed = true
97 
98         try {
99             val entitySymbolResolver = AppFunctionSymbolResolver(resolver)
100             val entityClasses = entitySymbolResolver.resolveAnnotatedAppFunctionSerializables()
101             val globalResolvedAnnotatedSerializableProxies =
102                 ResolvedAnnotatedSerializableProxies(
103                     entitySymbolResolver.resolveAllAnnotatedSerializableProxiesFromModule()
104                 )
105             val localResolvedAnnotatedSerializableProxies =
106                 ResolvedAnnotatedSerializableProxies(
107                     entitySymbolResolver.resolveLocalAnnotatedAppFunctionSerializableProxy()
108                 )
109             for (entity in entityClasses) {
110                 buildAppFunctionSerializableFactoryClass(
111                     entity,
112                     globalResolvedAnnotatedSerializableProxies
113                 )
114             }
115             for (entityProxy in
116                 localResolvedAnnotatedSerializableProxies.resolvedAnnotatedSerializableProxies) {
117                 // Only generate factory for local proxy classes to ensure that the factory is
118                 // only generated once in the same compilation unit as the prexy definition.
119                 buildAppFunctionSerializableProxyFactoryClass(
120                     entityProxy,
121                     globalResolvedAnnotatedSerializableProxies
122                 )
123             }
124             return globalResolvedAnnotatedSerializableProxies.resolvedAnnotatedSerializableProxies
125                 .map { it.appFunctionSerializableProxyClass }
126         } catch (e: ProcessingException) {
127             logger.logException(e)
128         }
129 
130         return emptyList()
131     }
132 
buildAppFunctionSerializableFactoryClassnull133     private fun buildAppFunctionSerializableFactoryClass(
134         annotatedClass: AnnotatedAppFunctionSerializable,
135         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
136     ) {
137         val superInterfaceClass =
138             AppFunctionSerializableFactoryClass.CLASS_NAME.parameterizedBy(
139                 listOf(annotatedClass.typeName)
140             )
141 
142         val factoryCodeBuilder =
143             AppFunctionSerializableFactoryCodeBuilder(
144                 annotatedClass,
145                 resolvedAnnotatedSerializableProxies
146             )
147 
148         val generatedFactoryClassName = "\$${annotatedClass.originalClassName.simpleName}Factory"
149         val fileSpec =
150             FileSpec.builder(
151                     annotatedClass.originalClassName.packageName,
152                     generatedFactoryClassName
153                 )
154                 .addType(
155                     TypeSpec.classBuilder(generatedFactoryClassName)
156                         .addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
157                         .addSuperinterface(superInterfaceClass)
158                         .apply {
159                             if (annotatedClass.modifiers.contains(Modifier.INTERNAL)) {
160                                 addModifiers(KModifier.INTERNAL)
161                             }
162 
163                             if (annotatedClass.typeParameters.isNotEmpty()) {
164                                 setGenericPrimaryConstructor(annotatedClass.typeParameters)
165                             }
166                         }
167                         .addFunction(
168                             buildFromAppFunctionDataFunction(annotatedClass, factoryCodeBuilder)
169                         )
170                         .addFunction(
171                             buildToAppFunctionDataFunction(annotatedClass, factoryCodeBuilder)
172                         )
173                         .build()
174                 )
175                 .build()
176         codeGenerator
177             .createNewFile(
178                 Dependencies(
179                     aggregating = true,
180                     *annotatedClass.getSerializableSourceFiles().toTypedArray()
181                 ),
182                 annotatedClass.originalClassName.packageName,
183                 generatedFactoryClassName
184             )
185             .bufferedWriter()
186             .use { fileSpec.writeTo(it) }
187     }
188 
setGenericPrimaryConstructornull189     private fun TypeSpec.Builder.setGenericPrimaryConstructor(
190         typeParameters: List<KSTypeParameter>
191     ) {
192         val primaryConstructorBuilder = FunSpec.constructorBuilder()
193         for (typeParameter in typeParameters) {
194             val typeParamName = typeParameter.name.asString()
195             val typeTokenType =
196                 AppFunctionSerializableFactoryClass.TypeParameterClass.CLASS_NAME.parameterizedBy(
197                     TypeVariableName(typeParameter.name.asString())
198                 )
199             val typeParameterPropertyName = getTypeParameterPropertyName(typeParameter)
200 
201             primaryConstructorBuilder.addParameter(
202                 typeParameterPropertyName,
203                 typeTokenType,
204             )
205 
206             addProperty(
207                 PropertySpec.builder(typeParameterPropertyName, typeTokenType)
208                     .initializer(typeParameterPropertyName)
209                     .addModifiers(KModifier.PRIVATE)
210                     .build()
211             )
212             addTypeVariable(TypeVariableName(typeParamName))
213         }
214 
215         primaryConstructor(primaryConstructorBuilder.build())
216     }
217 
buildAppFunctionSerializableProxyFactoryClassnull218     private fun buildAppFunctionSerializableProxyFactoryClass(
219         annotatedProxyClass: AnnotatedAppFunctionSerializableProxy,
220         resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies
221     ) {
222         val generatedSerializableProxyFactoryClassName =
223             "\$${checkNotNull(
224                 annotatedProxyClass.targetClassDeclaration.simpleName).asString()}Factory"
225         // Check if the factory class has already been generated.
226         if (
227             codeGenerator.generatedFile.any {
228                 it.path.contains(generatedSerializableProxyFactoryClassName)
229             }
230         ) {
231             return
232         }
233 
234         val proxySuperInterfaceClass =
235             AppFunctionSerializableFactoryClass.CLASS_NAME.parameterizedBy(
236                 annotatedProxyClass.targetClassDeclaration.toClassName()
237             )
238 
239         val serializableProxyClassBuilder =
240             TypeSpec.classBuilder(generatedSerializableProxyFactoryClassName)
241         val factoryCodeBuilder =
242             AppFunctionSerializableFactoryCodeBuilder(
243                 annotatedProxyClass,
244                 resolvedAnnotatedSerializableProxies
245             )
246         serializableProxyClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION)
247         serializableProxyClassBuilder.addSuperinterface(proxySuperInterfaceClass)
248         serializableProxyClassBuilder.addFunction(
249             buildProxyFromAppFunctionDataFunction(annotatedProxyClass, factoryCodeBuilder)
250         )
251         serializableProxyClassBuilder.addFunction(
252             buildProxyToAppFunctionDataFunction(annotatedProxyClass, factoryCodeBuilder)
253         )
254         val fileSpec =
255             FileSpec.builder(
256                     annotatedProxyClass.originalClassName.packageName,
257                     generatedSerializableProxyFactoryClassName
258                 )
259                 .addType(serializableProxyClassBuilder.build())
260                 .build()
261         codeGenerator
262             .createNewFile(
263                 Dependencies(
264                     aggregating = true,
265                     *annotatedProxyClass.getSerializableSourceFiles().toTypedArray()
266                 ),
267                 annotatedProxyClass.originalClassName.packageName,
268                 generatedSerializableProxyFactoryClassName
269             )
270             .bufferedWriter()
271             .use { fileSpec.writeTo(it) }
272     }
273 
buildFromAppFunctionDataFunctionnull274     private fun buildFromAppFunctionDataFunction(
275         annotatedClass: AnnotatedAppFunctionSerializable,
276         factoryCodeBuilder: AppFunctionSerializableFactoryCodeBuilder,
277     ): FunSpec {
278         return FunSpec.builder(
279                 AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod.METHOD_NAME
280             )
281             .addModifiers(KModifier.OVERRIDE)
282             .addParameter(
283                 ParameterSpec.builder(APP_FUNCTION_DATA_PARAM_NAME, AppFunctionData::class).build()
284             )
285             .addCode(factoryCodeBuilder.appendFromAppFunctionDataMethodBody())
286             .returns(annotatedClass.typeName)
287             .build()
288     }
289 
buildProxyFromAppFunctionDataFunctionnull290     private fun buildProxyFromAppFunctionDataFunction(
291         annotatedProxyClass: AnnotatedAppFunctionSerializableProxy,
292         factoryCodeBuilder: AppFunctionSerializableFactoryCodeBuilder,
293     ): FunSpec {
294         return FunSpec.builder(
295                 AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod.METHOD_NAME
296             )
297             .addModifiers(KModifier.OVERRIDE)
298             .addParameter(
299                 ParameterSpec.builder(APP_FUNCTION_DATA_PARAM_NAME, AppFunctionData::class).build()
300             )
301             .addCode(factoryCodeBuilder.appendFromAppFunctionDataMethodBodyForProxy())
302             .returns(annotatedProxyClass.targetClassDeclaration.toClassName())
303             .build()
304     }
305 
buildToAppFunctionDataFunctionnull306     private fun buildToAppFunctionDataFunction(
307         annotatedClass: AnnotatedAppFunctionSerializable,
308         factoryCodeBuilder: AppFunctionSerializableFactoryCodeBuilder
309     ): FunSpec {
310         return FunSpec.builder(
311                 AppFunctionSerializableFactoryClass.ToAppFunctionDataMethod.METHOD_NAME
312             )
313             .addModifiers(KModifier.OVERRIDE)
314             .addParameter(
315                 ParameterSpec.builder(APP_FUNCTION_SERIALIZABLE_PARAM_NAME, annotatedClass.typeName)
316                     .build()
317             )
318             .addCode(factoryCodeBuilder.appendToAppFunctionDataMethodBody())
319             .returns(AppFunctionData::class.asTypeName())
320             .build()
321     }
322 
buildProxyToAppFunctionDataFunctionnull323     private fun buildProxyToAppFunctionDataFunction(
324         annotatedProxyClass: AnnotatedAppFunctionSerializableProxy,
325         factoryCodeBuilder: AppFunctionSerializableFactoryCodeBuilder
326     ): FunSpec {
327         return FunSpec.builder(
328                 AppFunctionSerializableFactoryClass.ToAppFunctionDataMethod.METHOD_NAME
329             )
330             .addModifiers(KModifier.OVERRIDE)
331             .addParameter(
332                 ParameterSpec.builder(
333                         APP_FUNCTION_SERIALIZABLE_PARAM_NAME,
334                         annotatedProxyClass.targetClassDeclaration.toClassName()
335                     )
336                     .build()
337             )
338             .addCode(factoryCodeBuilder.appendToAppFunctionDataMethodBodyForProxy())
339             .returns(AppFunctionData::class.asTypeName())
340             .build()
341     }
342 
343     @VisibleForTesting
344     class Provider : SymbolProcessorProvider {
createnull345         override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
346             return AppFunctionSerializableProcessor(
347                 environment.codeGenerator,
348                 environment.logger,
349             )
350         }
351     }
352 }
353