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