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.AnnotatedAppFunctions 21 import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver 22 import androidx.appfunctions.compiler.core.fromCamelCaseToScreamingSnakeCase 23 import com.google.devtools.ksp.processing.CodeGenerator 24 import com.google.devtools.ksp.processing.Dependencies 25 import com.google.devtools.ksp.processing.Resolver 26 import com.google.devtools.ksp.processing.SymbolProcessor 27 import com.google.devtools.ksp.symbol.KSAnnotated 28 import com.squareup.kotlinpoet.FileSpec 29 import com.squareup.kotlinpoet.KModifier 30 import com.squareup.kotlinpoet.PropertySpec 31 import com.squareup.kotlinpoet.TypeSpec 32 import com.squareup.kotlinpoet.asTypeName 33 34 /** 35 * The processor to generate ID classes for AppFunction. 36 * 37 * For each AppFunction class, a corresponding ID class would be generated in the same package for 38 * developer to access the function id easily. For example, 39 * ``` 40 * class NoteFunction: CreateNote { 41 * @AppFunction 42 * override suspend fun createNote(): Note { ... } 43 * } 44 * ``` 45 * 46 * A corresponding `NoteFunctionIds` class will be generated: 47 * ``` 48 * object NoteFunctionIds { 49 * const val CREATE_NOTE_ID = "someId" 50 * } 51 * ``` 52 */ 53 class AppFunctionIdProcessor( 54 private val codeGenerator: CodeGenerator, 55 ) : SymbolProcessor { processnull56 override fun process(resolver: Resolver): List<KSAnnotated> { 57 val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver) 58 val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions() 59 for (appFunctionClass in appFunctionClasses) { 60 generateAppFunctionIdClasses(appFunctionClass) 61 } 62 return emptyList() 63 } 64 generateAppFunctionIdClassesnull65 private fun generateAppFunctionIdClasses(appFunctionClass: AnnotatedAppFunctions) { 66 val originalPackageName = appFunctionClass.classDeclaration.packageName.asString() 67 val originalClassName = appFunctionClass.classDeclaration.simpleName.asString() 68 69 val idClassName = getAppFunctionIdClassName(originalClassName) 70 val classBuilder = 71 TypeSpec.objectBuilder(idClassName).apply { 72 addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION) 73 addAppFunctionIdProperties(appFunctionClass) 74 } 75 76 val fileSpec = 77 FileSpec.builder(originalPackageName, idClassName).addType(classBuilder.build()).build() 78 codeGenerator 79 .createNewFile( 80 Dependencies( 81 // Isolating, because the information to construct the ID class only comes 82 // from a single class containing AppFunction implementations and never from 83 // other or new files. 84 aggregating = false, 85 checkNotNull(appFunctionClass.classDeclaration.containingFile) 86 ), 87 originalPackageName, 88 idClassName 89 ) 90 .bufferedWriter() 91 .use { fileSpec.writeTo(it) } 92 } 93 getAppFunctionIdClassNamenull94 private fun getAppFunctionIdClassName(functionClassName: String): String { 95 return "${functionClassName}Ids" 96 } 97 addAppFunctionIdPropertiesnull98 private fun TypeSpec.Builder.addAppFunctionIdProperties( 99 appFunctionClass: AnnotatedAppFunctions 100 ) { 101 for (appFunctionDeclaration in appFunctionClass.appFunctionDeclarations) { 102 // For example, transform method name from "createNote" to "CREATE_NOTE" 103 val functionMethodName = 104 appFunctionDeclaration.simpleName.asString().fromCamelCaseToScreamingSnakeCase() 105 val propertySpec = 106 PropertySpec.builder("${functionMethodName}_ID", String::class.asTypeName()) 107 .addModifiers(KModifier.CONST) 108 .initializer( 109 "%S", 110 appFunctionClass.getAppFunctionIdentifier(appFunctionDeclaration) 111 ) 112 .build() 113 this.addProperty(propertySpec) 114 } 115 } 116 } 117