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.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies 20 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_ARRAY 21 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_LIST 22 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_SINGULAR 23 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST 24 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_LIST 25 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR 26 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR 27 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.SUPPORTED_TYPES_STRING 28 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.isSupportedType 29 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.Companion.toAppFunctionDatatype 30 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionAnnotation 31 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionContextClass 32 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSchemaDefinitionAnnotation 33 import androidx.appfunctions.metadata.AppFunctionAllOfTypeMetadata 34 import androidx.appfunctions.metadata.AppFunctionArrayTypeMetadata 35 import androidx.appfunctions.metadata.AppFunctionComponentsMetadata 36 import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata 37 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata 38 import androidx.appfunctions.metadata.AppFunctionParameterMetadata 39 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata 40 import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata 41 import androidx.appfunctions.metadata.AppFunctionResponseMetadata 42 import androidx.appfunctions.metadata.AppFunctionSchemaMetadata 43 import androidx.appfunctions.metadata.CompileTimeAppFunctionMetadata 44 import com.google.devtools.ksp.getDeclaredProperties 45 import com.google.devtools.ksp.symbol.KSAnnotated 46 import com.google.devtools.ksp.symbol.KSClassDeclaration 47 import com.google.devtools.ksp.symbol.KSFile 48 import com.google.devtools.ksp.symbol.KSFunctionDeclaration 49 import com.google.devtools.ksp.symbol.KSTypeReference 50 import com.google.devtools.ksp.validate 51 import com.squareup.kotlinpoet.ClassName 52 53 /** 54 * Represents a collection of functions within a specific class that are annotated as app functions. 55 */ 56 data class AnnotatedAppFunctions( 57 /** The [KSClassDeclaration] of the class that contains the annotated app functions. */ 58 val classDeclaration: KSClassDeclaration, 59 /** The list of [KSFunctionDeclaration] that are annotated as app function. */ 60 val appFunctionDeclarations: List<KSFunctionDeclaration>, 61 ) { 62 /** Gets all annotated nodes. */ 63 fun getAllAnnotated(): List<KSAnnotated> { 64 return buildList { 65 // Only functions are annotated. 66 for (appFunctionDeclaration in appFunctionDeclarations) { 67 add(appFunctionDeclaration) 68 } 69 } 70 } 71 72 /** 73 * Validates if the AppFunction implementation is valid. 74 * 75 * @throws SymbolNotReadyException if any related nodes are not ready for processing yet. 76 */ 77 fun validate(): AnnotatedAppFunctions { 78 if (!classDeclaration.validate()) { 79 throw SymbolNotReadyException( 80 "AppFunction enclosing class not ready for processing yet", 81 classDeclaration, 82 ) 83 } 84 for (appFunction in appFunctionDeclarations) { 85 if (!appFunction.validate()) { 86 throw SymbolNotReadyException( 87 "AppFunction method not ready for processing yet", 88 appFunction, 89 ) 90 } 91 } 92 validateFirstParameter() 93 validateParameterTypes() 94 return this 95 } 96 97 private fun validateFirstParameter() { 98 for (appFunctionDeclaration in appFunctionDeclarations) { 99 val firstParam = appFunctionDeclaration.parameters.firstOrNull() 100 if (firstParam == null) { 101 throw ProcessingException( 102 "The first parameter of an app function must be " + 103 "${AppFunctionContextClass.CLASS_NAME}", 104 appFunctionDeclaration, 105 ) 106 } 107 if (!firstParam.type.isOfType(AppFunctionContextClass.CLASS_NAME)) { 108 throw ProcessingException( 109 "The first parameter of an app function must be " + 110 "${AppFunctionContextClass.CLASS_NAME}", 111 firstParam, 112 ) 113 } 114 } 115 } 116 117 private fun validateParameterTypes() { 118 for (appFunctionDeclaration in appFunctionDeclarations) { 119 for ((paramIndex, ksValueParameter) in appFunctionDeclaration.parameters.withIndex()) { 120 if (paramIndex == 0) { 121 // Skip the first parameter which is always the `AppFunctionContext`. 122 continue 123 } 124 125 if (!isSupportedType(ksValueParameter.type)) { 126 throw ProcessingException( 127 "App function parameters must be a supported type, or a type " + 128 "annotated as @AppFunctionSerializable. See list of supported types:\n" + 129 "${ 130 SUPPORTED_TYPES_STRING 131 }\n" + 132 "but found ${ 133 AppFunctionTypeReference(ksValueParameter.type) 134 .selfOrItemTypeReference.ensureQualifiedTypeName() 135 .asString() 136 }", 137 ksValueParameter, 138 ) 139 } 140 } 141 } 142 } 143 144 /** 145 * Gets the identifier of an app functions. 146 * 147 * The format of the identifier is `packageName.className#methodName`. 148 */ 149 fun getAppFunctionIdentifier(functionDeclaration: KSFunctionDeclaration): String { 150 val packageName = classDeclaration.packageName.asString() 151 val className = classDeclaration.simpleName.asString() 152 val methodName = functionDeclaration.simpleName.asString() 153 return "${packageName}.${className}#${methodName}" 154 } 155 156 /** 157 * Returns the set of files that need to be processed to obtain the complete information about 158 * the app functions defined in this class. 159 * 160 * This includes the class file containing the function declarations, the class file containing 161 * the schema definitions, and the class files containing the AppFunctionSerializable classes 162 * used in the function parameters. 163 */ 164 fun getSourceFiles(): Set<KSFile> { 165 val sourceFileSet: MutableSet<KSFile> = mutableSetOf() 166 167 // Add the class file containing the function declarations 168 classDeclaration.containingFile?.let { sourceFileSet.add(it) } 169 170 for (functionDeclaration in appFunctionDeclarations) { 171 // Add the class file containing the schema definitions 172 val rootAppFunctionSchemaInterface = 173 findRootAppFunctionSchemaInterface(functionDeclaration) 174 rootAppFunctionSchemaInterface?.containingFile?.let { sourceFileSet.add(it) } 175 176 // Traverse each functions parameter to obtain the relevant AppFunctionSerializable 177 // class files 178 for ((paramIndex, ksValueParameter) in functionDeclaration.parameters.withIndex()) { 179 if (paramIndex == 0) { 180 // Skip the first parameter which is always the `AppFunctionContext`. 181 continue 182 } 183 val parameterTypeReference = AppFunctionTypeReference(ksValueParameter.type) 184 if (parameterTypeReference.typeOrItemTypeIsAppFunctionSerializable()) { 185 sourceFileSet.addAll( 186 getAnnotatedAppFunctionSerializable(parameterTypeReference) 187 .getTransitiveSerializableSourceFiles() 188 ) 189 } 190 } 191 192 val returnTypeReference = 193 AppFunctionTypeReference(checkNotNull(functionDeclaration.returnType)) 194 if (returnTypeReference.typeOrItemTypeIsAppFunctionSerializable()) { 195 sourceFileSet.addAll( 196 getAnnotatedAppFunctionSerializable(returnTypeReference) 197 .getTransitiveSerializableSourceFiles() 198 ) 199 } 200 } 201 return sourceFileSet 202 } 203 204 /** Gets the [classDeclaration]'s [ClassName]. */ 205 fun getEnclosingClassName(): ClassName { 206 return ClassName( 207 classDeclaration.packageName.asString(), 208 classDeclaration.simpleName.asString(), 209 ) 210 } 211 212 /** 213 * Creates a list of [CompileTimeAppFunctionMetadata]] instances for each of the app functions 214 * defined in this class. 215 */ 216 fun createAppFunctionMetadataList( 217 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 218 ): List<CompileTimeAppFunctionMetadata> { 219 return appFunctionDeclarations.map { functionDeclaration -> 220 // Defining the shared types locally for this iteration is to isolate the components 221 // used per function. This is done with the expectation that they can be globally 222 // merged without encountering mismatching datatype metadata for the same object key. 223 val sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata> = mutableMapOf() 224 val seenDataTypeQualifiers: MutableSet<String> = mutableSetOf() 225 226 val appFunctionAnnotationProperties = 227 computeAppFunctionAnnotationProperties(functionDeclaration) 228 val parameterTypeMetadataList = 229 functionDeclaration.buildParameterTypeMetadataList( 230 sharedDataTypeMap, 231 seenDataTypeQualifiers, 232 resolvedAnnotatedSerializableProxies 233 ) 234 val responseTypeMetadata = 235 checkNotNull(functionDeclaration.returnType) 236 .toAppFunctionDataTypeMetadata( 237 sharedDataTypeMap, 238 seenDataTypeQualifiers, 239 resolvedAnnotatedSerializableProxies 240 ) 241 242 CompileTimeAppFunctionMetadata( 243 id = getAppFunctionIdentifier(functionDeclaration), 244 isEnabledByDefault = appFunctionAnnotationProperties.isEnabledByDefault, 245 schema = appFunctionAnnotationProperties.toAppFunctionSchemaMetadata(), 246 parameters = parameterTypeMetadataList, 247 response = AppFunctionResponseMetadata(valueType = responseTypeMetadata), 248 components = AppFunctionComponentsMetadata(dataTypes = sharedDataTypeMap), 249 ) 250 } 251 } 252 253 /** Builds a list of [AppFunctionParameterMetadata] for the parameters of an app function. */ 254 private fun KSFunctionDeclaration.buildParameterTypeMetadataList( 255 sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>, 256 seenDataTypeQualifiers: MutableSet<String>, 257 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 258 ): List<AppFunctionParameterMetadata> = buildList { 259 for (ksValueParameter in parameters) { 260 if (ksValueParameter.type.isOfType(AppFunctionContextClass.CLASS_NAME)) { 261 // Skip the first parameter which is always the `AppFunctionContext`. 262 continue 263 } 264 265 val parameterName = checkNotNull(ksValueParameter.name).asString() 266 val dataTypeMetadata = 267 ksValueParameter.type.toAppFunctionDataTypeMetadata( 268 sharedDataTypeMap, 269 seenDataTypeQualifiers, 270 resolvedAnnotatedSerializableProxies 271 ) 272 273 add( 274 AppFunctionParameterMetadata( 275 name = parameterName, 276 // TODO(b/394553462): Parse required state from annotation. 277 isRequired = true, 278 dataType = dataTypeMetadata, 279 ) 280 ) 281 } 282 } 283 284 private fun KSTypeReference.toAppFunctionDataTypeMetadata( 285 sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>, 286 seenDataTypeQualifiers: MutableSet<String>, 287 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 288 ): AppFunctionDataTypeMetadata { 289 val appFunctionTypeReference = AppFunctionTypeReference(this) 290 return when (appFunctionTypeReference.typeCategory) { 291 PRIMITIVE_SINGULAR -> 292 AppFunctionPrimitiveTypeMetadata( 293 type = appFunctionTypeReference.toAppFunctionDataType(), 294 isNullable = appFunctionTypeReference.isNullable, 295 ) 296 PRIMITIVE_ARRAY -> 297 AppFunctionArrayTypeMetadata( 298 itemType = 299 AppFunctionPrimitiveTypeMetadata( 300 type = appFunctionTypeReference.determineArrayItemType(), 301 isNullable = false, 302 ), 303 isNullable = appFunctionTypeReference.isNullable, 304 ) 305 PRIMITIVE_LIST -> 306 AppFunctionArrayTypeMetadata( 307 itemType = 308 AppFunctionPrimitiveTypeMetadata( 309 type = appFunctionTypeReference.determineArrayItemType(), 310 isNullable = 311 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference) 312 .isNullable, 313 ), 314 isNullable = appFunctionTypeReference.isNullable, 315 ) 316 SERIALIZABLE_SINGULAR -> { 317 val annotatedAppFunctionSerializable = 318 getAnnotatedAppFunctionSerializable(appFunctionTypeReference) 319 addSerializableTypeMetadataToSharedDataTypeMap( 320 annotatedAppFunctionSerializable, 321 annotatedAppFunctionSerializable 322 .getProperties() 323 .associateBy { checkNotNull(it.name).toString() } 324 .toMutableMap(), 325 sharedDataTypeMap, 326 seenDataTypeQualifiers, 327 resolvedAnnotatedSerializableProxies 328 ) 329 AppFunctionReferenceTypeMetadata( 330 referenceDataType = 331 appFunctionTypeReference.selfTypeReference 332 .toTypeName() 333 .ignoreNullable() 334 .toString(), 335 isNullable = appFunctionTypeReference.isNullable, 336 ) 337 } 338 SERIALIZABLE_LIST -> { 339 val annotatedAppFunctionSerializable = 340 getAnnotatedAppFunctionSerializable(appFunctionTypeReference) 341 addSerializableTypeMetadataToSharedDataTypeMap( 342 annotatedAppFunctionSerializable, 343 annotatedAppFunctionSerializable 344 .getProperties() 345 .associateBy { checkNotNull(it.name).toString() } 346 .toMutableMap(), 347 sharedDataTypeMap, 348 seenDataTypeQualifiers, 349 resolvedAnnotatedSerializableProxies 350 ) 351 AppFunctionArrayTypeMetadata( 352 itemType = 353 AppFunctionReferenceTypeMetadata( 354 referenceDataType = 355 appFunctionTypeReference.itemTypeReference 356 .toTypeName() 357 .ignoreNullable() 358 .toString(), 359 isNullable = 360 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference) 361 .isNullable, 362 ), 363 isNullable = appFunctionTypeReference.isNullable, 364 ) 365 } 366 SERIALIZABLE_PROXY_SINGULAR -> { 367 val targetSerializableProxy = 368 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 369 appFunctionTypeReference 370 ) 371 addSerializableTypeMetadataToSharedDataTypeMap( 372 targetSerializableProxy, 373 targetSerializableProxy 374 .getProperties() 375 .associateBy { checkNotNull(it.name).toString() } 376 .toMutableMap(), 377 sharedDataTypeMap, 378 seenDataTypeQualifiers, 379 resolvedAnnotatedSerializableProxies 380 ) 381 AppFunctionReferenceTypeMetadata( 382 referenceDataType = 383 appFunctionTypeReference.selfTypeReference 384 .toTypeName() 385 .ignoreNullable() 386 .toString(), 387 isNullable = appFunctionTypeReference.isNullable, 388 ) 389 } 390 SERIALIZABLE_PROXY_LIST -> { 391 val targetSerializableProxy = 392 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 393 appFunctionTypeReference 394 ) 395 addSerializableTypeMetadataToSharedDataTypeMap( 396 targetSerializableProxy, 397 targetSerializableProxy 398 .getProperties() 399 .associateBy { checkNotNull(it.name).toString() } 400 .toMutableMap(), 401 sharedDataTypeMap, 402 seenDataTypeQualifiers, 403 resolvedAnnotatedSerializableProxies 404 ) 405 AppFunctionArrayTypeMetadata( 406 itemType = 407 AppFunctionReferenceTypeMetadata( 408 referenceDataType = 409 appFunctionTypeReference.itemTypeReference 410 .toTypeName() 411 .ignoreNullable() 412 .toString(), 413 isNullable = 414 AppFunctionTypeReference(appFunctionTypeReference.itemTypeReference) 415 .isNullable, 416 ), 417 isNullable = appFunctionTypeReference.isNullable, 418 ) 419 } 420 } 421 } 422 423 /** 424 * Adds the [AppFunctionDataTypeMetadata] for a serializable/capability type to the shared data 425 * type map. 426 * 427 * @param appFunctionSerializableType the [AnnotatedAppFunctionSerializable] for the 428 * serializable or capability type being processed. 429 * @param unvisitedSerializableProperties a map of unvisited serializable properties. This map 430 * is used to track the properties that have not yet been visited. The map is updated as the 431 * properties are visited. 432 * @param sharedDataTypeMap a map of shared data types. This map is used to store the 433 * [AppFunctionDataTypeMetadata] for all serializable/capability types that are used in an app 434 * function. This map is used to avoid duplicating the metadata for the same serializable 435 * type. 436 * @param seenDataTypeQualifiers a set of seen data type qualifiers. This set is used to avoid 437 * processing the same serializable type multiple times. 438 * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies. 439 */ 440 // TODO: Document traversal rules. 441 private fun addSerializableTypeMetadataToSharedDataTypeMap( 442 appFunctionSerializableType: AnnotatedAppFunctionSerializable, 443 unvisitedSerializableProperties: MutableMap<String, AppFunctionPropertyDeclaration>, 444 sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>, 445 seenDataTypeQualifiers: MutableSet<String>, 446 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 447 ) { 448 val serializableTypeQualifiedName = 449 if (appFunctionSerializableType is AnnotatedAppFunctionSerializableProxy) { 450 checkNotNull(appFunctionSerializableType.targetClassDeclaration.qualifiedName) 451 .asString() 452 } else { 453 appFunctionSerializableType.qualifiedName 454 } 455 // This type has already been added to the sharedDataMap. 456 if (seenDataTypeQualifiers.contains(serializableTypeQualifiedName)) { 457 return 458 } 459 seenDataTypeQualifiers.add(serializableTypeQualifiedName) 460 461 val superTypesWithSerializableAnnotation = 462 appFunctionSerializableType.findSuperTypesWithSerializableAnnotation() 463 val superTypesWithCapabilityAnnotation = 464 appFunctionSerializableType.findSuperTypesWithCapabilityAnnotation() 465 if ( 466 superTypesWithSerializableAnnotation.isEmpty() && 467 superTypesWithCapabilityAnnotation.isEmpty() 468 ) { 469 // If there is no super type, then this is a base serializable object. 470 sharedDataTypeMap.put( 471 serializableTypeQualifiedName, 472 buildObjectTypeMetadataForObjectParameters( 473 serializableTypeQualifiedName, 474 appFunctionSerializableType.getProperties(), 475 unvisitedSerializableProperties, 476 sharedDataTypeMap, 477 seenDataTypeQualifiers, 478 resolvedAnnotatedSerializableProxies 479 ) 480 ) 481 } else { 482 // If there are superTypes, we first need to build the list of superTypes for this 483 // serializable to match. 484 val matchAllSuperTypesList: List<AppFunctionDataTypeMetadata> = buildList { 485 for (serializableSuperType in superTypesWithSerializableAnnotation) { 486 addSerializableTypeMetadataToSharedDataTypeMap( 487 AnnotatedAppFunctionSerializable(serializableSuperType), 488 unvisitedSerializableProperties, 489 sharedDataTypeMap, 490 seenDataTypeQualifiers, 491 resolvedAnnotatedSerializableProxies 492 ) 493 add( 494 AppFunctionReferenceTypeMetadata( 495 referenceDataType = 496 checkNotNull(serializableSuperType.toClassName().canonicalName), 497 // Shared type should be the most permissive version (i.e. nullable) by 498 // default. This is because the outer AllOfType to this shared type 499 // can add further constraint (i.e. non-null) if required. 500 isNullable = true 501 ) 502 ) 503 } 504 505 for (capabilitySuperType in superTypesWithCapabilityAnnotation) { 506 add( 507 buildObjectTypeMetadataForObjectParameters( 508 checkNotNull(capabilitySuperType.toClassName().canonicalName), 509 capabilitySuperType 510 .getDeclaredProperties() 511 .map { AppFunctionPropertyDeclaration(it) } 512 .toList(), 513 unvisitedSerializableProperties, 514 sharedDataTypeMap, 515 seenDataTypeQualifiers, 516 resolvedAnnotatedSerializableProxies 517 ) 518 ) 519 } 520 521 if (unvisitedSerializableProperties.isNotEmpty()) { 522 // Since all superTypes have been visited, then the remaining parameters in the 523 // unvisitedSerializableParameters map belong to the subclass directly. 524 add( 525 buildObjectTypeMetadataForObjectParameters( 526 serializableTypeQualifiedName, 527 unvisitedSerializableProperties.values.toList(), 528 unvisitedSerializableProperties, 529 sharedDataTypeMap, 530 seenDataTypeQualifiers, 531 resolvedAnnotatedSerializableProxies 532 ) 533 ) 534 } 535 } 536 537 // Finally add allOf the datatypes required to build this composed objects to the 538 // components map 539 sharedDataTypeMap.put( 540 serializableTypeQualifiedName, 541 AppFunctionAllOfTypeMetadata( 542 qualifiedName = serializableTypeQualifiedName, 543 matchAll = matchAllSuperTypesList, 544 // Shared type should be the most permissive version (i.e. nullable) by 545 // default. This is because the outer ReferenceType to this shared type 546 // can add further constraint (i.e. non-null) if required. 547 isNullable = true 548 ) 549 ) 550 } 551 } 552 553 /** 554 * Builds an [AppFunctionObjectTypeMetadata] for a serializable/capability type. 555 * 556 * @param typeQualifiedName the qualified name of the serializable/capability type being 557 * processed. This is the qualified name of the class that is annotated with 558 * [androidx.appfunctions.AppFunctionSerializable] or 559 * [androidx.appfunctions.AppFunctionSchemaCapability]. 560 * @param currentPropertiesList the list of properties from the serializable/capability class 561 * that is being processed. 562 * @param unvisitedSerializableProperties a map of unvisited serializable properties. This map 563 * is used to track the properties that have not yet been visited. The map is updated as the 564 * properties are visited. The map should be a superset of the [currentPropertiesList] as it 565 * can contain other properties belonging to a subclass of the current [typeQualifiedName] 566 * class being processed. 567 * @param sharedDataTypeMap a map of shared data types. This map is used to store the 568 * [AppFunctionDataTypeMetadata] for all serializable types that are used in an app function. 569 * This map is used to avoid duplicating the metadata for the same serializable type. 570 * @param seenDataTypeQualifiers a set of seen data type qualifiers. This set is used to avoid 571 * processing the same serializable type multiple times. 572 * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies. 573 * @return an [AppFunctionObjectTypeMetadata] for the serializable type. 574 */ 575 private fun buildObjectTypeMetadataForObjectParameters( 576 typeQualifiedName: String, 577 currentPropertiesList: List<AppFunctionPropertyDeclaration>, 578 unvisitedSerializableProperties: MutableMap<String, AppFunctionPropertyDeclaration>, 579 sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>, 580 seenDataTypeQualifiers: MutableSet<String>, 581 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 582 ): AppFunctionObjectTypeMetadata { 583 val currentSerializableProperties: List<AppFunctionPropertyDeclaration> = buildList { 584 for (property in currentPropertiesList) { 585 // This property has now been visited. Remove it from the 586 // unvisitedSerializableProperties map so that we don't visit it again when 587 // processing the rest of a sub-class that implements this superclass. 588 // This is because before processing a subclass we process its superclass first 589 // so the unvisitedSerializableProperties could still contain properties not 590 // directly included in the current class being processed. 591 add(checkNotNull(unvisitedSerializableProperties.remove(property.name))) 592 } 593 } 594 return buildObjectTypeMetadataForObjectProperty( 595 typeQualifiedName, 596 currentSerializableProperties, 597 sharedDataTypeMap, 598 seenDataTypeQualifiers, 599 resolvedAnnotatedSerializableProxies 600 ) 601 } 602 603 private fun buildObjectTypeMetadataForObjectProperty( 604 serializableTypeQualifiedName: String, 605 currentSerializableProperties: List<AppFunctionPropertyDeclaration>, 606 sharedDataTypeMap: MutableMap<String, AppFunctionDataTypeMetadata>, 607 seenDataTypeQualifiers: MutableSet<String>, 608 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 609 ): AppFunctionObjectTypeMetadata { 610 val requiredPropertiesList: MutableList<String> = mutableListOf() 611 val appFunctionSerializablePropertiesMap: Map<String, AppFunctionDataTypeMetadata> = 612 buildMap { 613 for (property in currentSerializableProperties) { 614 val innerAppFunctionDataTypeMetadata = 615 property.type.toAppFunctionDataTypeMetadata( 616 sharedDataTypeMap, 617 seenDataTypeQualifiers, 618 resolvedAnnotatedSerializableProxies 619 ) 620 put(property.name, innerAppFunctionDataTypeMetadata) 621 // TODO(b/394553462): Parse required state from annotation. 622 requiredPropertiesList.add(property.name) 623 } 624 } 625 return AppFunctionObjectTypeMetadata( 626 properties = appFunctionSerializablePropertiesMap, 627 required = requiredPropertiesList, 628 qualifiedName = serializableTypeQualifiedName, 629 // Shared type should be the most permissive version (i.e. nullable) by default. 630 // This is because the outer ReferenceType to this shared type can add further 631 // constraint (i.e. non-null) if required. 632 isNullable = true, 633 ) 634 } 635 636 private fun AppFunctionTypeReference.toAppFunctionDataType(): Int { 637 return when (this.typeCategory) { 638 PRIMITIVE_SINGULAR -> selfTypeReference.toAppFunctionDatatype() 639 SERIALIZABLE_PROXY_SINGULAR, 640 SERIALIZABLE_SINGULAR -> AppFunctionObjectTypeMetadata.TYPE 641 PRIMITIVE_ARRAY, 642 PRIMITIVE_LIST, 643 SERIALIZABLE_PROXY_LIST, 644 SERIALIZABLE_LIST -> AppFunctionArrayTypeMetadata.TYPE 645 } 646 } 647 648 private fun AppFunctionTypeReference.determineArrayItemType(): Int { 649 return when (this.typeCategory) { 650 SERIALIZABLE_LIST -> AppFunctionObjectTypeMetadata.TYPE 651 PRIMITIVE_ARRAY -> selfTypeReference.toAppFunctionDatatype() 652 PRIMITIVE_LIST -> itemTypeReference.toAppFunctionDatatype() 653 SERIALIZABLE_PROXY_LIST -> itemTypeReference.toAppFunctionDatatype() 654 PRIMITIVE_SINGULAR, 655 SERIALIZABLE_PROXY_SINGULAR, 656 SERIALIZABLE_SINGULAR -> 657 throw ProcessingException( 658 "Not a supported array type " + 659 selfTypeReference.ensureQualifiedTypeName().asString(), 660 selfTypeReference, 661 ) 662 } 663 } 664 665 private fun computeAppFunctionAnnotationProperties( 666 functionDeclaration: KSFunctionDeclaration 667 ): AppFunctionAnnotationProperties { 668 val appFunctionAnnotation = 669 functionDeclaration.annotations.findAnnotation(AppFunctionAnnotation.CLASS_NAME) 670 ?: throw ProcessingException( 671 "Function not annotated with @AppFunction.", 672 functionDeclaration, 673 ) 674 val enabled = 675 appFunctionAnnotation.requirePropertyValueOfType( 676 AppFunctionAnnotation.PROPERTY_IS_ENABLED, 677 Boolean::class, 678 ) 679 680 val rootInterfaceWithAppFunctionSchemaDefinition = 681 findRootAppFunctionSchemaInterface(functionDeclaration) 682 683 val schemaFunctionAnnotation = 684 rootInterfaceWithAppFunctionSchemaDefinition 685 ?.annotations 686 ?.findAnnotation(AppFunctionSchemaDefinitionAnnotation.CLASS_NAME) 687 val schemaCategory = 688 schemaFunctionAnnotation?.requirePropertyValueOfType( 689 AppFunctionSchemaDefinitionAnnotation.PROPERTY_CATEGORY, 690 String::class, 691 ) 692 val schemaName = 693 schemaFunctionAnnotation?.requirePropertyValueOfType( 694 AppFunctionSchemaDefinitionAnnotation.PROPERTY_NAME, 695 String::class, 696 ) 697 val schemaVersion = 698 schemaFunctionAnnotation 699 ?.requirePropertyValueOfType( 700 AppFunctionSchemaDefinitionAnnotation.PROPERTY_VERSION, 701 Int::class, 702 ) 703 ?.toLong() 704 705 return AppFunctionAnnotationProperties(enabled, schemaName, schemaVersion, schemaCategory) 706 } 707 708 private fun findRootAppFunctionSchemaInterface( 709 function: KSFunctionDeclaration 710 ): KSClassDeclaration? { 711 val parentDeclaration = function.parentDeclaration as? KSClassDeclaration ?: return null 712 713 // Check if the enclosing class has the @AppFunctionSchemaDefinition 714 val annotation = 715 parentDeclaration.annotations.findAnnotation( 716 AppFunctionSchemaDefinitionAnnotation.CLASS_NAME 717 ) 718 if (annotation != null) { 719 return parentDeclaration 720 } 721 722 val superClassFunction = (function.findOverridee() as? KSFunctionDeclaration) ?: return null 723 return findRootAppFunctionSchemaInterface(superClassFunction) 724 } 725 726 private fun getAnnotatedAppFunctionSerializable( 727 appFunctionTypeReference: AppFunctionTypeReference 728 ): AnnotatedAppFunctionSerializable { 729 val appFunctionSerializableClassDeclaration = 730 appFunctionTypeReference.selfOrItemTypeReference.resolve().declaration 731 as KSClassDeclaration 732 return AnnotatedAppFunctionSerializable( 733 appFunctionSerializableClassDeclaration, 734 ) 735 .parameterizedBy(appFunctionTypeReference.selfOrItemTypeReference.resolve().arguments) 736 .validate() 737 } 738 739 private fun AppFunctionAnnotationProperties.toAppFunctionSchemaMetadata(): 740 AppFunctionSchemaMetadata? { 741 return if (this.schemaName != null) { 742 AppFunctionSchemaMetadata( 743 category = checkNotNull(this.schemaCategory), 744 name = this.schemaName, 745 version = checkNotNull(this.schemaVersion), 746 ) 747 } else { 748 null 749 } 750 } 751 752 private fun AppFunctionTypeReference.typeOrItemTypeIsAppFunctionSerializable(): Boolean { 753 return this.isOfTypeCategory(SERIALIZABLE_SINGULAR) || 754 this.isOfTypeCategory(SERIALIZABLE_LIST) 755 } 756 757 private data class AppFunctionAnnotationProperties( 758 val isEnabledByDefault: Boolean, 759 val schemaName: String?, 760 val schemaVersion: Long?, 761 val schemaCategory: String?, 762 ) 763 } 764