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