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.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST 20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR 21 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR 22 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableAnnotation 23 import com.google.devtools.ksp.symbol.KSClassDeclaration 24 import com.google.devtools.ksp.symbol.KSFile 25 import com.google.devtools.ksp.symbol.KSFunctionDeclaration 26 import com.google.devtools.ksp.symbol.KSNode 27 import com.google.devtools.ksp.symbol.KSTypeArgument 28 import com.google.devtools.ksp.symbol.KSTypeParameter 29 import com.google.devtools.ksp.symbol.KSTypeReference 30 import com.google.devtools.ksp.symbol.Modifier 31 import com.squareup.kotlinpoet.ClassName 32 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 33 import com.squareup.kotlinpoet.TypeName 34 35 /** Represents a class annotated with [androidx.appfunctions.AppFunctionSerializable]. */ 36 open class AnnotatedAppFunctionSerializable( 37 private val appFunctionSerializableClass: KSClassDeclaration, 38 ) { 39 /** The qualified name of the class being annotated with AppFunctionSerializable. */ 40 open val qualifiedName: String by lazy { 41 appFunctionSerializableClass.toClassName().canonicalName 42 } 43 44 /** The super type of the class being annotated with AppFunctionSerializable */ 45 val superTypes: Sequence<KSTypeReference> by lazy { appFunctionSerializableClass.superTypes } 46 47 /** The modifier of the class being annotated with AppFunctionSerializable. */ 48 val modifiers: Set<Modifier> by lazy { appFunctionSerializableClass.modifiers } 49 50 /** The primary constructor if available. */ 51 val primaryConstructor: KSFunctionDeclaration? by lazy { 52 appFunctionSerializableClass.primaryConstructor 53 } 54 55 /** The [KSNode] to which the processing error is attributed. */ 56 val attributeNode: KSNode by lazy { appFunctionSerializableClass } 57 58 /** The list of [KSTypeParameter] of the AppFunctionSerializable */ 59 val typeParameters: List<KSTypeParameter> by lazy { 60 appFunctionSerializableClass.typeParameters 61 } 62 63 /** The original [ClassName] of the AppFunctionSerializable. */ 64 val originalClassName: ClassName by lazy { 65 ClassName( 66 appFunctionSerializableClass.packageName.asString(), 67 appFunctionSerializableClass.simpleName.asString() 68 ) 69 } 70 71 /** The [TypeName] of the AppFunctionSerializable. */ 72 val typeName: TypeName by lazy { 73 if (typeParameters.isEmpty()) { 74 originalClassName 75 } else { 76 originalClassName.parameterizedBy( 77 typeParameters.map(KSTypeParameter::toTypeVariableName) 78 ) 79 } 80 } 81 82 /** 83 * Parameterize [AnnotatedAppFunctionSerializable] with [arguments]. 84 * 85 * If [arguments] is empty, the original [AnnotatedAppFunctionSerializable] would be returned 86 * directly. 87 */ 88 fun parameterizedBy(arguments: List<KSTypeArgument>): AnnotatedAppFunctionSerializable { 89 if (arguments.isEmpty()) { 90 return this 91 } 92 return AnnotatedParameterizedAppFunctionSerializable( 93 appFunctionSerializableClass, 94 arguments 95 ) 96 } 97 98 // TODO(b/392587953): throw an error if a property has the same name as one of the factory 99 // method parameters 100 /** 101 * Validates that the class annotated with AppFunctionSerializable follows app function's spec. 102 * 103 * @throws ProcessingException if the class does not adhere to the requirements 104 */ 105 open fun validate(): AnnotatedAppFunctionSerializable { 106 val validateHelper = AppFunctionSerializableValidateHelper(this) 107 validateHelper.validatePrimaryConstructor() 108 validateHelper.validateParameters() 109 return this 110 } 111 112 /** 113 * Finds all super types of the serializable [appFunctionSerializableClass] that are annotated 114 * with the [androidx.appfunctions.AppFunctionSchemaCapability] annotation. 115 * 116 * For example, consider the following classes: 117 * ``` 118 * @AppFunctionSchemaCapability 119 * public interface AppFunctionOpenable { 120 * public val intentToOpen: PendingIntent 121 * } 122 * 123 * public interface OpenableResponse : AppFunctionOpenable { 124 * override val intentToOpen: PendingIntent 125 * } 126 * 127 * @AppFunctionSerializable 128 * class MySerializableClass( 129 * override val intentToOpen: PendingIntent 130 * ) : OpenableResponse 131 * ``` 132 * 133 * This method will return the [KSClassDeclaration] of `AppFunctionOpenable` since it is a super 134 * type of `MySerializableClass` and is annotated with the 135 * [androidx.appfunctions.AppFunctionSchemaCapability] annotation. 136 * 137 * @return a set of [KSClassDeclaration] for all super types of the 138 * [appFunctionSerializableClass] that are annotated with 139 * [androidx.appfunctions.AppFunctionSchemaCapability]. 140 */ 141 fun findSuperTypesWithCapabilityAnnotation(): Set<KSClassDeclaration> { 142 return buildSet { 143 val unvisitedSuperTypes: MutableList<KSTypeReference> = 144 appFunctionSerializableClass.superTypes.toMutableList() 145 146 while (!unvisitedSuperTypes.isEmpty()) { 147 val superTypeClassDeclaration = 148 unvisitedSuperTypes.removeLast().resolve().declaration as KSClassDeclaration 149 if ( 150 superTypeClassDeclaration.annotations.findAnnotation( 151 IntrospectionHelper.AppFunctionSchemaCapability.CLASS_NAME 152 ) != null 153 ) { 154 add(superTypeClassDeclaration) 155 } 156 if ( 157 superTypeClassDeclaration.annotations.findAnnotation( 158 IntrospectionHelper.AppFunctionSerializableAnnotation.CLASS_NAME 159 ) == null 160 ) { 161 // Only consider non serializable super types since serializable super types 162 // are already handled separately 163 unvisitedSuperTypes.addAll(superTypeClassDeclaration.superTypes) 164 } 165 } 166 } 167 } 168 169 /** 170 * Finds all super types of the serializable [appFunctionSerializableClass] that are annotated 171 * with the [androidx.appfunctions.AppFunctionSerializable] annotation. 172 * 173 * For example, consider the following classes: 174 * ``` 175 * @AppFunctionSerializable 176 * open class Address ( 177 * open val street: String, 178 * open val city: String, 179 * open val state: String, 180 * open val zipCode: String, 181 * ) 182 * 183 * @AppFunctionSerializable 184 * class MySerializableClass( 185 * override val street: String, 186 * override val city: String, 187 * override val state: String, 188 * override val zipCode: String, 189 * ) : Address 190 * ``` 191 * 192 * This method will return the [KSClassDeclaration] of `Address` since it is a super type of 193 * `MySerializableClass` and is annotated with the 194 * [androidx.appfunctions.AppFunctionSerializable] annotation. 195 * 196 * @return a set of [KSClassDeclaration] for all super types of the 197 * [appFunctionSerializableClass] that are annotated with 198 * [androidx.appfunctions.AppFunctionSerializable]. 199 */ 200 fun findSuperTypesWithSerializableAnnotation(): Set<KSClassDeclaration> { 201 return appFunctionSerializableClass.superTypes 202 .map { it.resolve().declaration as KSClassDeclaration } 203 .filter { 204 it.annotations.findAnnotation(AppFunctionSerializableAnnotation.CLASS_NAME) != null 205 } 206 .toSet() 207 } 208 209 /** Returns the annotated class's properties as defined in its primary constructor. */ 210 open fun getProperties(): List<AppFunctionPropertyDeclaration> { 211 return checkNotNull(appFunctionSerializableClass.primaryConstructor).parameters.map { 212 valueParameter -> 213 AppFunctionPropertyDeclaration(valueParameter) 214 } 215 } 216 217 /** Returns the properties that have @AppFunctionSerializable class types. */ 218 fun getSerializablePropertyTypeReferences(): Set<AppFunctionTypeReference> { 219 return getProperties() 220 .filterNot { it.isGenericType } 221 .map { property -> AppFunctionTypeReference(property.type) } 222 .filter { afType -> 223 afType.isOfTypeCategory(SERIALIZABLE_SINGULAR) || 224 afType.isOfTypeCategory(SERIALIZABLE_LIST) 225 } 226 .toSet() 227 } 228 229 /** Returns the properties that have @AppFunctionSerializableProxy class types. */ 230 fun getSerializableProxyPropertyTypeReferences(): Set<AppFunctionTypeReference> { 231 return getProperties() 232 .filterNot { it.isGenericType } 233 .map { it -> AppFunctionTypeReference(it.type) } 234 .filter { afType -> afType.isOfTypeCategory(SERIALIZABLE_PROXY_SINGULAR) } 235 .toSet() 236 } 237 238 /** 239 * Returns the set of source files that contain the definition of [appFunctionSerializableClass] 240 * and all @AppFunctionSerializable classes directly reachable through its fields. This method 241 * differs from [getTransitiveSerializableSourceFiles] by excluding transitively 242 * nested @AppFunctionSerializable classes. 243 */ 244 fun getSerializableSourceFiles(): Set<KSFile> { 245 val sourceFileSet: MutableSet<KSFile> = mutableSetOf() 246 appFunctionSerializableClass.containingFile?.let { sourceFileSet.add(it) } 247 for (serializableAfType in getSerializablePropertyTypeReferences()) { 248 val appFunctionSerializableDefinition = 249 serializableAfType.selfOrItemTypeReference.resolve().declaration 250 as KSClassDeclaration 251 appFunctionSerializableDefinition.containingFile?.let { sourceFileSet.add(it) } 252 } 253 return sourceFileSet 254 } 255 256 /** 257 * Returns the set of source files that contain the definition of [appFunctionSerializableClass] 258 * and all @AppFunctionSerializable classes transitively reachable through its fields or nested 259 * classes. 260 */ 261 fun getTransitiveSerializableSourceFiles(): Set<KSFile> { 262 val sourceFileSet: MutableSet<KSFile> = mutableSetOf() 263 val visitedSerializableSet: MutableSet<ClassName> = mutableSetOf() 264 265 // Add the file containing the AppFunctionSerializable class definition immediately it's 266 // seen 267 appFunctionSerializableClass.containingFile?.let { sourceFileSet.add(it) } 268 visitedSerializableSet.add(originalClassName) 269 traverseSerializableClassSourceFiles(sourceFileSet, visitedSerializableSet) 270 return sourceFileSet 271 } 272 273 private fun traverseSerializableClassSourceFiles( 274 sourceFileSet: MutableSet<KSFile>, 275 visitedSerializableSet: MutableSet<ClassName> 276 ) { 277 for (serializableAfType in getSerializablePropertyTypeReferences()) { 278 val appFunctionSerializableDefinition = 279 serializableAfType.selfOrItemTypeReference.resolve().declaration 280 as KSClassDeclaration 281 // Skip serializable that have been seen before 282 if (visitedSerializableSet.contains(originalClassName)) { 283 continue 284 } 285 // Process newly found serializable 286 sourceFileSet.addAll( 287 AnnotatedAppFunctionSerializable(appFunctionSerializableDefinition) 288 .parameterizedBy(serializableAfType.selfOrItemTypeReference.resolve().arguments) 289 .getTransitiveSerializableSourceFiles() 290 ) 291 } 292 } 293 } 294