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