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.core
18 
19 import com.google.devtools.ksp.symbol.KSClassDeclaration
20 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
21 import com.google.devtools.ksp.symbol.KSType
22 import com.google.devtools.ksp.symbol.KSTypeReference
23 import com.squareup.kotlinpoet.ClassName
24 
25 /**
26  * A class that represents a class annotated with @AppFunctionSerializableProxy.
27  *
28  * @param appFunctionSerializableProxyClass The class annotated with @AppFunctionSerializableProxy.
29  */
30 data class AnnotatedAppFunctionSerializableProxy(
31     val appFunctionSerializableProxyClass: KSClassDeclaration
32 ) : AnnotatedAppFunctionSerializable(appFunctionSerializableProxyClass) {
33 
34     /** The type of the class that the proxy class is proxying. */
<lambda>null35     val targetClassDeclaration: KSClassDeclaration by lazy {
36         (appFunctionSerializableProxyClass.annotations.findAnnotation(
37                 IntrospectionHelper.AppFunctionSerializableProxyAnnotation.CLASS_NAME
38             )
39                 ?: throw ProcessingException(
40                     "Class Must have @AppFunctionSerializableProxy annotation",
41                     appFunctionSerializableProxyClass
42                 ))
43             .requirePropertyValueOfType(
44                 IntrospectionHelper.AppFunctionSerializableProxyAnnotation.PROPERTY_TARGET_CLASS,
45                 KSType::class
46             )
47             .declaration as KSClassDeclaration
48     }
49 
50     /** The name of the method that returns an instance of the target class. */
<lambda>null51     val toTargetClassMethodName: String by lazy {
52         "to${targetClassDeclaration.simpleName.asString()}"
53     }
54 
55     /** The name of the companion method that returns an instance of the proxy class. */
<lambda>null56     val fromTargetClassMethodName: String by lazy {
57         "from${targetClassDeclaration.simpleName.asString()}"
58     }
59 
60     /** The type of the serializable reference. */
61     // TODO(b/403199251): Clean up hack.
<lambda>null62     val serializableReferenceType: KSTypeReference by lazy {
63         checkNotNull(
64             appFunctionSerializableProxyClass.declarations
65                 .filterIsInstance<KSClassDeclaration>()
66                 .filter { it.isCompanionObject }
67                 .single()
68                 .getAllFunctions()
69                 .filter { it.simpleName.asString() == fromTargetClassMethodName }
70                 .first()
71                 .returnType
72         )
73     }
74 
75     /**
76      * Validates the class annotated with @AppFunctionSerializableProxy.
77      *
78      * @return The validated class.
79      */
validatenull80     override fun validate(): AnnotatedAppFunctionSerializableProxy {
81         super.validate()
82         validateProxyHasToTargetClassMethod()
83         validateProxyHasFromTargetClassMethod()
84         return this
85     }
86 
87     /** Validates that the proxy class has a method that returns an instance of the target class. */
validateProxyHasToTargetClassMethodnull88     private fun validateProxyHasToTargetClassMethod() {
89         val toTargetClassNameFunctionList: List<KSFunctionDeclaration> =
90             appFunctionSerializableProxyClass
91                 .getAllFunctions()
92                 .filter { it.simpleName.asString() == toTargetClassMethodName }
93                 .toList()
94         if (toTargetClassNameFunctionList.size != 1) {
95             throw ProcessingException(
96                 "Class must have exactly one member function: $toTargetClassMethodName",
97                 appFunctionSerializableProxyClass
98             )
99         }
100         val toTargetClassNameFunction = toTargetClassNameFunctionList.first()
101         if (
102             checkNotNull(
103                     checkNotNull(toTargetClassNameFunction.returnType)
104                         .resolve()
105                         .declaration
106                         .qualifiedName
107                 )
108                 .asString() != checkNotNull(targetClassDeclaration.qualifiedName).asString()
109         ) {
110             throw ProcessingException(
111                 "Function $toTargetClassMethodName should return an instance of target class",
112                 appFunctionSerializableProxyClass
113             )
114         }
115     }
116 
117     /** Validates that the proxy class has a method that returns an instance of the target class. */
validateProxyHasFromTargetClassMethodnull118     private fun validateProxyHasFromTargetClassMethod() {
119         val targetClassName = checkNotNull(targetClassDeclaration.simpleName).asString()
120         val targetCompanionClass =
121             appFunctionSerializableProxyClass.declarations
122                 .filterIsInstance<KSClassDeclaration>()
123                 .filter { it.isCompanionObject }
124                 .single()
125 
126         val fromTargetClassNameFunctionList =
127             targetCompanionClass
128                 .getAllFunctions()
129                 .filter { it.simpleName.asString() == fromTargetClassMethodName }
130                 .toList()
131         if (fromTargetClassNameFunctionList.size != 1) {
132             throw ProcessingException(
133                 "Companion Class must have exactly one member function: " +
134                     fromTargetClassMethodName,
135                 appFunctionSerializableProxyClass
136             )
137         }
138         val fromTargetClassNameFunction = fromTargetClassNameFunctionList.first()
139         if (
140             fromTargetClassNameFunction.parameters.size != 1 ||
141                 fromTargetClassNameFunction.parameters.first().type.toTypeName().toString() !=
142                     checkNotNull(targetClassDeclaration.qualifiedName).asString()
143         ) {
144             throw ProcessingException(
145                 "Function $fromTargetClassMethodName should have one parameter of type " +
146                     targetClassName,
147                 appFunctionSerializableProxyClass
148             )
149         }
150         val returnTypeClassDeclaration =
151             checkNotNull(fromTargetClassNameFunction.returnType).resolve().declaration
152                 as KSClassDeclaration
153         if (
154             checkNotNull(returnTypeClassDeclaration.qualifiedName).asString() !=
155                 checkNotNull(appFunctionSerializableProxyClass.qualifiedName).asString()
156         ) {
157             throw ProcessingException(
158                 "Function $fromTargetClassMethodName should return an instance of " +
159                     "this serializable class (${checkNotNull(appFunctionSerializableProxyClass
160                         .qualifiedName).asString()}). Instead, it returns ${checkNotNull(
161                             returnTypeClassDeclaration.qualifiedName).asString()}",
162                 fromTargetClassNameFunction.returnType
163             )
164         }
165     }
166 
167     /**
168      * A class that represents a list of resolved AnnotatedAppFunctionSerializableProxy.
169      *
170      * @param resolvedAnnotatedSerializableProxies The list of resolved
171      *   AnnotatedAppFunctionSerializableProxy.
172      */
173     data class ResolvedAnnotatedSerializableProxies(
174         val resolvedAnnotatedSerializableProxies: List<AnnotatedAppFunctionSerializableProxy>
175     ) {
176         private val proxyTargetToSerializableProxy:
<lambda>null177             Map<ClassName, AnnotatedAppFunctionSerializableProxy> by lazy {
178             resolvedAnnotatedSerializableProxies.associateBy {
179                 it.targetClassDeclaration.toClassName()
180             }
181         }
182 
183         /**
184          * Returns the AnnotatedAppFunctionSerializableProxy for the given AppFunctionTypeReference.
185          *
186          * @param appFunctionTypeReference The AppFunctionTypeReference to get the
187          *   AnnotatedAppFunctionSerializableProxy for.
188          * @return The AnnotatedAppFunctionSerializableProxy for the given AppFunctionTypeReference.
189          */
getSerializableProxyForTypeReferencenull190         fun getSerializableProxyForTypeReference(
191             appFunctionTypeReference: AppFunctionTypeReference,
192         ): AnnotatedAppFunctionSerializableProxy {
193             val targetClassName =
194                 (appFunctionTypeReference.selfOrItemTypeReference.resolve().declaration
195                         as KSClassDeclaration)
196                     .toClassName()
197             return proxyTargetToSerializableProxy.getValue(targetClassName)
198         }
199     }
200 }
201