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.processors 18 19 import androidx.appfunctions.compiler.AppFunctionCompiler 20 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies 21 import androidx.appfunctions.compiler.core.AnnotatedAppFunctions 22 import androidx.appfunctions.compiler.core.AppFunctionComponentRegistryGenerator 23 import androidx.appfunctions.compiler.core.AppFunctionComponentRegistryGenerator.AppFunctionComponent 24 import androidx.appfunctions.compiler.core.AppFunctionSymbolResolver 25 import androidx.appfunctions.compiler.core.IntrospectionHelper 26 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionComponentRegistryAnnotation 27 import androidx.appfunctions.compiler.core.ProcessingException 28 import androidx.appfunctions.metadata.AppFunctionAllOfTypeMetadata 29 import androidx.appfunctions.metadata.AppFunctionArrayTypeMetadata 30 import androidx.appfunctions.metadata.AppFunctionComponentsMetadata 31 import androidx.appfunctions.metadata.AppFunctionDataTypeMetadata 32 import androidx.appfunctions.metadata.AppFunctionObjectTypeMetadata 33 import androidx.appfunctions.metadata.AppFunctionParameterMetadata 34 import androidx.appfunctions.metadata.AppFunctionPrimitiveTypeMetadata 35 import androidx.appfunctions.metadata.AppFunctionReferenceTypeMetadata 36 import androidx.appfunctions.metadata.AppFunctionResponseMetadata 37 import androidx.appfunctions.metadata.AppFunctionSchemaMetadata 38 import androidx.appfunctions.metadata.CompileTimeAppFunctionMetadata 39 import com.google.devtools.ksp.KspExperimental 40 import com.google.devtools.ksp.processing.CodeGenerator 41 import com.google.devtools.ksp.processing.Dependencies 42 import com.google.devtools.ksp.processing.Resolver 43 import com.google.devtools.ksp.processing.SymbolProcessor 44 import com.google.devtools.ksp.symbol.KSAnnotated 45 import com.squareup.kotlinpoet.CodeBlock 46 import com.squareup.kotlinpoet.FileSpec 47 import com.squareup.kotlinpoet.KModifier 48 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 49 import com.squareup.kotlinpoet.PropertySpec 50 import com.squareup.kotlinpoet.TypeSpec 51 import com.squareup.kotlinpoet.asClassName 52 import com.squareup.kotlinpoet.buildCodeBlock 53 54 /** 55 * Generates implementations for the AppFunctionInventory interface. 56 * 57 * It resolves all functions in a class annotated with `@AppFunction`, and generates the 58 * corresponding metadata for those functions. 59 * 60 * **Important:** [androidx.appfunctions.compiler.processors.AppFunctionInventoryProcessor] will 61 * process exactly once for each compilation unit to generate a single registry for looking up all 62 * generated inventories within the compilation unit. 63 */ 64 class AppFunctionInventoryProcessor( 65 private val codeGenerator: CodeGenerator, 66 ) : SymbolProcessor { 67 68 private var hasProcessed = false 69 70 @OptIn(KspExperimental::class) processnull71 override fun process(resolver: Resolver): List<KSAnnotated> { 72 if (hasProcessed) return emptyList() 73 hasProcessed = true 74 75 val appFunctionSymbolResolver = AppFunctionSymbolResolver(resolver) 76 val appFunctionClasses = appFunctionSymbolResolver.resolveAnnotatedAppFunctions() 77 val resolvedAnnotatedSerializableProxies = 78 ResolvedAnnotatedSerializableProxies( 79 appFunctionSymbolResolver.resolveAllAnnotatedSerializableProxiesFromModule() 80 ) 81 val generatedInventoryComponents = 82 buildList<AppFunctionComponent> { 83 for (appFunctionClass in appFunctionClasses) { 84 val inventoryQualifiedName = 85 generateAppFunctionInventoryClass( 86 appFunctionClass, 87 resolvedAnnotatedSerializableProxies 88 ) 89 add( 90 AppFunctionComponent( 91 qualifiedName = inventoryQualifiedName, 92 sourceFiles = appFunctionClass.getSourceFiles(), 93 ) 94 ) 95 } 96 } 97 98 AppFunctionComponentRegistryGenerator(codeGenerator) 99 .generateRegistry( 100 resolver.getModuleName().asString(), 101 AppFunctionComponentRegistryAnnotation.Category.INVENTORY, 102 generatedInventoryComponents, 103 ) 104 return resolvedAnnotatedSerializableProxies.resolvedAnnotatedSerializableProxies.map { 105 it.appFunctionSerializableProxyClass 106 } 107 } 108 109 /** 110 * Generates an implementation of AppFunctionInventory for [appFunctionClass]. 111 * 112 * @return fully qualified name of the generated inventory implementation class. 113 */ generateAppFunctionInventoryClassnull114 private fun generateAppFunctionInventoryClass( 115 appFunctionClass: AnnotatedAppFunctions, 116 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 117 ): String { 118 val originalPackageName = appFunctionClass.classDeclaration.packageName.asString() 119 val originalClassName = appFunctionClass.classDeclaration.simpleName.asString() 120 121 val inventoryClassName = getAppFunctionInventoryClassName(originalClassName) 122 val inventoryClassBuilder = TypeSpec.classBuilder(inventoryClassName) 123 inventoryClassBuilder.addSuperinterface(IntrospectionHelper.APP_FUNCTION_INVENTORY_CLASS) 124 inventoryClassBuilder.addAnnotation(AppFunctionCompiler.GENERATED_ANNOTATION) 125 inventoryClassBuilder.addKdoc(buildSourceFilesKdoc(appFunctionClass)) 126 addFunctionMetadataProperties( 127 inventoryClassBuilder, 128 appFunctionClass, 129 resolvedAnnotatedSerializableProxies 130 ) 131 132 val fileSpec = 133 FileSpec.builder(originalPackageName, inventoryClassName) 134 .addType(inventoryClassBuilder.build()) 135 .build() 136 codeGenerator 137 .createNewFile( 138 Dependencies(aggregating = true, *appFunctionClass.getSourceFiles().toTypedArray()), 139 originalPackageName, 140 inventoryClassName 141 ) 142 .bufferedWriter() 143 .use { fileSpec.writeTo(it) } 144 145 return "${originalPackageName}.$inventoryClassName" 146 } 147 148 /** 149 * Adds properties to the `AppFunctionInventory` class for each function in the class. 150 * 151 * @param inventoryClassBuilder The builder for the `AppFunctionInventory` class. 152 * @param appFunctionClass The class annotated with `@AppFunction`. 153 * @param resolvedAnnotatedSerializableProxies The resolved annotated serializable proxies. 154 */ addFunctionMetadataPropertiesnull155 private fun addFunctionMetadataProperties( 156 inventoryClassBuilder: TypeSpec.Builder, 157 appFunctionClass: AnnotatedAppFunctions, 158 resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 159 ) { 160 val appFunctionMetadataList = 161 appFunctionClass.createAppFunctionMetadataList(resolvedAnnotatedSerializableProxies) 162 163 for (functionMetadata in appFunctionMetadataList) { 164 val functionMetadataObjectClassBuilder = 165 TypeSpec.objectBuilder(getFunctionMetadataObjectClassName(functionMetadata.id)) 166 .addModifiers(KModifier.PRIVATE) 167 addSchemaMetadataPropertyForFunction( 168 functionMetadataObjectClassBuilder, 169 functionMetadata.schema 170 ) 171 addPropertiesForParameterMetadataList( 172 functionMetadataObjectClassBuilder, 173 functionMetadata.parameters 174 ) 175 addPropertyForResponseMetadata( 176 functionMetadataObjectClassBuilder, 177 functionMetadata.response 178 ) 179 addPropertyForComponentsMetadata( 180 functionMetadataObjectClassBuilder, 181 functionMetadata.components 182 ) 183 addPropertyForAppFunctionMetadata(functionMetadataObjectClassBuilder, functionMetadata) 184 inventoryClassBuilder.addType(functionMetadataObjectClassBuilder.build()) 185 } 186 addFunctionIdToMetadataMapProperty(inventoryClassBuilder, appFunctionMetadataList) 187 } 188 addPropertyForAppFunctionMetadatanull189 private fun addPropertyForAppFunctionMetadata( 190 functionMetadataObjectClassBuilder: TypeSpec.Builder, 191 functionMetadata: CompileTimeAppFunctionMetadata 192 ) { 193 functionMetadataObjectClassBuilder.addProperty( 194 PropertySpec.builder( 195 APP_FUNCTION_METADATA_PROPERTY_NAME, 196 IntrospectionHelper.APP_FUNCTION_METADATA_CLASS 197 ) 198 .addModifiers(KModifier.PUBLIC) 199 .initializer( 200 buildCodeBlock { 201 addStatement( 202 """ 203 %T( 204 id = %S, 205 isEnabledByDefault = %L, 206 schema = %L, 207 parameters = %L, 208 response = %L, 209 components = %L 210 ) 211 """ 212 .trimIndent(), 213 IntrospectionHelper.APP_FUNCTION_METADATA_CLASS, 214 functionMetadata.id, 215 functionMetadata.isEnabledByDefault, 216 SCHEMA_METADATA_PROPERTY_NAME, 217 PARAMETER_METADATA_LIST_PROPERTY_NAME, 218 RESPONSE_METADATA_PROPERTY_NAME, 219 COMPONENT_METADATA_PROPERTY_NAME 220 ) 221 } 222 ) 223 .build() 224 ) 225 } 226 addPropertyForComponentsMetadatanull227 private fun addPropertyForComponentsMetadata( 228 functionMetadataObjectClassBuilder: TypeSpec.Builder, 229 appFunctionComponentsMetadata: AppFunctionComponentsMetadata 230 ) { 231 val componentDataTypesPropertyName = COMPONENT_METADATA_PROPERTY_NAME + "_DATA_TYPES_MAP" 232 addPropertyForComponentsDataTypes( 233 componentDataTypesPropertyName, 234 functionMetadataObjectClassBuilder, 235 appFunctionComponentsMetadata.dataTypes 236 ) 237 functionMetadataObjectClassBuilder.addProperty( 238 PropertySpec.builder( 239 COMPONENT_METADATA_PROPERTY_NAME, 240 IntrospectionHelper.APP_FUNCTION_COMPONENTS_METADATA_CLASS 241 ) 242 .addModifiers(KModifier.PRIVATE) 243 .initializer( 244 buildCodeBlock { 245 addStatement( 246 """ 247 %T( 248 dataTypes = %L 249 ) 250 """ 251 .trimIndent(), 252 IntrospectionHelper.APP_FUNCTION_COMPONENTS_METADATA_CLASS, 253 componentDataTypesPropertyName 254 ) 255 } 256 ) 257 .build() 258 ) 259 } 260 addPropertyForComponentsDataTypesnull261 private fun addPropertyForComponentsDataTypes( 262 propertyName: String, 263 functionMetadataObjectClassBuilder: TypeSpec.Builder, 264 dataTypes: Map<String, AppFunctionDataTypeMetadata> 265 ) { 266 functionMetadataObjectClassBuilder.addProperty( 267 PropertySpec.builder( 268 propertyName, 269 Map::class.asClassName() 270 .parameterizedBy( 271 String::class.asClassName(), 272 IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA 273 ), 274 ) 275 .addModifiers(KModifier.PRIVATE) 276 .initializer( 277 buildCodeBlock { 278 addStatement("mapOf(") 279 indent() 280 for ((componentReferenceKey, componentReferenceTypeMetadata) in dataTypes) { 281 val datatypeVariableName = 282 when (componentReferenceTypeMetadata) { 283 is AppFunctionObjectTypeMetadata -> { 284 val objectTypeMetadataPropertyName = 285 getObjectTypeMetadataPropertyNameForComponent( 286 componentReferenceKey 287 ) 288 addPropertyForObjectTypeMetadata( 289 objectTypeMetadataPropertyName, 290 functionMetadataObjectClassBuilder, 291 componentReferenceTypeMetadata 292 ) 293 objectTypeMetadataPropertyName 294 } 295 is AppFunctionAllOfTypeMetadata -> { 296 val allOfTypeMetadataPropertyName = 297 getAllOfTypeMetadataPropertyNameForComponent( 298 componentReferenceKey 299 ) 300 addPropertyForAllOfTypeMetadata( 301 allOfTypeMetadataPropertyName, 302 functionMetadataObjectClassBuilder, 303 componentReferenceTypeMetadata 304 ) 305 allOfTypeMetadataPropertyName 306 } 307 else -> { 308 // TODO provide KSNode to improve error message 309 throw ProcessingException( 310 "Component types contains unsupported datatype: " + 311 componentReferenceTypeMetadata, 312 null 313 ) 314 } 315 } 316 addStatement( 317 """ 318 %S to %L, 319 """ 320 .trimIndent(), 321 componentReferenceKey, 322 datatypeVariableName 323 ) 324 } 325 addStatement(")") 326 unindent() 327 } 328 ) 329 .build() 330 ) 331 } 332 addPropertyForResponseMetadatanull333 private fun addPropertyForResponseMetadata( 334 functionMetadataObjectClassBuilder: TypeSpec.Builder, 335 appFunctionResponseMetadata: AppFunctionResponseMetadata 336 ) { 337 val responseMetadataValueTypeName = 338 when (val castDataType = appFunctionResponseMetadata.valueType) { 339 is AppFunctionPrimitiveTypeMetadata -> { 340 val primitiveReturnTypeMetadataPropertyName = "PRIMITIVE_RESPONSE_VALUE_TYPE" 341 addPropertyForPrimitiveTypeMetadata( 342 primitiveReturnTypeMetadataPropertyName, 343 functionMetadataObjectClassBuilder, 344 castDataType 345 ) 346 primitiveReturnTypeMetadataPropertyName 347 } 348 is AppFunctionArrayTypeMetadata -> { 349 val arrayReturnTypeMetadataPropertyName = "ARRAY_RESPONSE_VALUE_TYPE" 350 addPropertyForArrayTypeMetadata( 351 arrayReturnTypeMetadataPropertyName, 352 functionMetadataObjectClassBuilder, 353 castDataType 354 ) 355 arrayReturnTypeMetadataPropertyName 356 } 357 is AppFunctionObjectTypeMetadata -> { 358 val objectReturnTypeMetadataPropertyName = "OBJECT_RESPONSE_VALUE_TYPE" 359 addPropertyForObjectTypeMetadata( 360 objectReturnTypeMetadataPropertyName, 361 functionMetadataObjectClassBuilder, 362 castDataType 363 ) 364 objectReturnTypeMetadataPropertyName 365 } 366 is AppFunctionReferenceTypeMetadata -> { 367 val referenceReturnTypeMetadataPropertyName = "REFERENCE_RESPONSE_VALUE_TYPE" 368 addPropertyForReferenceTypeMetadata( 369 referenceReturnTypeMetadataPropertyName, 370 functionMetadataObjectClassBuilder, 371 castDataType 372 ) 373 referenceReturnTypeMetadataPropertyName 374 } 375 else -> { 376 // TODO provide KSNode to improve error message 377 throw ProcessingException( 378 "Unable to build parameter metadata for unknown datatype: $castDataType", 379 null 380 ) 381 } 382 } 383 functionMetadataObjectClassBuilder.addProperty( 384 PropertySpec.builder( 385 RESPONSE_METADATA_PROPERTY_NAME, 386 IntrospectionHelper.APP_FUNCTION_RESPONSE_METADATA_CLASS 387 ) 388 .addModifiers(KModifier.PRIVATE) 389 .initializer( 390 buildCodeBlock { 391 addStatement( 392 """ 393 %T( 394 valueType = %L 395 ) 396 """ 397 .trimIndent(), 398 IntrospectionHelper.APP_FUNCTION_RESPONSE_METADATA_CLASS, 399 responseMetadataValueTypeName 400 ) 401 } 402 ) 403 .build() 404 ) 405 } 406 addPropertiesForParameterMetadataListnull407 private fun addPropertiesForParameterMetadataList( 408 functionMetadataObjectClassBuilder: TypeSpec.Builder, 409 parameterMetadataList: List<AppFunctionParameterMetadata> 410 ) { 411 functionMetadataObjectClassBuilder.addProperty( 412 PropertySpec.builder( 413 PARAMETER_METADATA_LIST_PROPERTY_NAME, 414 List::class.asClassName() 415 .parameterizedBy(IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS) 416 ) 417 .addModifiers(KModifier.PRIVATE) 418 .initializer( 419 buildCodeBlock { 420 addStatement("listOf(") 421 indent() 422 for (parameterMetadata in parameterMetadataList) { 423 addPropertiesForParameterMetadata( 424 parameterMetadata, 425 functionMetadataObjectClassBuilder 426 ) 427 addStatement( 428 "%L,", 429 "${parameterMetadata.name.uppercase()}_PARAMETER_METADATA" 430 ) 431 } 432 unindent() 433 addStatement(")") 434 } 435 ) 436 .build() 437 ) 438 } 439 addPropertiesForParameterMetadatanull440 private fun addPropertiesForParameterMetadata( 441 parameterMetadata: AppFunctionParameterMetadata, 442 functionMetadataObjectClassBuilder: TypeSpec.Builder, 443 ) { 444 val parameterMetadataPropertyName = 445 "${parameterMetadata.name.uppercase()}_PARAMETER_METADATA" 446 val datatypeVariableName = 447 when (val castDataType = parameterMetadata.dataType) { 448 is AppFunctionPrimitiveTypeMetadata -> { 449 val primitiveTypeMetadataPropertyName = 450 getPrimitiveTypeMetadataPropertyNameForParameter(parameterMetadata) 451 addPropertyForPrimitiveTypeMetadata( 452 primitiveTypeMetadataPropertyName, 453 functionMetadataObjectClassBuilder, 454 castDataType 455 ) 456 primitiveTypeMetadataPropertyName 457 } 458 is AppFunctionArrayTypeMetadata -> { 459 val arrayTypeMetadataPropertyName = 460 getArrayTypeMetadataPropertyNameForParameter(parameterMetadata) 461 addPropertyForArrayTypeMetadata( 462 arrayTypeMetadataPropertyName, 463 functionMetadataObjectClassBuilder, 464 castDataType 465 ) 466 arrayTypeMetadataPropertyName 467 } 468 is AppFunctionObjectTypeMetadata -> { 469 val objectTypeMetadataPropertyName = 470 getObjectTypeMetadataPropertyNameForParameter(parameterMetadata) 471 addPropertyForObjectTypeMetadata( 472 objectTypeMetadataPropertyName, 473 functionMetadataObjectClassBuilder, 474 castDataType 475 ) 476 objectTypeMetadataPropertyName 477 } 478 is AppFunctionReferenceTypeMetadata -> { 479 val referenceTypeMetadataPropertyName = 480 getReferenceTypeMetadataPropertyNameForParameter(parameterMetadata) 481 addPropertyForReferenceTypeMetadata( 482 referenceTypeMetadataPropertyName, 483 functionMetadataObjectClassBuilder, 484 castDataType 485 ) 486 referenceTypeMetadataPropertyName 487 } 488 else -> { 489 // TODO provide KSNode to improve error message 490 throw ProcessingException( 491 "Unable to build parameter metadata for unknown datatype: $castDataType", 492 null 493 ) 494 } 495 } 496 functionMetadataObjectClassBuilder.addProperty( 497 PropertySpec.builder( 498 parameterMetadataPropertyName, 499 IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS 500 ) 501 .addModifiers(KModifier.PRIVATE) 502 .initializer( 503 buildCodeBlock { 504 addStatement( 505 """ 506 %T( 507 name = %S, 508 isRequired = %L, 509 dataType = %L 510 ) 511 """ 512 .trimIndent(), 513 IntrospectionHelper.APP_FUNCTION_PARAMETER_METADATA_CLASS, 514 parameterMetadata.name, 515 parameterMetadata.isRequired, 516 datatypeVariableName 517 ) 518 } 519 ) 520 .build() 521 ) 522 } 523 addPropertyForPrimitiveTypeMetadatanull524 private fun addPropertyForPrimitiveTypeMetadata( 525 propertyName: String, 526 functionMetadataObjectClassBuilder: TypeSpec.Builder, 527 primitiveTypeMetadata: AppFunctionPrimitiveTypeMetadata 528 ) { 529 functionMetadataObjectClassBuilder.addProperty( 530 PropertySpec.builder( 531 propertyName, 532 IntrospectionHelper.APP_FUNCTION_PRIMITIVE_TYPE_METADATA_CLASS 533 ) 534 .addModifiers(KModifier.PRIVATE) 535 .initializer( 536 buildCodeBlock { 537 addStatement( 538 """ 539 %T( 540 type = %L, 541 isNullable = %L 542 ) 543 """ 544 .trimIndent(), 545 IntrospectionHelper.APP_FUNCTION_PRIMITIVE_TYPE_METADATA_CLASS, 546 primitiveTypeMetadata.type, 547 primitiveTypeMetadata.isNullable 548 ) 549 } 550 ) 551 .build() 552 ) 553 } 554 addPropertyForArrayTypeMetadatanull555 private fun addPropertyForArrayTypeMetadata( 556 propertyName: String, 557 functionMetadataObjectClassBuilder: TypeSpec.Builder, 558 arrayTypeMetadata: AppFunctionArrayTypeMetadata 559 ) { 560 val itemTypeVariableName = 561 when (val castItemType = arrayTypeMetadata.itemType) { 562 is AppFunctionPrimitiveTypeMetadata -> { 563 val primitiveItemTypeVariableName = propertyName + "_PRIMITIVE_ITEM_TYPE" 564 addPropertyForPrimitiveTypeMetadata( 565 primitiveItemTypeVariableName, 566 functionMetadataObjectClassBuilder, 567 castItemType 568 ) 569 primitiveItemTypeVariableName 570 } 571 is AppFunctionObjectTypeMetadata -> { 572 val objectItemTypeVariableName = propertyName + "_OBJECT_ITEM_TYPE" 573 addPropertyForObjectTypeMetadata( 574 objectItemTypeVariableName, 575 functionMetadataObjectClassBuilder, 576 castItemType 577 ) 578 objectItemTypeVariableName 579 } 580 is AppFunctionReferenceTypeMetadata -> { 581 val referenceItemTypeVariableName = propertyName + "_REFERENCE_ITEM_TYPE" 582 addPropertyForReferenceTypeMetadata( 583 referenceItemTypeVariableName, 584 functionMetadataObjectClassBuilder, 585 castItemType 586 ) 587 referenceItemTypeVariableName 588 } 589 else -> { 590 // TODO provide KSNode to improve error message 591 throw ProcessingException( 592 "Unable to build parameter item type metadata for unknown itemType: " + 593 "$castItemType", 594 null 595 ) 596 } 597 } 598 functionMetadataObjectClassBuilder.addProperty( 599 PropertySpec.builder( 600 propertyName, 601 IntrospectionHelper.APP_FUNCTION_ARRAY_TYPE_METADATA_CLASS 602 ) 603 .addModifiers(KModifier.PRIVATE) 604 .initializer( 605 buildCodeBlock { 606 addStatement( 607 """ 608 %T( 609 itemType = %L, 610 isNullable = %L 611 ) 612 """ 613 .trimIndent(), 614 IntrospectionHelper.APP_FUNCTION_ARRAY_TYPE_METADATA_CLASS, 615 itemTypeVariableName, 616 arrayTypeMetadata.isNullable 617 ) 618 } 619 ) 620 .build() 621 ) 622 } 623 addPropertyForReferenceTypeMetadatanull624 private fun addPropertyForReferenceTypeMetadata( 625 propertyName: String, 626 functionMetadataObjectClassBuilder: TypeSpec.Builder, 627 referenceTypeMetadata: AppFunctionReferenceTypeMetadata, 628 ) { 629 functionMetadataObjectClassBuilder.addProperty( 630 PropertySpec.builder( 631 propertyName, 632 IntrospectionHelper.APP_FUNCTION_REFERENCE_TYPE_METADATA_CLASS 633 ) 634 .addModifiers(KModifier.PRIVATE) 635 .initializer( 636 buildCodeBlock { 637 addStatement( 638 """ 639 %T( 640 referenceDataType = %S, 641 isNullable = %L 642 ) 643 """ 644 .trimIndent(), 645 IntrospectionHelper.APP_FUNCTION_REFERENCE_TYPE_METADATA_CLASS, 646 referenceTypeMetadata.referenceDataType, 647 referenceTypeMetadata.isNullable 648 ) 649 } 650 ) 651 .build() 652 ) 653 } 654 addPropertyForObjectTypeMetadatanull655 private fun addPropertyForObjectTypeMetadata( 656 propertyName: String, 657 functionMetadataObjectClassBuilder: TypeSpec.Builder, 658 objectTypeMetadata: AppFunctionObjectTypeMetadata, 659 ) { 660 val objectPropertiesMapPropertyName = propertyName + "_PROPERTIES_MAP" 661 addPropertyForObjectPropertiesMap( 662 objectPropertiesMapPropertyName, 663 functionMetadataObjectClassBuilder, 664 objectTypeMetadata.properties 665 ) 666 val requiredPropertiesListPropertyName = propertyName + "_REQUIRED_PROPERTIES_LIST" 667 addPropertyForListOfRequiredObjectProperties( 668 requiredPropertiesListPropertyName, 669 functionMetadataObjectClassBuilder, 670 objectTypeMetadata.required 671 ) 672 functionMetadataObjectClassBuilder.addProperty( 673 PropertySpec.builder( 674 propertyName, 675 IntrospectionHelper.APP_FUNCTION_OBJECT_TYPE_METADATA_CLASS 676 ) 677 .addModifiers(KModifier.PRIVATE) 678 .initializer( 679 buildCodeBlock { 680 addStatement( 681 """ 682 %T( 683 properties = %L, 684 required = %L, 685 qualifiedName = %S, 686 isNullable = %L 687 ) 688 """ 689 .trimIndent(), 690 IntrospectionHelper.APP_FUNCTION_OBJECT_TYPE_METADATA_CLASS, 691 objectPropertiesMapPropertyName, 692 requiredPropertiesListPropertyName, 693 objectTypeMetadata.qualifiedName, 694 objectTypeMetadata.isNullable, 695 ) 696 } 697 ) 698 .build() 699 ) 700 } 701 addPropertyForAllOfTypeMetadatanull702 private fun addPropertyForAllOfTypeMetadata( 703 propertyName: String, 704 functionMetadataObjectClassBuilder: TypeSpec.Builder, 705 allOfTypeMetadata: AppFunctionAllOfTypeMetadata, 706 ) { 707 val matchAllListPropertyName = propertyName + "_MATCH_ALL_LIST" 708 addPropertyForMatchAllList( 709 matchAllListPropertyName, 710 functionMetadataObjectClassBuilder, 711 allOfTypeMetadata.matchAll 712 ) 713 functionMetadataObjectClassBuilder.addProperty( 714 PropertySpec.builder( 715 propertyName, 716 IntrospectionHelper.APP_FUNCTION_ALL_OF_TYPE_METADATA_CLASS 717 ) 718 .addModifiers(KModifier.PRIVATE) 719 .initializer( 720 buildCodeBlock { 721 addStatement( 722 """ 723 %T( 724 matchAll = %L, 725 qualifiedName = %S, 726 isNullable = %L 727 ) 728 """ 729 .trimIndent(), 730 IntrospectionHelper.APP_FUNCTION_ALL_OF_TYPE_METADATA_CLASS, 731 matchAllListPropertyName, 732 allOfTypeMetadata.qualifiedName, 733 allOfTypeMetadata.isNullable 734 ) 735 } 736 ) 737 .build() 738 ) 739 } 740 addPropertyForListOfRequiredObjectPropertiesnull741 private fun addPropertyForListOfRequiredObjectProperties( 742 propertyName: String, 743 functionMetadataObjectClassBuilder: TypeSpec.Builder, 744 requiredProperties: List<String> 745 ) { 746 functionMetadataObjectClassBuilder.addProperty( 747 PropertySpec.builder( 748 propertyName, 749 List::class.asClassName().parameterizedBy(String::class.asClassName()) 750 ) 751 .addModifiers(KModifier.PRIVATE) 752 .initializer( 753 buildCodeBlock { 754 addStatement("listOf(") 755 indent() 756 for (requiredProperty in requiredProperties) { 757 addStatement("%S,", requiredProperty) 758 } 759 unindent() 760 addStatement(")") 761 } 762 ) 763 .build() 764 ) 765 } 766 addPropertyForObjectPropertiesMapnull767 private fun addPropertyForObjectPropertiesMap( 768 propertyName: String, 769 functionMetadataObjectClassBuilder: TypeSpec.Builder, 770 propertiesMap: Map<String, AppFunctionDataTypeMetadata>, 771 ) { 772 functionMetadataObjectClassBuilder.addProperty( 773 PropertySpec.builder( 774 propertyName, 775 Map::class.asClassName() 776 .parameterizedBy( 777 String::class.asClassName(), 778 IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA 779 ), 780 ) 781 .addModifiers(KModifier.PRIVATE) 782 .initializer( 783 buildCodeBlock { 784 addStatement("mapOf(") 785 indent() 786 for ((objectPropertyName, objectPropertyTypeMetadata) in propertiesMap) { 787 val dataTypeVariableName = 788 propertyName + "_${objectPropertyName.uppercase()}" 789 when (objectPropertyTypeMetadata) { 790 is AppFunctionPrimitiveTypeMetadata -> 791 addPropertyForPrimitiveTypeMetadata( 792 dataTypeVariableName, 793 functionMetadataObjectClassBuilder, 794 objectPropertyTypeMetadata 795 ) 796 is AppFunctionArrayTypeMetadata -> 797 addPropertyForArrayTypeMetadata( 798 dataTypeVariableName, 799 functionMetadataObjectClassBuilder, 800 objectPropertyTypeMetadata 801 ) 802 is AppFunctionObjectTypeMetadata -> 803 addPropertyForObjectTypeMetadata( 804 dataTypeVariableName, 805 functionMetadataObjectClassBuilder, 806 objectPropertyTypeMetadata 807 ) 808 is AppFunctionReferenceTypeMetadata -> 809 addPropertyForReferenceTypeMetadata( 810 dataTypeVariableName, 811 functionMetadataObjectClassBuilder, 812 objectPropertyTypeMetadata 813 ) 814 else -> { 815 // TODO provide KSNode to improve error message 816 throw ProcessingException( 817 "Unable to build metadata for unknown object property " + 818 "datatype: $objectPropertyTypeMetadata", 819 null 820 ) 821 } 822 } 823 addStatement( 824 """ 825 %S to %L, 826 """ 827 .trimIndent(), 828 objectPropertyName, 829 dataTypeVariableName 830 ) 831 } 832 unindent() 833 addStatement(")") 834 } 835 ) 836 .build() 837 ) 838 } 839 addPropertyForMatchAllListnull840 private fun addPropertyForMatchAllList( 841 propertyName: String, 842 functionMetadataObjectClassBuilder: TypeSpec.Builder, 843 matchAllList: List<AppFunctionDataTypeMetadata>, 844 ) { 845 functionMetadataObjectClassBuilder.addProperty( 846 PropertySpec.builder( 847 propertyName, 848 List::class.asClassName() 849 .parameterizedBy(IntrospectionHelper.APP_FUNCTION_DATA_TYPE_METADATA) 850 ) 851 .addModifiers(KModifier.PRIVATE) 852 .initializer( 853 buildCodeBlock { 854 addStatement("listOf(") 855 indent() 856 for ((index, dataTypeToMatch) in matchAllList.withIndex()) { 857 val dataTypeToMatchPropertyName = propertyName + "_ITEM_${index}" 858 addPropertyForDataTypeToMatch( 859 dataTypeToMatchPropertyName, 860 functionMetadataObjectClassBuilder, 861 dataTypeToMatch 862 ) 863 addStatement("%L,", dataTypeToMatchPropertyName) 864 } 865 unindent() 866 addStatement(")") 867 } 868 ) 869 .build() 870 ) 871 } 872 addPropertyForDataTypeToMatchnull873 private fun addPropertyForDataTypeToMatch( 874 propertyName: String, 875 functionMetadataObjectClassBuilder: TypeSpec.Builder, 876 dataTypeToMatch: AppFunctionDataTypeMetadata 877 ) { 878 when (dataTypeToMatch) { 879 is AppFunctionReferenceTypeMetadata -> 880 addPropertyForReferenceTypeMetadata( 881 propertyName, 882 functionMetadataObjectClassBuilder, 883 dataTypeToMatch 884 ) 885 is AppFunctionObjectTypeMetadata -> 886 addPropertyForObjectTypeMetadata( 887 propertyName, 888 functionMetadataObjectClassBuilder, 889 dataTypeToMatch 890 ) 891 else -> 892 // TODO provide KSNode to improve error message 893 throw ProcessingException( 894 "Invalid datatype metadata to match in allOf type. Only object and reference " + 895 "types are supported: $dataTypeToMatch", 896 null 897 ) 898 } 899 } 900 901 /** Creates the `functionIdToMetadataMap` property of the `AppFunctionInventory`. */ addFunctionIdToMetadataMapPropertynull902 private fun addFunctionIdToMetadataMapProperty( 903 inventoryClassBuilder: TypeSpec.Builder, 904 appFunctionMetadataList: List<CompileTimeAppFunctionMetadata> 905 ) { 906 inventoryClassBuilder.addProperty( 907 PropertySpec.builder( 908 FUNCTION_ID_TO_METADATA_MAP_PROPERTY_NAME, 909 Map::class.asClassName() 910 .parameterizedBy( 911 String::class.asClassName(), 912 IntrospectionHelper.APP_FUNCTION_METADATA_CLASS 913 ), 914 ) 915 .addModifiers(KModifier.OVERRIDE) 916 .initializer( 917 buildCodeBlock { 918 addStatement("mapOf(") 919 indent() 920 for (appFunctionMetadata in appFunctionMetadataList) { 921 addStatement( 922 """ 923 %S to %L.%L, 924 """ 925 .trimIndent(), 926 appFunctionMetadata.id, 927 getFunctionMetadataObjectClassName(appFunctionMetadata.id), 928 APP_FUNCTION_METADATA_PROPERTY_NAME 929 ) 930 } 931 unindent() 932 addStatement(")") 933 } 934 ) 935 .build() 936 ) 937 } 938 addSchemaMetadataPropertyForFunctionnull939 private fun addSchemaMetadataPropertyForFunction( 940 functionMetadataObjectClassBuilder: TypeSpec.Builder, 941 schemaMetadata: AppFunctionSchemaMetadata? 942 ) { 943 functionMetadataObjectClassBuilder.addProperty( 944 PropertySpec.builder( 945 SCHEMA_METADATA_PROPERTY_NAME, 946 IntrospectionHelper.APP_FUNCTION_SCHEMA_METADATA_CLASS.copy(nullable = true) 947 ) 948 .addModifiers(KModifier.PRIVATE) 949 .initializer( 950 buildCodeBlock { 951 if (schemaMetadata == null) { 952 addStatement("%L", null) 953 } else { 954 addStatement( 955 "%T(category= %S, name=%S, version=%L)", 956 IntrospectionHelper.APP_FUNCTION_SCHEMA_METADATA_CLASS, 957 schemaMetadata.category, 958 schemaMetadata.name, 959 schemaMetadata.version 960 ) 961 } 962 } 963 ) 964 .build() 965 ) 966 } 967 buildSourceFilesKdocnull968 private fun buildSourceFilesKdoc(appFunctionClass: AnnotatedAppFunctions): CodeBlock { 969 return buildCodeBlock { 970 addStatement("Source Files:") 971 for (file in appFunctionClass.getSourceFiles()) { 972 addStatement(file.fileName) 973 } 974 } 975 } 976 getAppFunctionInventoryClassNamenull977 private fun getAppFunctionInventoryClassName(functionClassName: String): String { 978 return "$%s_AppFunctionInventory".format(functionClassName) 979 } 980 981 /** 982 * Generates the name of the class for the metadata object of a function. 983 * 984 * @param functionId The ID of the function. 985 * @return The name of the class. 986 */ getFunctionMetadataObjectClassNamenull987 private fun getFunctionMetadataObjectClassName(functionId: String): String { 988 return functionId.replace("[^A-Za-z0-9]".toRegex(), "_").split("_").joinToString("") { 989 it.replaceFirstChar { it.uppercase() } 990 } + "MetadataObject" 991 } 992 993 /** 994 * Generates the name of the property for the primitive type metadata of a parameter. 995 * 996 * @param parameterMetadata The metadata of the parameter. 997 * @return The name of the property. 998 */ getPrimitiveTypeMetadataPropertyNameForParameternull999 private fun getPrimitiveTypeMetadataPropertyNameForParameter( 1000 parameterMetadata: AppFunctionParameterMetadata 1001 ): String { 1002 return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_PRIMITIVE_DATA_TYPE" 1003 } 1004 1005 /** 1006 * Generates the name of the property for the array type metadata of a parameter. 1007 * 1008 * @param parameterMetadata The metadata of the parameter. 1009 * @return The name of the property. 1010 */ getArrayTypeMetadataPropertyNameForParameternull1011 private fun getArrayTypeMetadataPropertyNameForParameter( 1012 parameterMetadata: AppFunctionParameterMetadata 1013 ): String { 1014 return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_ARRAY_DATA_TYPE" 1015 } 1016 1017 /** 1018 * Generates the name of the property for the object type metadata of a parameter. 1019 * 1020 * @param parameterMetadata The metadata of the parameter. 1021 * @return The name of the property. 1022 */ getObjectTypeMetadataPropertyNameForParameternull1023 private fun getObjectTypeMetadataPropertyNameForParameter( 1024 parameterMetadata: AppFunctionParameterMetadata 1025 ): String { 1026 return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_OBJECT_DATA_TYPE" 1027 } 1028 1029 /** 1030 * Generates the name of the property for the reference type metadata of a parameter. 1031 * 1032 * @param parameterMetadata The metadata of the parameter. 1033 * @return The name of the property. 1034 */ getReferenceTypeMetadataPropertyNameForParameternull1035 private fun getReferenceTypeMetadataPropertyNameForParameter( 1036 parameterMetadata: AppFunctionParameterMetadata 1037 ): String { 1038 return "PARAMETER_METADATA_${parameterMetadata.name.uppercase()}_REFERENCE_DATA_TYPE" 1039 } 1040 1041 /** 1042 * Generates the name of the property for the object type metadata of a component. 1043 * 1044 * @param componentName The name of the component. 1045 * @return The name of the property. 1046 */ getObjectTypeMetadataPropertyNameForComponentnull1047 private fun getObjectTypeMetadataPropertyNameForComponent(componentName: String): String { 1048 return "${componentName.uppercase().replace(Regex("[.<>]"), "_").replace("?", "_NULLABLE")}_OBJECT_DATA_TYPE" 1049 } 1050 1051 /** 1052 * Generates the name of the property for the all of type metadata of a component. 1053 * 1054 * @param componentName The name of the component. 1055 * @return The name of the property. 1056 */ getAllOfTypeMetadataPropertyNameForComponentnull1057 private fun getAllOfTypeMetadataPropertyNameForComponent(componentName: String): String { 1058 return "${componentName.uppercase().replace(".", "_")}_ALL_OF_DATA_TYPE" 1059 } 1060 1061 companion object { 1062 const val APP_FUNCTION_METADATA_PROPERTY_NAME = "APP_FUNCTION_METADATA" 1063 const val SCHEMA_METADATA_PROPERTY_NAME = "SCHEMA_METADATA" 1064 const val PARAMETER_METADATA_LIST_PROPERTY_NAME = "PARAMETER_METADATA_LIST" 1065 const val RESPONSE_METADATA_PROPERTY_NAME = "RESPONSE_METADATA" 1066 const val COMPONENT_METADATA_PROPERTY_NAME = "COMPONENTS_METADATA" 1067 const val FUNCTION_ID_TO_METADATA_MAP_PROPERTY_NAME = "functionIdToMetadataMap" 1068 } 1069 } 1070