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.processors 18 19 import androidx.appfunctions.AppFunctionData 20 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializable 21 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy 22 import androidx.appfunctions.compiler.core.AnnotatedAppFunctionSerializableProxy.ResolvedAnnotatedSerializableProxies 23 import androidx.appfunctions.compiler.core.AnnotatedParameterizedAppFunctionSerializable 24 import androidx.appfunctions.compiler.core.AppFunctionPropertyDeclaration 25 import androidx.appfunctions.compiler.core.AppFunctionTypeReference 26 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_ARRAY 27 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_LIST 28 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.PRIMITIVE_SINGULAR 29 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_LIST 30 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_LIST 31 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_PROXY_SINGULAR 32 import androidx.appfunctions.compiler.core.AppFunctionTypeReference.AppFunctionSupportedTypeCategory.SERIALIZABLE_SINGULAR 33 import androidx.appfunctions.compiler.core.IntrospectionHelper 34 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod 35 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.FromAppFunctionDataMethod.APP_FUNCTION_DATA_PARAM_NAME 36 import androidx.appfunctions.compiler.core.IntrospectionHelper.AppFunctionSerializableFactoryClass.ToAppFunctionDataMethod.APP_FUNCTION_SERIALIZABLE_PARAM_NAME 37 import androidx.appfunctions.compiler.core.ProcessingException 38 import androidx.appfunctions.compiler.core.ensureQualifiedTypeName 39 import androidx.appfunctions.compiler.core.ignoreNullable 40 import androidx.appfunctions.compiler.core.isOfType 41 import androidx.appfunctions.compiler.core.toPascalCase 42 import androidx.appfunctions.compiler.core.toTypeName 43 import com.google.devtools.ksp.symbol.KSClassDeclaration 44 import com.google.devtools.ksp.symbol.KSTypeParameter 45 import com.google.devtools.ksp.symbol.KSTypeReference 46 import com.squareup.kotlinpoet.ClassName 47 import com.squareup.kotlinpoet.CodeBlock 48 import com.squareup.kotlinpoet.LIST 49 import com.squareup.kotlinpoet.buildCodeBlock 50 51 /** 52 * Wraps methods to build the [CodeBlock]s that make up the method bodies of the generated 53 * AppFunctionSerializableFactory. 54 */ 55 // TODO(b/392587953): extract common format maps 56 class AppFunctionSerializableFactoryCodeBuilder( 57 val annotatedClass: AnnotatedAppFunctionSerializable, 58 val resolvedAnnotatedSerializableProxies: ResolvedAnnotatedSerializableProxies 59 ) { 60 /** 61 * Generates the method body of fromAppFunctionData for a non proxy serializable. 62 * 63 * This method uses [appendFromAppFunctionDataMethodBodyCommon] to generate the common code for 64 * iterating through all the properties of a target serializable and extracting its 65 * corresponding value from an [AppFunctionData]. It then returns the serializable itself. 66 * 67 * For example, given the following non proxy serializable class: 68 * ``` 69 * @AppFunctionSerializable 70 * class SampleSerializable( 71 * val longParam: Long, 72 * val doubleParam: Double, 73 * ) 74 * ``` 75 * 76 * The generated `fromAppFunctionData` method would look like: 77 * ``` 78 * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : SampleSerializable { 79 * val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam")) 80 * val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam")) 81 * val resultSampleSerializable = SampleSerializable(longParam, doubleParam) 82 * return resultSampleSerializable 83 * } 84 * ``` 85 */ 86 fun appendFromAppFunctionDataMethodBody(): CodeBlock { 87 return buildCodeBlock { 88 val getterResultName = getResultParamName(annotatedClass) 89 add(appendFromAppFunctionDataMethodBodyCommon(getterResultName)) 90 addStatement( 91 """ 92 return %L 93 """ 94 .trimIndent(), 95 getterResultName 96 ) 97 } 98 } 99 100 /** 101 * Generates the method body of fromAppFunctionData for a proxy serializable. 102 * 103 * This method is similar to [appendFromAppFunctionDataMethodBody]. It uses 104 * [appendFromAppFunctionDataMethodBodyCommon] to generate the common code for iterating through 105 * all the properties of a target serializable and extracting its corresponding value from an 106 * [AppFunctionData]. However, It then returns a proxy serializable target class instead of the 107 * serializable itself. 108 * 109 * For example, given the following proxy serializable class: 110 * ``` 111 * @AppFunctionSerializableProxy(targetClass = LocalDateTime::class) 112 * class SampleSerializableProxy( 113 * val longParam: Long, 114 * val doubleParam: Double, 115 * ) { 116 * public fun toLocalDateTime(): LocalDateTime { 117 * return LocalDateTime.of(...) 118 * } 119 * 120 * public companion object { 121 * public fun fromLocalDateTime(localDateTIme: LocalDateTime) : SampleSerializableProxy 122 * { 123 * return SampleSerializableProxy(...) 124 * } 125 * } 126 * } 127 * ``` 128 * 129 * The generated `fromAppFunctionData` method would look like: 130 * ``` 131 * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : LocalDateTime { 132 * val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam")) 133 * val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam")) 134 * val resultSampleSerializableProxy = SampleSerializableProxy(longParam, doubleParam) 135 * return resultSampleSerializableProxy.toLocalDateTime() 136 * } 137 * ``` 138 */ 139 fun appendFromAppFunctionDataMethodBodyForProxy(): CodeBlock { 140 if (annotatedClass !is AnnotatedAppFunctionSerializableProxy) { 141 throw ProcessingException( 142 "Attempting to generate proxy getter for non proxy serializable.", 143 annotatedClass.attributeNode 144 ) 145 } 146 return buildCodeBlock { 147 val getterResultName = getResultParamName(annotatedClass) 148 add(appendFromAppFunctionDataMethodBodyCommon(getterResultName)) 149 addStatement( 150 """ 151 return %L.%L() 152 """ 153 .trimIndent(), 154 getterResultName, 155 annotatedClass.toTargetClassMethodName 156 ) 157 } 158 } 159 160 /** 161 * Generates common factory code for iterating through all the properties of a target 162 * serializable and extracting its corresponding value from an [AppFunctionData]. 163 * 164 * This function is used to build the `FromAppFunctionData` method of the generated 165 * AppFunctionSerializableFactory. 166 * 167 * For example, given the following serializable class: 168 * ``` 169 * @AppFunctionSerializable 170 * class SampleSerializable( 171 * val longParam: Long, 172 * val doubleParam: Double, 173 * ) 174 * ``` 175 * 176 * The generated `fromAppFunctionData` method would look like: 177 * ``` 178 * override fun fromAppFunctionData(appFunctionData: AppFunctionData) : SampleSerializable { 179 * val longParam = checkNotNull(appFunctionData.getLongOrNull("longParam")) 180 * val doubleParam = checkNotNull(appFunctionData.getDoubleOrNull("doubleParam")) 181 * val resultSampleSerializable = SampleSerializable(longParam, doubleParam) 182 * } 183 * ``` 184 * 185 * Note that this method does not actually populate the value to be returned. It will only 186 * handle extracting the relevant properties from the provided [AppFunctionData] to construct 187 * the relevant [androidx.appfunctions.AppFunctionSerializable] data class. The caller will 188 * append the actual return statement which could return the dataclass itself or a proxy target 189 * class. 190 */ 191 private fun appendFromAppFunctionDataMethodBodyCommon(getterResultName: String): CodeBlock { 192 return buildCodeBlock { 193 add(factoryInitStatements) 194 for ((paramName, paramType) in annotatedClass.getProperties()) { 195 val declaration = paramType.resolve().declaration 196 if (declaration is KSTypeParameter) { 197 appendGenericGetterStatement(paramName, declaration) 198 } else { 199 appendGetterStatement(paramName, paramType) 200 } 201 } 202 appendGetterResultConstructorCallStatement( 203 annotatedClass.originalClassName, 204 annotatedClass.getProperties(), 205 getterResultName 206 ) 207 add("\n") 208 } 209 } 210 211 /** 212 * Generates the method body of toAppFunctionData for a non proxy serializable. 213 * 214 * This method uses [appendToAppFunctionDataMethodBodyCommon] to generate the common code for 215 * iterating through all the properties of a target serializable and extracting its single 216 * property values. It then returns an [AppFunctionData] instance with the extracted values. 217 * 218 * For example, given the following non proxy serializable class: 219 * ``` 220 * @AppFunctionSerializable 221 * class SampleSerializable( 222 * val longParam: Long, 223 * val doubleParam: Double, 224 * ) 225 * ``` 226 * 227 * The generated `toAppFunctionData` method would look like: 228 * ``` 229 * override fun toAppFunctionData(appFunctionSerializable: SampleSerializable) : AppFunctionData { 230 * val sampleSerializable_appFunctionSerializable = appFunctionSerializable 231 * val longParam = sampleSerializable_appFunctionSerializable.longParam 232 * val doubleParam = sampleSerializable_appFunctionSerializable.doubleParam 233 * val builder = AppFunctionData.Builder("...") 234 * builder.setLong("longParam", longParam) 235 * builder.setDouble("doubleParam", doubleParam) 236 * return builder.build() 237 * } 238 * ``` 239 */ 240 fun appendToAppFunctionDataMethodBody(): CodeBlock { 241 return buildCodeBlock { 242 addStatement( 243 """ 244 val %L = %L 245 """ 246 .trimIndent(), 247 getSerializableParamName(annotatedClass), 248 APP_FUNCTION_SERIALIZABLE_PARAM_NAME 249 ) 250 add(appendToAppFunctionDataMethodBodyCommon()) 251 } 252 } 253 254 /** 255 * Generates the method body of toAppFunctionData for a proxy serializable. 256 * 257 * This method is similar to [appendToAppFunctionDataMethodBody]. It uses 258 * [appendToAppFunctionDataMethodBodyCommon] to generate the common code for iterating through 259 * all the properties of a target serializable and extracting its single property values. It 260 * then returns an [AppFunctionData] instance with the extracted values. 261 * 262 * The key difference from [appendToAppFunctionDataMethodBody] is the `toAppFunctionData` 263 * factory method accepts the target class instead of an AppFunctionSerializable type directly. 264 * The serializable type is obtained using the mandatory factory from the 265 * [AnnotatedAppFunctionSerializableProxy]. 266 * 267 * For example, given the following proxy serializable class: 268 * ``` 269 * @AppFunctionSerializableProxy(targetClass = LocalDateTime::class) 270 * class SampleSerializableProxy( 271 * val longParam: Long, 272 * val doubleParam: Double, 273 * ) { 274 * public fun toLocalDateTime(): LocalDateTime { 275 * return LocalDateTime.of(...) 276 * } 277 * 278 * public companion object { 279 * public fun fromLocalDateTime(localDateTIme: LocalDateTime) : SampleSerializableProxy 280 * { 281 * return SampleSerializableProxy(...) 282 * } 283 * } 284 * } 285 * ``` 286 * 287 * The generated `toAppFunctionData` method would look like: 288 * ``` 289 * override fun toAppFunctionData(appFunctionSerializable: LocalDateTime) : AppFunctionData { 290 * val localDateTime_appFunctionSerializable = 291 * SampleSerializableProxy.fromLocalDateTime(appFunctionSerializable) 292 * val longParam = localDateTime_appFunctionSerializable.longParam 293 * val doubleParam = localDateTime_appFunctionSerializable.doubleParam 294 * val builder = AppFunctionData.Builder("...") 295 * builder.setLong("longParam", longParam) 296 * builder.setDouble("doubleParam", doubleParam) 297 * return builder.build() 298 * } 299 * ``` 300 */ 301 fun appendToAppFunctionDataMethodBodyForProxy(): CodeBlock { 302 if (annotatedClass !is AnnotatedAppFunctionSerializableProxy) { 303 throw ProcessingException( 304 "Attempting to generate proxy setter for non proxy serializable.", 305 annotatedClass.attributeNode 306 ) 307 } 308 return buildCodeBlock { 309 addStatement( 310 """ 311 val %L = %T.%L(%L) 312 """ 313 .trimIndent(), 314 getSerializableParamName(annotatedClass), 315 annotatedClass.originalClassName, 316 annotatedClass.fromTargetClassMethodName, 317 APP_FUNCTION_SERIALIZABLE_PARAM_NAME 318 ) 319 add(appendToAppFunctionDataMethodBodyCommon()) 320 } 321 } 322 323 /** 324 * Generates common factory code for iterating through all the properties of an 325 * [androidx.appfunctions.AppFunctionSerializable] to populate an [AppFunctionData] instance. 326 * 327 * This function is used to build the `toAppFunctionData` method of the generated 328 * AppFunctionSerializableFactory. 329 * 330 * For example, given the following serializable class: 331 * ``` 332 * @AppFunctionSerializable 333 * class SampleSerializable( 334 * val longParam: Long, 335 * val doubleParam: Double, 336 * ) 337 * ``` 338 * 339 * The generated `toAppFunctionData` method would look like: 340 * ``` 341 * override fun toAppFunctionData(sampleSerializable: SampleSerializable) : AppFunctionData { 342 * val longParam = sampleSerializable.longParam 343 * val doubleParam = sampleSerializable.doubleParam 344 * val builder = AppFunctionData.Builder("androidx.appfunctions.compiler.processors.SampleSerializable") 345 * builder.setLong("longParam", longParam) 346 * builder.setDouble("doubleParam", doubleParam) 347 * return builder.build() 348 * } 349 * ``` 350 * 351 * Note that this method works directly with an [androidx.appfunctions.AppFunctionSerializable] 352 * class. In a case where the factory is for a proxy, the caller is expected to add the 353 * serializable representation of the proxy to the code block before calling this method. 354 */ 355 private fun appendToAppFunctionDataMethodBodyCommon(): CodeBlock { 356 return buildCodeBlock { 357 add(factoryInitStatements) 358 val qualifiedClassName = annotatedClass.qualifiedName 359 addStatement("val builder = %T(%S)", AppFunctionData.Builder::class, qualifiedClassName) 360 for (property in annotatedClass.getProperties()) { 361 val formatStringMap = 362 mapOf<String, Any>( 363 "param_name" to property.name, 364 "annotated_class_instance" to getSerializableParamName(annotatedClass) 365 ) 366 addNamed( 367 "val %param_name:L = %annotated_class_instance:L.%param_name:L\n", 368 formatStringMap 369 ) 370 val resolvedType = property.type.resolve() 371 val declaration = resolvedType.declaration 372 if (declaration is KSTypeParameter) { 373 appendGenericSetterStatement(property.name, declaration) 374 } else { 375 if (resolvedType.isMarkedNullable) { 376 appendNullableSetterStatement(property.name, property.type) 377 } else { 378 appendSetterStatement(property.name, property.type) 379 } 380 } 381 } 382 add("\nreturn builder.build()") 383 } 384 } 385 386 private fun CodeBlock.Builder.appendGenericGetterStatement( 387 paramName: String, 388 paramTypeParameter: KSTypeParameter 389 ): CodeBlock.Builder { 390 val formatStringMap = 391 mapOf<String, Any>( 392 "param_name" to paramName, 393 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME, 394 "type_parameter_property_name" to getTypeParameterPropertyName(paramTypeParameter), 395 "property_item_clazz_name" to 396 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 397 .ListTypeParameterClass 398 .PROPERTY_ITEM_CLAZZ_NAME, 399 "property_clazz_name" to 400 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 401 .PrimitiveTypeParameterClass 402 .PROPERTY_CLAZZ_NAME, 403 ) 404 addNamed("val %param_name:L = when (%type_parameter_property_name:L) {\n", formatStringMap) 405 indent() 406 add( 407 "is %T<*, *> -> {\n", 408 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 409 .ListTypeParameterClass 410 .CLASS_NAME 411 ) 412 indent() 413 addNamed( 414 "%app_function_data_param_name:L.getGenericListField(\"%param_name:L\", %type_parameter_property_name:L.%property_item_clazz_name:L)\n", 415 formatStringMap 416 ) 417 unindent() 418 add("}\n") 419 add( 420 "is %T -> {\n", 421 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 422 .PrimitiveTypeParameterClass 423 .CLASS_NAME 424 ) 425 indent() 426 addNamed( 427 "%app_function_data_param_name:L.getGenericField(\"%param_name:L\", %type_parameter_property_name:L.%property_clazz_name:L)\n", 428 formatStringMap 429 ) 430 unindent() 431 add("}\n") 432 unindent() 433 add("}\n") 434 return this 435 } 436 437 private fun CodeBlock.Builder.appendGetterStatement( 438 paramName: String, 439 paramType: KSTypeReference 440 ): CodeBlock.Builder { 441 val afType = AppFunctionTypeReference(paramType) 442 return when (afType.typeCategory) { 443 PRIMITIVE_SINGULAR, 444 PRIMITIVE_ARRAY, 445 PRIMITIVE_LIST -> appendPrimitiveGetterStatement(paramName, afType) 446 SERIALIZABLE_SINGULAR -> appendSerializableGetterStatement(paramName, afType) 447 SERIALIZABLE_LIST -> 448 appendSerializableListGetterStatement( 449 paramName, 450 afType, 451 afType.itemTypeReference.getTypeShortName() 452 ) 453 SERIALIZABLE_PROXY_SINGULAR -> { 454 val targetSerializableProxy = 455 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 456 afType 457 ) 458 appendSerializableGetterStatement( 459 paramName, 460 AppFunctionTypeReference(targetSerializableProxy.serializableReferenceType) 461 ) 462 } 463 SERIALIZABLE_PROXY_LIST -> { 464 val targetSerializableProxy = 465 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 466 afType 467 ) 468 appendSerializableListGetterStatement( 469 paramName, 470 afType, 471 targetSerializableProxy.serializableReferenceType.getTypeShortName() 472 ) 473 } 474 } 475 } 476 477 private fun CodeBlock.Builder.appendPrimitiveGetterStatement( 478 paramName: String, 479 afType: AppFunctionTypeReference 480 ): CodeBlock.Builder { 481 val formatStringMap = 482 mapOf<String, Any>( 483 "param_name" to paramName, 484 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME, 485 "getter_name" to getAppFunctionDataGetterName(afType), 486 "default_value_postfix" to getGetterDefaultValuePostfix(afType) 487 ) 488 if (afType.isNullable) { 489 addNamed( 490 "val %param_name:L = %app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L\n", 491 formatStringMap 492 ) 493 } else { 494 addNamed( 495 "val %param_name:L = checkNotNull(%app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L)\n", 496 formatStringMap 497 ) 498 } 499 return this 500 } 501 502 private fun CodeBlock.Builder.appendSerializableGetterStatement( 503 paramName: String, 504 afType: AppFunctionTypeReference 505 ): CodeBlock.Builder { 506 val annotatedSerializable = getAnnotatedSerializable(afType) 507 val formatStringMap = 508 mapOf<String, Any>( 509 "param_name" to paramName, 510 "param_type" to afType.selfTypeReference.toTypeName(), 511 "factory_name" to getSerializableFactoryVariableName(annotatedSerializable), 512 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME, 513 "getter_name" to getAppFunctionDataGetterName(afType), 514 "from_app_function_data_method_name" to FromAppFunctionDataMethod.METHOD_NAME, 515 "serializable_data_val_name" to "${paramName}Data" 516 ) 517 518 if (afType.isNullable) { 519 addNamed( 520 "val %serializable_data_val_name:L = %app_function_data_param_name:L.%getter_name:L(%param_name:S)\n", 521 formatStringMap 522 ) 523 return addNamed("var %param_name:L: %param_type:T = null\n", formatStringMap) 524 .addNamed("if (%serializable_data_val_name:L != null) {\n", formatStringMap) 525 .indent() 526 .addNamed( 527 "%param_name:L = %factory_name:L.%from_app_function_data_method_name:L(%serializable_data_val_name:L)\n", 528 formatStringMap 529 ) 530 .unindent() 531 .addStatement("}") 532 } else { 533 addNamed( 534 "val %serializable_data_val_name:L = checkNotNull(%app_function_data_param_name:L.%getter_name:L(%param_name:S))\n", 535 formatStringMap 536 ) 537 addNamed( 538 "val %param_name:L = %factory_name:L.%from_app_function_data_method_name:L(%serializable_data_val_name:L)\n", 539 formatStringMap 540 ) 541 } 542 return this 543 } 544 545 private fun CodeBlock.Builder.appendSerializableListGetterStatement( 546 paramName: String, 547 afType: AppFunctionTypeReference, 548 parametrizedItemTypeName: String 549 ): CodeBlock.Builder { 550 val factoryName = parametrizedItemTypeName + "Factory" 551 val factoryInstanceName = factoryName.lowerFirstChar() 552 val formatStringMap = 553 mapOf<String, Any>( 554 "param_name" to paramName, 555 "temp_list_name" to "${paramName}Data", 556 "app_function_data_param_name" to APP_FUNCTION_DATA_PARAM_NAME, 557 "factory_instance_name" to factoryInstanceName, 558 "getter_name" to getAppFunctionDataGetterName(afType), 559 "default_value_postfix" to getGetterDefaultValuePostfix(afType), 560 "null_safe_op" to if (afType.isNullable) "?" else "" 561 ) 562 563 addNamed( 564 "val %temp_list_name:L = %app_function_data_param_name:L.%getter_name:L(\"%param_name:L\")%default_value_postfix:L\n", 565 formatStringMap 566 ) 567 .addNamed( 568 "val %param_name:L = %temp_list_name:L%null_safe_op:L.map { data ->\n", 569 formatStringMap 570 ) 571 .indent() 572 .addNamed("%factory_instance_name:L.fromAppFunctionData(data)\n", formatStringMap) 573 .unindent() 574 .addStatement("}") 575 return this 576 } 577 578 private fun CodeBlock.Builder.appendGetterResultConstructorCallStatement( 579 originalClassName: ClassName, 580 properties: List<AppFunctionPropertyDeclaration>, 581 getterResultName: String 582 ): CodeBlock.Builder { 583 val formatStringMap = 584 mapOf<String, Any>( 585 "original_class_name" to originalClassName, 586 "params_list" to properties.joinToString(", ") { it.name }, 587 "getter_result_name" to getterResultName 588 ) 589 590 addNamed( 591 "\nval %getter_result_name:L = %original_class_name:T(%params_list:L)", 592 formatStringMap 593 ) 594 return this 595 } 596 597 private fun CodeBlock.Builder.appendGenericSetterStatement( 598 paramName: String, 599 paramTypeParameter: KSTypeParameter 600 ): CodeBlock.Builder { 601 val formatStringMap = 602 mapOf<String, Any>( 603 "param_name" to paramName, 604 "type_parameter_property_name" to getTypeParameterPropertyName(paramTypeParameter), 605 "property_item_clazz_name" to 606 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 607 .ListTypeParameterClass 608 .PROPERTY_ITEM_CLAZZ_NAME, 609 "property_clazz_name" to 610 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 611 .PrimitiveTypeParameterClass 612 .PROPERTY_CLAZZ_NAME, 613 ) 614 addNamed("when (%type_parameter_property_name:L) {\n", formatStringMap) 615 indent() 616 add( 617 "is %T<*, *> -> {\n", 618 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 619 .ListTypeParameterClass 620 .CLASS_NAME 621 ) 622 indent() 623 addNamed( 624 "builder.setGenericListField(\"%param_name:L\", %param_name:L as List<*>?, %type_parameter_property_name:L.%property_item_clazz_name:L)\n", 625 formatStringMap 626 ) 627 unindent() 628 add("}\n") 629 add( 630 "is %T -> {\n", 631 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 632 .PrimitiveTypeParameterClass 633 .CLASS_NAME 634 ) 635 indent() 636 addNamed( 637 "builder.setGenericField(\"%param_name:L\", %param_name:L, %type_parameter_property_name:L.%property_clazz_name:L)\n", 638 formatStringMap 639 ) 640 unindent() 641 add("}\n") 642 unindent() 643 add("}\n") 644 return this 645 } 646 647 private fun CodeBlock.Builder.appendNullableSetterStatement( 648 paramName: String, 649 typeReference: KSTypeReference, 650 ): CodeBlock.Builder { 651 val formatStringMap = 652 mapOf<String, Any>( 653 "param_name" to paramName, 654 ) 655 656 return addNamed("if (%param_name:L != null) {\n", formatStringMap) 657 .indent() 658 .appendSetterStatement(paramName, typeReference) 659 .unindent() 660 .addStatement("}") 661 } 662 663 private fun CodeBlock.Builder.appendSetterStatement( 664 paramName: String, 665 typeReference: KSTypeReference, 666 ): CodeBlock.Builder { 667 val afType = AppFunctionTypeReference(typeReference) 668 return when (afType.typeCategory) { 669 PRIMITIVE_SINGULAR, 670 PRIMITIVE_ARRAY, 671 PRIMITIVE_LIST -> appendPrimitiveSetterStatement(paramName, afType) 672 SERIALIZABLE_SINGULAR -> appendSerializableSetterStatement(paramName, afType) 673 SERIALIZABLE_LIST -> 674 appendSerializableListSetterStatement( 675 paramName, 676 afType, 677 afType.itemTypeReference.getTypeShortName() 678 ) 679 SERIALIZABLE_PROXY_SINGULAR -> { 680 val targetSerializableProxy = 681 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 682 afType 683 ) 684 appendSerializableSetterStatement( 685 paramName, 686 AppFunctionTypeReference(targetSerializableProxy.serializableReferenceType) 687 ) 688 } 689 SERIALIZABLE_PROXY_LIST -> { 690 val targetSerializableProxy = 691 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 692 afType 693 ) 694 appendSerializableListSetterStatement( 695 paramName, 696 afType, 697 targetSerializableProxy.serializableReferenceType.getTypeShortName() 698 ) 699 } 700 } 701 } 702 703 private fun CodeBlock.Builder.appendPrimitiveSetterStatement( 704 paramName: String, 705 afType: AppFunctionTypeReference 706 ): CodeBlock.Builder { 707 val formatStringMap = 708 mapOf<String, Any>( 709 "param_name" to paramName, 710 "setter_name" to getAppFunctionDataSetterName(afType), 711 ) 712 addNamed("builder.%setter_name:L(\"%param_name:L\", %param_name:L)\n", formatStringMap) 713 return this 714 } 715 716 private fun CodeBlock.Builder.appendSerializableSetterStatement( 717 paramName: String, 718 afType: AppFunctionTypeReference 719 ): CodeBlock.Builder { 720 val annotatedSerializable = getAnnotatedSerializable(afType) 721 val formatStringMap = 722 mapOf<String, Any>( 723 "param_name" to paramName, 724 "factory_name" to getSerializableFactoryVariableName(annotatedSerializable), 725 "setter_name" to getAppFunctionDataSetterName(afType), 726 ) 727 728 addNamed( 729 "builder.%setter_name:L(\"%param_name:L\", %factory_name:L.toAppFunctionData(%param_name:L))\n", 730 formatStringMap 731 ) 732 return this 733 } 734 735 private fun CodeBlock.Builder.appendSerializableListSetterStatement( 736 paramName: String, 737 afType: AppFunctionTypeReference, 738 parametrizedItemTypeName: String 739 ): CodeBlock.Builder { 740 741 val formatStringMap = 742 mapOf<String, Any>( 743 "param_name" to paramName, 744 "factory_name" to "${parametrizedItemTypeName}Factory".lowerFirstChar(), 745 "setter_name" to getAppFunctionDataSetterName(afType), 746 "lambda_param_name" to parametrizedItemTypeName.lowerFirstChar() 747 ) 748 749 addNamed( 750 "builder.%setter_name:L(\"%param_name:L\", " + 751 "%param_name:L" + 752 ".map{ %lambda_param_name:L ->\n", 753 formatStringMap 754 ) 755 .indent() 756 .addNamed("%factory_name:L.toAppFunctionData(%lambda_param_name:L)\n", formatStringMap) 757 .unindent() 758 .addStatement("})") 759 return this 760 } 761 762 private fun getAppFunctionDataGetterName(afType: AppFunctionTypeReference): String { 763 val shortTypeName = afType.selfOrItemTypeReference.getTypeShortName() 764 return when (afType.typeCategory) { 765 PRIMITIVE_SINGULAR -> "get${shortTypeName}OrNull" 766 PRIMITIVE_ARRAY -> "get$shortTypeName" 767 SERIALIZABLE_PROXY_SINGULAR, 768 SERIALIZABLE_SINGULAR -> "getAppFunctionData" 769 SERIALIZABLE_PROXY_LIST, 770 SERIALIZABLE_LIST -> "getAppFunctionDataList" 771 PRIMITIVE_LIST -> "get${shortTypeName}List" 772 } 773 } 774 775 // Missing list/array types default to an empty list/array; missing singular properties throw an 776 // error; all nullable properties default to null. 777 private fun getGetterDefaultValuePostfix(afType: AppFunctionTypeReference): String { 778 return when (afType.typeCategory) { 779 PRIMITIVE_SINGULAR, 780 SERIALIZABLE_PROXY_SINGULAR, 781 SERIALIZABLE_SINGULAR -> "" 782 PRIMITIVE_ARRAY -> 783 if (afType.isNullable) { 784 "" 785 } else { 786 " ?: ${afType.selfOrItemTypeReference.getTypeShortName()}(0)" 787 } 788 PRIMITIVE_LIST, 789 SERIALIZABLE_PROXY_LIST, 790 SERIALIZABLE_LIST -> if (afType.isNullable) "" else " ?: emptyList()" 791 } 792 } 793 794 private fun getAppFunctionDataSetterName(afType: AppFunctionTypeReference): String { 795 return when (afType.typeCategory) { 796 PRIMITIVE_SINGULAR, 797 PRIMITIVE_ARRAY -> "set${afType.selfOrItemTypeReference.getTypeShortName()}" 798 PRIMITIVE_LIST -> "set${afType.selfOrItemTypeReference.getTypeShortName()}List" 799 SERIALIZABLE_SINGULAR, 800 SERIALIZABLE_PROXY_SINGULAR -> "setAppFunctionData" 801 SERIALIZABLE_PROXY_LIST, 802 SERIALIZABLE_LIST -> "setAppFunctionDataList" 803 } 804 } 805 806 private val factoryInitStatements = buildCodeBlock { 807 val factoryInstanceNameToAnnotatedClassMap: Map<String, AnnotatedAppFunctionSerializable> = 808 buildMap { 809 for (serializableTypeReference in 810 annotatedClass.getSerializablePropertyTypeReferences()) { 811 val annotatedSerializable = getAnnotatedSerializable(serializableTypeReference) 812 put( 813 getSerializableFactoryVariableName(annotatedSerializable), 814 annotatedSerializable, 815 ) 816 } 817 818 for (proxyTypeReference in 819 annotatedClass.getSerializableProxyPropertyTypeReferences()) { 820 val targetSerializableProxy = 821 resolvedAnnotatedSerializableProxies.getSerializableProxyForTypeReference( 822 proxyTypeReference 823 ) 824 put( 825 getSerializableFactoryVariableName(targetSerializableProxy), 826 targetSerializableProxy 827 ) 828 } 829 } 830 for ((paramName, annotatedSerializable) in factoryInstanceNameToAnnotatedClassMap) { 831 when (annotatedSerializable) { 832 is AnnotatedAppFunctionSerializableProxy -> { 833 addStatement( 834 "val %L = %T()", 835 paramName, 836 ClassName( 837 annotatedSerializable.originalClassName.packageName, 838 "$${annotatedSerializable.targetClassDeclaration.simpleName.asString()}Factory" 839 ) 840 ) 841 } 842 is AnnotatedParameterizedAppFunctionSerializable -> { 843 addParameterizedFactoryInitStatement(paramName, annotatedSerializable) 844 } 845 else -> { 846 addStatement( 847 "val %L = %T()", 848 paramName, 849 ClassName( 850 annotatedSerializable.originalClassName.packageName, 851 "$${annotatedSerializable.originalClassName.simpleName}Factory" 852 ) 853 ) 854 } 855 } 856 } 857 add("\n") 858 } 859 860 /** 861 * Adds an Serializable factory initialize statement for [annotatedSerializable] 862 * 863 * For example, if a serializable has a parameterized parameters `val title: SetField<String?>`, 864 * it would add a statement of 865 * 866 * ``` 867 * val setFieldStringNullableFactory = `$SetFieldFactory`<String?>`( 868 * TypeParameter.PrimitiveTypeParameter(String::class.java as Class<String?>) 869 * ) 870 * ``` 871 */ 872 private fun CodeBlock.Builder.addParameterizedFactoryInitStatement( 873 paramName: String, 874 annotatedSerializable: AnnotatedParameterizedAppFunctionSerializable, 875 ) { 876 add( 877 "val %L = %T", 878 paramName, 879 ClassName( 880 annotatedSerializable.originalClassName.packageName, 881 "$${annotatedSerializable.originalClassName.simpleName}Factory" 882 ) 883 ) 884 add("<") 885 for ((index, typeArgumentReference) in 886 annotatedSerializable.typeParameterMap.values.withIndex()) { 887 add("%T", typeArgumentReference.toTypeName()) 888 if (index != annotatedSerializable.typeParameterMap.size - 1) { 889 add(",") 890 } 891 } 892 addStatement(">(") 893 indent() 894 for (typeArgumentReference in annotatedSerializable.typeParameterMap.values) { 895 val typeArgument = typeArgumentReference.resolve() 896 val typeParameterTypeName = 897 if (typeArgumentReference.isOfType(LIST)) { 898 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 899 .ListTypeParameterClass 900 .CLASS_NAME 901 } else { 902 IntrospectionHelper.AppFunctionSerializableFactoryClass.TypeParameterClass 903 .PrimitiveTypeParameterClass 904 .CLASS_NAME 905 } 906 val typeParameterArg = 907 if (typeArgumentReference.isOfType(LIST)) { 908 checkNotNull(typeArgument.arguments.first().type).toTypeName().ignoreNullable() 909 } else { 910 typeArgumentReference.toTypeName().ignoreNullable() 911 } 912 913 if (typeArgument.isMarkedNullable) { 914 addStatement("@Suppress(\"UNCHECKED_CAST\")") 915 addStatement( 916 "%1T(%2T::class.java as Class<%3T>),", 917 typeParameterTypeName, 918 typeParameterArg, 919 typeArgumentReference.toTypeName(), 920 ) 921 } else { 922 addStatement( 923 "%1T(%2T::class.java),", 924 typeParameterTypeName, 925 typeParameterArg, 926 ) 927 } 928 } 929 unindent() 930 addStatement(")") 931 } 932 933 private fun KSTypeReference.getTypeShortName(): String { 934 return this.ensureQualifiedTypeName().getShortName() 935 } 936 937 private fun String.lowerFirstChar(): String { 938 return replaceFirstChar { it -> it.lowercase() } 939 } 940 941 private fun getResultParamName(annotatedClass: AnnotatedAppFunctionSerializable): String { 942 return "result${annotatedClass.originalClassName.simpleName}" 943 } 944 945 private fun getSerializableParamName(annotatedClass: AnnotatedAppFunctionSerializable): String { 946 return "${annotatedClass.originalClassName.simpleName.replaceFirstChar { 947 it -> it.lowercase() }}_appFunctionSerializable" 948 } 949 950 private fun getAnnotatedSerializable( 951 typeReference: AppFunctionTypeReference 952 ): AnnotatedAppFunctionSerializable { 953 val serializableType = typeReference.selfOrItemTypeReference.resolve() 954 val serializableDeclaration = serializableType.declaration as KSClassDeclaration 955 return AnnotatedAppFunctionSerializable(serializableDeclaration) 956 .parameterizedBy(serializableType.arguments) 957 } 958 959 private fun getSerializableFactoryVariableName( 960 annotatedSerializable: AnnotatedAppFunctionSerializable 961 ): String { 962 return when (annotatedSerializable) { 963 is AnnotatedParameterizedAppFunctionSerializable -> { 964 val typeArgumentSuffix = 965 annotatedSerializable.typeParameterMap.values.joinToString { typeArgument -> 966 typeArgument 967 .toTypeName() 968 .toString() 969 .replace(Regex("[_<>]"), "_") 970 .replace("?", "_Nullable") 971 .toPascalCase() 972 } 973 "${annotatedSerializable.originalClassName.simpleName.lowerFirstChar()}${typeArgumentSuffix}Factory" 974 } 975 else -> { 976 "${annotatedSerializable.originalClassName.simpleName.lowerFirstChar()}Factory" 977 } 978 } 979 } 980 981 companion object { 982 /** Gets the TypeParameter property name used by generic AppFunctionSerializableFactory */ 983 fun getTypeParameterPropertyName(typeParameter: KSTypeParameter): String { 984 return "${typeParameter.name.asString().uppercase().replaceFirstChar { it.lowercase() }}TypeParameter" 985 } 986 } 987 } 988