1 package com.android.codegen 2 3 import com.github.javaparser.ast.body.FieldDeclaration 4 import com.github.javaparser.ast.body.MethodDeclaration 5 import com.github.javaparser.ast.body.VariableDeclarator 6 import com.github.javaparser.ast.expr.AnnotationExpr 7 import com.github.javaparser.ast.expr.ArrayInitializerExpr 8 import java.io.File 9 10 11 /** 12 * IntDefs and StringDefs based on constants 13 */ 14 fun ClassPrinter.generateConstDefs() { 15 val consts = classAst.fields.filter { 16 it.isStatic && it.isFinal && it.variables.all { variable -> 17 variable.type.asString() in listOf("int", "String") 18 } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs } 19 }.flatMap { field -> field.variables.map { it to field } } 20 val intConsts = consts.filter { it.first.type.asString() == "int" } 21 val strConsts = consts.filter { it.first.type.asString() == "String" } 22 val intGroups = intConsts.groupBy { it.first.nameAsString.split("_")[0] }.values 23 val strGroups = strConsts.groupBy { it.first.nameAsString.split("_")[0] }.values 24 intGroups.forEach { 25 generateConstDef(it) 26 } 27 strGroups.forEach { 28 generateConstDef(it) 29 } 30 } 31 32 fun ClassPrinter.generateConstDef(consts: List<Pair<VariableDeclarator, FieldDeclaration>>) { 33 if (consts.size <= 1) return 34 35 val names = consts.map { it.first.nameAsString!! } 36 val prefix = names 37 .reduce { a, b -> a.commonPrefixWith(b) } 38 .dropLastWhile { it != '_' } 39 .dropLast(1) 40 if (prefix.isEmpty()) { 41 println("Failed to generate const def for $names") 42 return 43 } 44 var AnnotationName = prefix.split("_") 45 .filterNot { it.isBlank() } 46 .map { it.toLowerCase().capitalize() } 47 .joinToString("") 48 val annotatedConst = consts.find { it.second.annotations.isNonEmpty } 49 if (annotatedConst != null) { 50 AnnotationName = annotatedConst.second.annotations.first().nameAsString 51 } 52 val type = consts[0].first.type.asString() 53 val flag = type == "int" && consts.all { it.first.initializer.get().toString().startsWith("0x") } 54 val constDef = ConstDef(type = when { 55 type == "String" -> ConstDef.Type.STRING 56 flag -> ConstDef.Type.INT_FLAGS 57 else -> ConstDef.Type.INT 58 }, 59 AnnotationName = AnnotationName, 60 values = consts.map { it.second } 61 ) 62 constDefs += constDef 63 fields.forEachApply { 64 if (fieldAst.annotations.any { it.nameAsString == AnnotationName }) { 65 this.intOrStringDef = constDef 66 } 67 } 68 69 val visibility = if (consts[0].second.isPublic) "public" else "/* package-private */" 70 71 val Retention = classRef("java.lang.annotation.Retention") 72 val RetentionPolicySource = memberRef("java.lang.annotation.RetentionPolicy.SOURCE") 73 val ConstDef = classRef("android.annotation.${type.capitalize()}Def") 74 75 if (FeatureFlag.CONST_DEFS.hidden) { 76 +"/** @hide */" 77 } 78 "@$ConstDef(${if_(flag, "flag = true, ")}prefix = \"${prefix}_\", value = {" { 79 names.forEachLastAware { name, isLast -> 80 +"$name${if_(!isLast, ",")}" 81 } 82 } + ")" 83 +"@$Retention($RetentionPolicySource)" 84 +GENERATED_MEMBER_HEADER 85 +"$visibility @interface $AnnotationName {}" 86 +"" 87 88 if (type == "int") { 89 if (FeatureFlag.CONST_DEFS.hidden) { 90 +"/** @hide */" 91 } 92 +GENERATED_MEMBER_HEADER 93 val methodDefLine = "$visibility static String ${AnnotationName.decapitalize()}ToString(" + 94 "@$AnnotationName int value)" 95 if (flag) { 96 val flg2str = memberRef("com.android.internal.util.BitUtils.flagsToString") 97 methodDefLine { 98 "return $flg2str(" { 99 +"value, $ClassName::single${AnnotationName}ToString" 100 } + ";" 101 } 102 +GENERATED_MEMBER_HEADER 103 !"static String single${AnnotationName}ToString(@$AnnotationName int value)" 104 } else { 105 !methodDefLine 106 } 107 " {" { 108 "switch (value) {" { 109 names.forEach { name -> 110 "case $name:" { 111 +"return \"$name\";" 112 } 113 } 114 +"default: return Integer.toHexString(value);" 115 } 116 } 117 } 118 } 119 120 fun FileInfo.generateAidl() { 121 val aidl = File(file.path.substringBeforeLast(".java") + ".aidl") 122 if (aidl.exists()) return 123 aidl.writeText(buildString { 124 sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach { 125 appendln(it) 126 } 127 append("\nparcelable ${mainClass.nameAsString};\n") 128 }) 129 } 130 131 /** 132 * ``` 133 * Foo newFoo = oldFoo.withBar(newBar); 134 * ``` 135 */ 136 fun ClassPrinter.generateWithers() { 137 fields.forEachApply { 138 val metodName = "with$NameUpperCamel" 139 if (!isMethodGenerationSuppressed(metodName, Type)) { 140 generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden) 141 """@$NonNull 142 $GENERATED_MEMBER_HEADER 143 public $ClassType $metodName($annotatedTypeForSetterParam value)""" { 144 val changedFieldName = name 145 146 "return new $ClassType(" { 147 fields.forEachTrimmingTrailingComma { 148 if (name == changedFieldName) +"value," else +"$name," 149 } 150 } + ";" 151 } 152 } 153 } 154 } 155 156 fun ClassPrinter.generateCopyConstructor() { 157 if (classAst.constructors.any { 158 it.parameters.size == 1 && 159 it.parameters[0].type.asString() == ClassType 160 }) { 161 return 162 } 163 164 +"/**" 165 +" * Copy constructor" 166 if (FeatureFlag.COPY_CONSTRUCTOR.hidden) { 167 +" * @hide" 168 } 169 +" */" 170 +GENERATED_MEMBER_HEADER 171 "public $ClassName(@$NonNull $ClassName orig)" { 172 fields.forEachApply { 173 +"$name = orig.$name;" 174 } 175 } 176 } 177 178 /** 179 * ``` 180 * Foo newFoo = oldFoo.buildUpon().setBar(newBar).build(); 181 * ``` 182 */ 183 fun ClassPrinter.generateBuildUpon() { 184 if (isMethodGenerationSuppressed("buildUpon")) return 185 186 +"/**" 187 +" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance." 188 if (FeatureFlag.BUILD_UPON.hidden) { 189 +" * @hide" 190 } 191 +" */" 192 +GENERATED_MEMBER_HEADER 193 "public $BuilderType buildUpon()" { 194 "return new $BuilderType()" { 195 fields.forEachApply { 196 +".set$NameUpperCamel($internalGetter)" 197 } + ";" 198 } 199 } 200 } 201 202 fun ClassPrinter.generateBuilder() { 203 val setterVisibility = if (cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)) 204 "protected" else "public" 205 val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS) 206 "public" else "/* package-*/" 207 208 val providedSubclassAst = nestedClasses.find { 209 it.extendedTypes.any { it.nameAsString == BASE_BUILDER_CLASS } 210 } 211 212 val BuilderSupertype = if (customBaseBuilderAst != null) { 213 customBaseBuilderAst!!.nameAsString 214 } else { 215 "Object" 216 } 217 218 val maybeFinal = if_(classAst.isFinal, "final ") 219 220 +"/**" 221 +" * A builder for {@link $ClassName}" 222 if (FeatureFlag.BUILDER.hidden) +" * @hide" 223 +" */" 224 +"@SuppressWarnings(\"WeakerAccess\")" 225 +GENERATED_MEMBER_HEADER 226 !"public static ${maybeFinal}class $BuilderClass$genericArgs" 227 if (BuilderSupertype != "Object") { 228 appendSameLine(" extends $BuilderSupertype") 229 } 230 " {" { 231 232 +"" 233 fields.forEachApply { 234 +"private $annotationsAndType $name;" 235 } 236 +"" 237 +"private long mBuilderFieldsSet = 0L;" 238 +"" 239 240 val requiredFields = fields.filter { !it.hasDefault } 241 242 generateConstructorJavadoc( 243 fields = requiredFields, 244 ClassName = BuilderClass, 245 hidden = false) 246 "$constructorVisibility $BuilderClass(" { 247 requiredFields.forEachLastAware { field, isLast -> 248 +"${field.annotationsAndType} ${field._name}${if_(!isLast, ",")}" 249 } 250 }; " {" { 251 requiredFields.forEachApply { 252 generateSetFrom(_name) 253 } 254 } 255 256 generateBuilderSetters(setterVisibility) 257 258 generateBuilderBuild() 259 260 "private void checkNotUsed() {" { 261 "if ((mBuilderFieldsSet & ${bitAtExpr(fields.size)}) != 0)" { 262 "throw new IllegalStateException(" { 263 +"\"This Builder should not be reused. Use a new Builder instance instead\"" 264 } 265 +";" 266 } 267 } 268 269 rmEmptyLine() 270 } 271 } 272 273 private fun ClassPrinter.generateBuilderMethod( 274 defVisibility: String, 275 name: String, 276 paramAnnotations: String? = null, 277 paramTypes: List<String>, 278 paramNames: List<String> = listOf("value"), 279 genJavadoc: ClassPrinter.() -> Unit, 280 genBody: ClassPrinter.() -> Unit) { 281 282 val providedMethod = customBaseBuilderAst?.members?.find { 283 it is MethodDeclaration 284 && it.nameAsString == name 285 && it.parameters.map { it.typeAsString } == paramTypes.toTypedArray().toList() 286 } as? MethodDeclaration 287 288 if ((providedMethod == null || providedMethod.isAbstract) 289 && name !in builderSuppressedMembers) { 290 val visibility = providedMethod?.visibility?.asString() ?: defVisibility 291 val ReturnType = providedMethod?.typeAsString ?: CANONICAL_BUILDER_CLASS 292 val Annotations = providedMethod?.annotations?.joinToString("\n") 293 294 genJavadoc() 295 +GENERATED_MEMBER_HEADER 296 if (providedMethod?.isAbstract == true) +"@Override" 297 if (!Annotations.isNullOrEmpty()) +Annotations 298 val ParamAnnotations = if (!paramAnnotations.isNullOrEmpty()) "$paramAnnotations " else "" 299 300 "$visibility @$NonNull $ReturnType $name(${ 301 paramTypes.zip(paramNames).joinToString(", ") { (Type, paramName) -> 302 "$ParamAnnotations$Type $paramName" 303 } 304 })" { 305 genBody() 306 } 307 } 308 } 309 310 private fun ClassPrinter.generateBuilderSetters(visibility: String) { 311 312 fields.forEachApply { 313 val maybeCast = 314 if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)") 315 316 val setterName = "set$NameUpperCamel" 317 318 generateBuilderMethod( 319 name = setterName, 320 defVisibility = visibility, 321 paramAnnotations = annotationsForSetterParam, 322 paramTypes = listOf(SetterParamType), 323 genJavadoc = { generateFieldJavadoc() }) { 324 +"checkNotUsed();" 325 +"mBuilderFieldsSet |= $fieldBit;" 326 +"$name = value;" 327 +"return$maybeCast this;" 328 } 329 330 val javadocSeeSetter = "/** @see #$setterName */" 331 val adderName = "add$SingularName" 332 333 val singularNameCustomizationHint = if (SingularNameOrNull == null) { 334 "// You can refine this method's name by providing item's singular name, e.g.:\n" + 335 "// @DataClass.PluralOf(\"item\")) mItems = ...\n\n" 336 } else "" 337 338 339 if (isList && FieldInnerType != null) { 340 generateBuilderMethod( 341 name = adderName, 342 defVisibility = visibility, 343 paramAnnotations = "@$NonNull", 344 paramTypes = listOf(FieldInnerType), 345 genJavadoc = { +javadocSeeSetter }) { 346 347 !singularNameCustomizationHint 348 +"if ($name == null) $setterName(new $ArrayList<>());" 349 +"$name.add(value);" 350 +"return$maybeCast this;" 351 } 352 } 353 354 if (isMap && FieldInnerType != null) { 355 generateBuilderMethod( 356 name = adderName, 357 defVisibility = visibility, 358 paramAnnotations = "@$NonNull", 359 paramTypes = fieldTypeGenegicArgs, 360 paramNames = listOf("key", "value"), 361 genJavadoc = { +javadocSeeSetter }) { 362 !singularNameCustomizationHint 363 +"if ($name == null) $setterName(new ${if (FieldClass == "Map") LinkedHashMap else FieldClass}());" 364 +"$name.put(key, value);" 365 +"return$maybeCast this;" 366 } 367 } 368 } 369 } 370 371 private fun ClassPrinter.generateBuilderBuild() { 372 +"/** Builds the instance. This builder should not be touched after calling this! */" 373 "public @$NonNull $ClassType build()" { 374 +"checkNotUsed();" 375 +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used" 376 +"" 377 fields.forEachApply { 378 if (hasDefault) { 379 "if ((mBuilderFieldsSet & $fieldBit) == 0)" { 380 +"$name = $defaultExpr;" 381 } 382 } 383 } 384 "$ClassType o = new $ClassType(" { 385 fields.forEachTrimmingTrailingComma { 386 +"$name," 387 } 388 } + ";" 389 +"return o;" 390 } 391 } 392 393 fun ClassPrinter.generateParcelable() { 394 val booleanFields = fields.filter { it.Type == "boolean" } 395 val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES } 396 val nullableFields = objectFields.filter { it.mayBeNull && it.Type !in PRIMITIVE_ARRAY_TYPES } 397 val nonBooleanFields = fields - booleanFields 398 399 400 val flagStorageType = when (fields.size) { 401 in 0..7 -> "byte" 402 in 8..15 -> "int" 403 in 16..31 -> "long" 404 else -> throw NotImplementedError("32+ field classes not yet supported") 405 } 406 val FlagStorageType = flagStorageType.capitalize() 407 408 fields.forEachApply { 409 if (sParcelling != null) { 410 +GENERATED_MEMBER_HEADER 411 "static $Parcelling<$Type> $sParcelling =" { 412 "$Parcelling.Cache.get(" { 413 +"$customParcellingClass.class" 414 } + ";" 415 } 416 "static {" { 417 "if ($sParcelling == null)" { 418 "$sParcelling = $Parcelling.Cache.put(" { 419 +"new $customParcellingClass()" 420 } + ";" 421 } 422 } 423 +"" 424 } 425 } 426 427 val Parcel = classRef("android.os.Parcel") 428 if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) { 429 +"@Override" 430 +GENERATED_MEMBER_HEADER 431 "public void writeToParcel(@$NonNull $Parcel dest, int flags)" { 432 +"// You can override field parcelling by defining methods like:" 433 +"// void parcelFieldName(Parcel dest, int flags) { ... }" 434 +"" 435 436 if (extendsParcelableClass) { 437 +"super.writeToParcel(dest, flags);\n" 438 } 439 440 if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) { 441 +"$flagStorageType flg = 0;" 442 booleanFields.forEachApply { 443 +"if ($internalGetter) flg |= $fieldBit;" 444 } 445 nullableFields.forEachApply { 446 +"if ($internalGetter != null) flg |= $fieldBit;" 447 } 448 +"dest.write$FlagStorageType(flg);" 449 } 450 451 nonBooleanFields.forEachApply { 452 val customParcellingMethod = "parcel$NameUpperCamel" 453 when { 454 hasMethod(customParcellingMethod, Parcel, "int") -> 455 +"$customParcellingMethod(dest, flags);" 456 customParcellingClass != null -> +"$sParcelling.parcel($name, dest, flags);" 457 hasAnnotation("@$DataClassEnum") -> 458 +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());" 459 else -> { 460 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) !"if ($internalGetter != null) " 461 var args = internalGetter 462 if (ParcelMethodsSuffix.startsWith("Parcelable") 463 || ParcelMethodsSuffix.startsWith("TypedObject") 464 || ParcelMethodsSuffix == "TypedArray") { 465 args += ", flags" 466 } 467 +"dest.write$ParcelMethodsSuffix($args);" 468 } 469 } 470 } 471 } 472 } 473 474 if (!isMethodGenerationSuppressed("describeContents")) { 475 +"@Override" 476 +GENERATED_MEMBER_HEADER 477 +"public int describeContents() { return 0; }" 478 +"" 479 } 480 481 if (!hasMethod(ClassName, Parcel)) { 482 val visibility = if (classAst.isFinal) "/* package-private */" else "protected" 483 484 +"/** @hide */" 485 +"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})" 486 +GENERATED_MEMBER_HEADER 487 "$visibility $ClassName(@$NonNull $Parcel in) {" { 488 +"// You can override field unparcelling by defining methods like:" 489 +"// static FieldType unparcelFieldName(Parcel in) { ... }" 490 +"" 491 492 if (extendsParcelableClass) { 493 +"super(in);\n" 494 } 495 496 if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) { 497 +"$flagStorageType flg = in.read$FlagStorageType();" 498 } 499 booleanFields.forEachApply { 500 +"$Type $_name = (flg & $fieldBit) != 0;" 501 } 502 nonBooleanFields.forEachApply { 503 504 // Handle customized parceling 505 val customParcellingMethod = "unparcel$NameUpperCamel" 506 if (hasMethod(customParcellingMethod, Parcel)) { 507 +"$Type $_name = $customParcellingMethod(in);" 508 } else if (customParcellingClass != null) { 509 +"$Type $_name = $sParcelling.unparcel(in);" 510 } else if (hasAnnotation("@$DataClassEnum")) { 511 val ordinal = "${_name}Ordinal" 512 +"int $ordinal = in.readInt();" 513 +"$Type $_name = $ordinal < 0 ? null : $FieldClass.values()[$ordinal];" 514 } else { 515 val methodArgs = mutableListOf<String>() 516 517 // Create container if any 518 val containerInitExpr = when { 519 FieldClass == "Map" -> "new $LinkedHashMap<>()" 520 isMap -> "new $FieldClass()" 521 FieldClass == "List" || FieldClass == "ArrayList" -> 522 "new ${classRef("java.util.ArrayList")}<>()" 523 else -> "" 524 } 525 val passContainer = containerInitExpr.isNotEmpty() 526 527 // nullcheck + 528 // "FieldType fieldName = (FieldType)" 529 if (passContainer) { 530 methodArgs.add(_name) 531 !"$Type $_name = " 532 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { 533 +"null;" 534 !"if ((flg & $fieldBit) != 0) {" 535 pushIndent() 536 +"" 537 !"$_name = " 538 } 539 +"$containerInitExpr;" 540 } else { 541 !"$Type $_name = " 542 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { 543 !"(flg & $fieldBit) == 0 ? null : " 544 } 545 if (ParcelMethodsSuffix == "StrongInterface") { 546 !"$FieldClass.Stub.asInterface(" 547 } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" && 548 (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") && 549 ParcelMethodsSuffix != "Parcelable") { 550 !"($FieldClass) " 551 } 552 } 553 554 // Determine method args 555 when { 556 ParcelMethodsSuffix == "Parcelable" -> 557 methodArgs += "$FieldClass.class.getClassLoader()" 558 ParcelMethodsSuffix == "SparseArray" -> 559 methodArgs += "$FieldInnerClass.class.getClassLoader()" 560 ParcelMethodsSuffix == "TypedObject" -> 561 methodArgs += "$FieldClass.CREATOR" 562 ParcelMethodsSuffix == "TypedArray" -> 563 methodArgs += "$FieldInnerClass.CREATOR" 564 ParcelMethodsSuffix == "Map" -> 565 methodArgs += "${fieldTypeGenegicArgs[1].substringBefore("<")}.class.getClassLoader()" 566 ParcelMethodsSuffix.startsWith("Parcelable") 567 || (isList || isArray) 568 && FieldInnerType !in PRIMITIVE_TYPES + "String" -> 569 methodArgs += "$FieldInnerClass.class.getClassLoader()" 570 } 571 572 // ...in.readFieldType(args...); 573 when { 574 ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder" 575 isArray -> !"in.create$ParcelMethodsSuffix" 576 else -> !"in.read$ParcelMethodsSuffix" 577 } 578 !"(${methodArgs.joinToString(", ")})" 579 if (ParcelMethodsSuffix == "StrongInterface") !")" 580 +";" 581 582 // Cleanup if passContainer 583 if (passContainer && mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { 584 popIndent() 585 rmEmptyLine() 586 +"\n}" 587 } 588 } 589 } 590 591 +"" 592 fields.forEachApply { 593 !"this." 594 generateSetFrom(_name) 595 } 596 597 generateOnConstructedCallback() 598 } 599 } 600 601 if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) { 602 val Creator = classRef("android.os.Parcelable.Creator") 603 604 +GENERATED_MEMBER_HEADER 605 "public static final @$NonNull $Creator<$ClassName> CREATOR" { 606 +"= new $Creator<$ClassName>()" 607 }; " {" { 608 609 +"@Override" 610 "public $ClassName[] newArray(int size)" { 611 +"return new $ClassName[size];" 612 } 613 614 +"@Override" 615 "public $ClassName createFromParcel(@$NonNull $Parcel in)" { 616 +"return new $ClassName(in);" 617 } 618 rmEmptyLine() 619 } + ";" 620 +"" 621 } 622 } 623 624 fun ClassPrinter.generateEqualsHashcode() { 625 if (!isMethodGenerationSuppressed("equals", "Object")) { 626 +"@Override" 627 +GENERATED_MEMBER_HEADER 628 "public boolean equals(@$Nullable Object o)" { 629 +"// You can override field equality logic by defining either of the methods like:" 630 +"// boolean fieldNameEquals($ClassName other) { ... }" 631 +"// boolean fieldNameEquals(FieldType otherValue) { ... }" 632 +"" 633 """if (this == o) return true; 634 if (o == null || getClass() != o.getClass()) return false; 635 @SuppressWarnings("unchecked") 636 $ClassType that = ($ClassType) o; 637 //noinspection PointlessBooleanExpression 638 return true""" { 639 fields.forEachApply { 640 val sfx = if (isLast) ";" else "" 641 val customEquals = "${nameLowerCamel}Equals" 642 when { 643 hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx" 644 hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx" 645 else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx" 646 } 647 } 648 } 649 } 650 } 651 652 if (!isMethodGenerationSuppressed("hashCode")) { 653 +"@Override" 654 +GENERATED_MEMBER_HEADER 655 "public int hashCode()" { 656 +"// You can override field hashCode logic by defining methods like:" 657 +"// int fieldNameHashCode() { ... }" 658 +"" 659 +"int _hash = 1;" 660 fields.forEachApply { 661 !"_hash = 31 * _hash + " 662 val customHashCode = "${nameLowerCamel}HashCode" 663 when { 664 hasMethod(customHashCode) -> +"$customHashCode();" 665 Type == "int" || Type == "byte" -> +"$internalGetter;" 666 Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);" 667 isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);" 668 else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);" 669 } 670 } 671 +"return _hash;" 672 } 673 } 674 } 675 676 //TODO support IntDef flags? 677 fun ClassPrinter.generateToString() { 678 if (!isMethodGenerationSuppressed("toString")) { 679 +"@Override" 680 +GENERATED_MEMBER_HEADER 681 "public String toString()" { 682 +"// You can override field toString logic by defining methods like:" 683 +"// String fieldNameToString() { ... }" 684 +"" 685 "return \"$ClassName { \" +" { 686 fields.forEachApply { 687 val customToString = "${nameLowerCamel}ToString" 688 val expr = when { 689 hasMethod(customToString) -> "$customToString()" 690 isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)" 691 intOrStringDef?.type?.isInt == true -> 692 "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)" 693 else -> internalGetter 694 } 695 +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +" 696 } 697 } 698 +"\" }\";" 699 } 700 } 701 } 702 703 fun ClassPrinter.generateSetters() { 704 fields.forEachApply { 705 if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type) 706 && !fieldAst.isPublic 707 && !isFinal) { 708 709 generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden) 710 +GENERATED_MEMBER_HEADER 711 "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { 712 generateSetFrom("value") 713 +"return this;" 714 } 715 } 716 } 717 } 718 719 fun ClassPrinter.generateGetters() { 720 (fields + lazyTransientFields).forEachApply { 721 val methodPrefix = if (Type == "boolean") "is" else "get" 722 val methodName = methodPrefix + NameUpperCamel 723 724 if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) { 725 726 generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden) 727 +GENERATED_MEMBER_HEADER 728 "public $annotationsAndType $methodName()" { 729 if (lazyInitializer == null) { 730 +"return $name;" 731 } else { 732 +"$Type $_name = $name;" 733 "if ($_name == null)" { 734 if (fieldAst.isVolatile) { 735 "synchronized(this)" { 736 +"$_name = $name;" 737 "if ($_name == null)" { 738 +"$_name = $name = $lazyInitializer();" 739 } 740 } 741 } else { 742 +"// You can mark field as volatile for thread-safe double-check init" 743 +"$_name = $name = $lazyInitializer();" 744 } 745 } 746 +"return $_name;" 747 } 748 } 749 } 750 } 751 } 752 753 fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter { 754 if (javadocFull != null || forceHide) { 755 var hidden = false 756 (javadocFull ?: "/**\n */").lines().forEach { 757 if (it.contains("@hide")) hidden = true 758 if (it.contains("*/") && forceHide && !hidden) { 759 if (javadocFull != null) +" *" 760 +" * @hide" 761 } 762 +it 763 } 764 } 765 } 766 767 fun FieldInfo.generateSetFrom(source: String) = classPrinter { 768 +"$name = $source;" 769 generateFieldValidation(field = this@generateSetFrom) 770 } 771 772 fun ClassPrinter.generateConstructor(visibility: String = "public") { 773 if (visibility == "public") { 774 generateConstructorJavadoc() 775 } 776 +GENERATED_MEMBER_HEADER 777 "$visibility $ClassName(" { 778 fields.forEachApply { 779 +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}" 780 } 781 } 782 " {" { 783 fields.forEachApply { 784 !"this." 785 generateSetFrom(nameLowerCamel) 786 } 787 788 generateOnConstructedCallback() 789 } 790 } 791 792 private fun ClassPrinter.generateConstructorJavadoc( 793 fields: List<FieldInfo> = this.fields, 794 ClassName: String = this.ClassName, 795 hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) { 796 if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return 797 +"/**" 798 +" * Creates a new $ClassName." 799 +" *" 800 fields.filter { it.javadoc != null }.forEachApply { 801 javadocTextNoAnnotationLines?.apply { 802 +" * @param $nameLowerCamel" 803 forEach { 804 +" * $it" 805 } 806 } 807 } 808 if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide" 809 +" */" 810 } 811 812 private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) { 813 val lines = text.lines() 814 if (lines.isNotEmpty()) { 815 !lines[0] 816 } 817 if (lines.size >= 2) { 818 "" { 819 lines.drop(1).forEach { 820 +it 821 } 822 } 823 } 824 } 825 826 private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run { 827 if (isNonEmpty) { 828 "if ($isEmptyExpr)" { 829 +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");" 830 } 831 } 832 if (intOrStringDef != null) { 833 if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) { 834 +"" 835 "$Preconditions.checkFlagsArgument(" { 836 +"$name, " 837 appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| ")) 838 } 839 +";" 840 } else { 841 +"" 842 !"if (" 843 appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") { 844 "!(${isEqualToExpr(it)})" 845 }) 846 rmEmptyLine(); ") {" { 847 "throw new ${classRef<IllegalArgumentException>()}(" { 848 "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" { 849 850 intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast -> 851 +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}"""" 852 } 853 } 854 } 855 +";" 856 } 857 } 858 } 859 860 val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line 861 val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter { 862 it.nameAsString != Each && 863 it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false 864 } 865 866 val Size = classRef("android.annotation.Size") 867 fieldAst.annotations.filterNot { 868 it.nameAsString == intOrStringDef?.AnnotationName 869 || it.nameAsString in knownNonValidationAnnotations 870 || it in perElementValidations 871 || it.args.any { (_, value) -> value is ArrayInitializerExpr } 872 }.forEach { annotation -> 873 appendValidateCall(annotation, 874 valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name) 875 } 876 877 if (perElementValidations.isNotEmpty()) { 878 +"int ${nameLowerCamel}Size = $sizeExpr;" 879 "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" { 880 perElementValidations.forEach { annotation -> 881 appendValidateCall(annotation, 882 valueToValidate = elemAtIndexExpr("i")) 883 } 884 } 885 } 886 } 887 888 fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) { 889 val validate = memberRef("com.android.internal.util.AnnotationValidations.validate") 890 "$validate(" { 891 !"${annotation.nameAsString}.class, null, $valueToValidate" 892 annotation.args.forEach { name, value -> 893 !",\n\"$name\", $value" 894 } 895 } 896 +";" 897 } 898 899 private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") { 900 +"" 901 val call = "${prefix}onConstructed();" 902 if (hasMethod("onConstructed")) { 903 +call 904 } else { 905 +"// $call // You can define this method to get a callback" 906 } 907 } 908 909 fun ClassPrinter.generateForEachField() { 910 val specializations = listOf("Object", "int") 911 val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" } 912 val usedSpecializationsSet = usedSpecializations.toSet() 913 914 val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction") 915 916 +GENERATED_MEMBER_HEADER 917 "void forEachField(" { 918 usedSpecializationsSet.toList().forEachLastAware { specType, isLast -> 919 val SpecType = specType.capitalize() 920 val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction") 921 +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}" 922 } 923 }; " {" { 924 usedSpecializations.forEachIndexed { i, specType -> 925 val SpecType = specType.capitalize() 926 fields[i].apply { 927 +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);" 928 } 929 } 930 } 931 932 if (usedSpecializationsSet.size > 1) { 933 +"/** @deprecated May cause boxing allocations - use with caution! */" 934 +"@Deprecated" 935 +GENERATED_MEMBER_HEADER 936 "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" { 937 fields.forEachApply { 938 +"action.acceptObject(this, \"$nameLowerCamel\", $name);" 939 } 940 } 941 } 942 } 943 944 fun ClassPrinter.generateMetadata(file: File) { 945 "@$DataClassGenerated(" { 946 +"time = ${System.currentTimeMillis()}L," 947 +"codegenVersion = \"$CODEGEN_VERSION\"," 948 +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\"," 949 +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\"" 950 } 951 +"" 952 +"@Deprecated" 953 +"private void __metadata() {}\n" 954 } 955