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.IntrospectionHelper.APP_FUNCTIONS_AGGREGATED_DEPS_PACKAGE_NAME 20 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation 21 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionComponentRegistryAnnotation 22 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableAnnotation 23 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableProxyAnnotation 24 import androidx.appfunctions.compiler.core.IntrospectionHelper.SERIALIZABLE_PROXY_PACKAGE_NAME 25 import com.google.devtools.ksp.KspExperimental 26 import com.google.devtools.ksp.processing.Resolver 27 import com.google.devtools.ksp.symbol.KSClassDeclaration 28 import com.google.devtools.ksp.symbol.KSFunctionDeclaration 29 30 /** The helper class to resolve AppFunction related symbols. */ 31 class AppFunctionSymbolResolver(private val resolver: Resolver) { 32 33 /** Resolves valid functions annotated with @AppFunction annotation. */ 34 fun resolveAnnotatedAppFunctions(): List<AnnotatedAppFunctions> { 35 return resolver 36 .getSymbolsWithAnnotation(AppFunctionAnnotation.CLASS_NAME.canonicalName) 37 .map { declaration -> 38 if (declaration !is KSFunctionDeclaration) { 39 throw ProcessingException( 40 "Only functions can be annotated with @AppFunction", 41 declaration 42 ) 43 } 44 declaration 45 } 46 .groupBy { declaration -> 47 declaration.parentDeclaration as? KSClassDeclaration 48 ?: throw ProcessingException( 49 "Top level functions cannot be annotated with @AppFunction ", 50 declaration 51 ) 52 } 53 .map { (classDeclaration, appFunctionsDeclarations) -> 54 AnnotatedAppFunctions(classDeclaration, appFunctionsDeclarations).validate() 55 } 56 } 57 58 /** 59 * Resolves all classes annotated with @AppFunctionSerializable 60 * 61 * @return a list of AnnotatedAppFunctionSerializable 62 */ 63 fun resolveAnnotatedAppFunctionSerializables(): List<AnnotatedAppFunctionSerializable> { 64 return resolver 65 .getSymbolsWithAnnotation(AppFunctionSerializableAnnotation.CLASS_NAME.canonicalName) 66 .map { declaration -> 67 if (declaration !is KSClassDeclaration) { 68 throw ProcessingException( 69 "Only classes can be annotated with @AppFunctionSerializable", 70 declaration 71 ) 72 } 73 AnnotatedAppFunctionSerializable(declaration).validate() 74 } 75 .toList() 76 } 77 78 /** 79 * Resolves all classes annotated with @AppFunctionSerializableProxy from the current 80 * compilation unit. 81 * 82 * @return a list of AnnotatedAppFunctionSerializableProxy 83 */ 84 fun resolveLocalAnnotatedAppFunctionSerializableProxy(): 85 List<AnnotatedAppFunctionSerializableProxy> { 86 return resolver 87 .getSymbolsWithAnnotation( 88 AppFunctionSerializableProxyAnnotation.CLASS_NAME.canonicalName 89 ) 90 .map { declaration -> 91 if (declaration !is KSClassDeclaration) { 92 throw ProcessingException( 93 "Only classes can be annotated with @AppFunctionSerializableProxy", 94 declaration 95 ) 96 } 97 AnnotatedAppFunctionSerializableProxy(declaration).validate() 98 } 99 .toList() 100 } 101 102 /** 103 * Resolves all classes annotated with @AppFunctionSerializableProxy from the 104 * [SERIALIZABLE_PROXY_PACKAGE_NAME] package. 105 * 106 * @return a list of AnnotatedAppFunctionSerializableProxy 107 */ 108 @OptIn(KspExperimental::class) 109 fun resolveAllAnnotatedSerializableProxiesFromModule(): 110 List<AnnotatedAppFunctionSerializableProxy> { 111 return resolver 112 .getDeclarationsFromPackage(SERIALIZABLE_PROXY_PACKAGE_NAME) 113 .filter { 114 it.annotations.findAnnotation(AppFunctionSerializableProxyAnnotation.CLASS_NAME) != 115 null 116 } 117 .map { declaration -> 118 if (declaration !is KSClassDeclaration) { 119 throw ProcessingException( 120 "Only classes can be annotated with @AppFunctionSerializableProxy", 121 declaration 122 ) 123 } 124 AnnotatedAppFunctionSerializableProxy(declaration).validate() 125 } 126 .toList() 127 } 128 129 /** 130 * Gets all [AnnotatedAppFunctions] from all processed modules. 131 * 132 * Unlike [resolveAnnotatedAppFunctions] that resolves symbols from annotation within the same 133 * compilation unit. [getAnnotatedAppFunctionsFromAllModules] looks up all AppFunction symbols, 134 * including those are already processed. 135 */ 136 fun getAnnotatedAppFunctionsFromAllModules(): List<AnnotatedAppFunctions> { 137 return filterAppFunctionComponentQualifiedNames( 138 AppFunctionComponentRegistryAnnotation.Category.FUNCTION 139 ) 140 .map { componentName -> 141 val ksName = resolver.getKSNameFromString(componentName) 142 val functionDeclarations = resolver.getFunctionDeclarationsByName(ksName).toList() 143 if (functionDeclarations.isEmpty()) { 144 throw ProcessingException( 145 "Unable to find KSFunctionDeclaration for ${ksName.asString()}", 146 null 147 ) 148 } 149 if (functionDeclarations.size > 1) { 150 throw ProcessingException( 151 "Conflicts KSFunctionDeclaration for ${ksName.asString()}", 152 null 153 ) 154 } 155 functionDeclarations.single() 156 } 157 .groupBy { declaration -> 158 declaration.parentDeclaration as? KSClassDeclaration 159 ?: throw ProcessingException( 160 "Top level functions cannot be annotated with @AppFunction ", 161 declaration 162 ) 163 } 164 .map { (classDeclaration, appFunctionsDeclarations) -> 165 AnnotatedAppFunctions(classDeclaration, appFunctionsDeclarations).validate() 166 } 167 } 168 169 /** Gets generated AppFunctionInventory implementations. */ 170 fun getGeneratedAppFunctionInventories(): List<KSClassDeclaration> { 171 return filterAppFunctionComponentQualifiedNames( 172 AppFunctionComponentRegistryAnnotation.Category.INVENTORY 173 ) 174 .map { componentName -> 175 val ksName = resolver.getKSNameFromString(componentName) 176 resolver.getClassDeclarationByName(ksName) 177 ?: throw ProcessingException( 178 "Unable to find KSClassDeclaration for ${ksName.asString()}", 179 null 180 ) 181 } 182 } 183 184 /** Gets generated AppFunctionInvoker implementations. */ 185 fun getGeneratedAppFunctionInvokers(): List<KSClassDeclaration> { 186 return filterAppFunctionComponentQualifiedNames( 187 AppFunctionComponentRegistryAnnotation.Category.INVOKER 188 ) 189 .map { componentName -> 190 val ksName = resolver.getKSNameFromString(componentName) 191 resolver.getClassDeclarationByName(ksName) 192 ?: throw ProcessingException( 193 "Unable to find KSClassDeclaration for ${ksName.asString()}", 194 null 195 ) 196 } 197 } 198 199 @OptIn(KspExperimental::class) 200 private fun filterAppFunctionComponentQualifiedNames( 201 filterComponentCategory: String, 202 ): List<String> { 203 return resolver 204 .getDeclarationsFromPackage(APP_FUNCTIONS_AGGREGATED_DEPS_PACKAGE_NAME) 205 .flatMap { node -> 206 val registryAnnotation = 207 node.annotations.findAnnotation( 208 AppFunctionComponentRegistryAnnotation.CLASS_NAME 209 ) ?: return@flatMap emptyList<String>() 210 val componentCategory = 211 registryAnnotation.requirePropertyValueOfType( 212 AppFunctionComponentRegistryAnnotation.PROPERTY_COMPONENT_CATEGORY, 213 String::class 214 ) 215 val componentNames = 216 registryAnnotation.requirePropertyValueOfType( 217 AppFunctionComponentRegistryAnnotation.PROPERTY_COMPONENT_NAMES, 218 List::class 219 ) 220 return@flatMap if (componentCategory == filterComponentCategory) { 221 componentNames.filterIsInstance<String>() 222 } else { 223 emptyList<String>() 224 } 225 } 226 .toList() 227 } 228 } 229