<lambda>null1 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
ClassPrinternull32 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
generateAidlnull120 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 */
generateWithersnull136 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
generateCopyConstructornull156 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 */
generateBuildUponnull183 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
ClassPrinternull202 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
generateBuilderMethodnull273 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
generateBuilderSettersnull310 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 =
331 if (isHidden()) "/** @see #$setterName @hide */" else "/** @see #$setterName */"
332 val adderName = "add$SingularName"
333
334 val singularNameCustomizationHint = if (SingularNameOrNull == null) {
335 "// You can refine this method's name by providing item's singular name, e.g.:\n" +
336 "// @DataClass.PluralOf(\"item\")) mItems = ...\n\n"
337 } else ""
338
339
340 if (isList && FieldInnerType != null) {
341 generateBuilderMethod(
342 name = adderName,
343 defVisibility = visibility,
344 paramAnnotations = "@$NonNull",
345 paramTypes = listOf(FieldInnerType),
346 genJavadoc = { +javadocSeeSetter }) {
347
348 !singularNameCustomizationHint
349 +"if ($name == null) $setterName(new $ArrayList<>());"
350 +"$name.add(value);"
351 +"return$maybeCast this;"
352 }
353 }
354
355 if (isMap && FieldInnerType != null) {
356 generateBuilderMethod(
357 name = adderName,
358 defVisibility = visibility,
359 paramAnnotations = "@$NonNull",
360 paramTypes = fieldTypeGenegicArgs,
361 paramNames = listOf("key", "value"),
362 genJavadoc = { +javadocSeeSetter }) {
363 !singularNameCustomizationHint
364 +"if ($name == null) $setterName(new ${if (FieldClass == "Map") LinkedHashMap else FieldClass}());"
365 +"$name.put(key, value);"
366 +"return$maybeCast this;"
367 }
368 }
369 }
370 }
371
generateBuilderBuildnull372 private fun ClassPrinter.generateBuilderBuild() {
373 +"/** Builds the instance. This builder should not be touched after calling this! */"
374 "public @$NonNull $ClassType build()" {
375 +"checkNotUsed();"
376 +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used"
377 +""
378 fields.forEachApply {
379 if (hasDefault) {
380 "if ((mBuilderFieldsSet & $fieldBit) == 0)" {
381 +"$name = $defaultExpr;"
382 }
383 }
384 }
385 "$ClassType o = new $ClassType(" {
386 fields.forEachTrimmingTrailingComma {
387 +"$name,"
388 }
389 } + ";"
390 +"return o;"
391 }
392 }
393
generateParcelablenull394 fun ClassPrinter.generateParcelable() {
395 val booleanFields = fields.filter { it.Type == "boolean" }
396 val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES }
397 val nullableFields = objectFields.filter { it.mayBeNull && it.Type !in PRIMITIVE_ARRAY_TYPES }
398 val nonBooleanFields = fields - booleanFields
399
400
401 val flagStorageType = when (fields.size) {
402 in 0..7 -> "byte"
403 in 8..15 -> "int"
404 in 16..31 -> "long"
405 else -> throw NotImplementedError("32+ field classes not yet supported")
406 }
407 val FlagStorageType = flagStorageType.capitalize()
408
409 fields.forEachApply {
410 if (sParcelling != null) {
411 +GENERATED_MEMBER_HEADER
412 "static $Parcelling<$Type> $sParcelling =" {
413 "$Parcelling.Cache.get(" {
414 +"$customParcellingClass.class"
415 } + ";"
416 }
417 "static {" {
418 "if ($sParcelling == null)" {
419 "$sParcelling = $Parcelling.Cache.put(" {
420 +"new $customParcellingClass()"
421 } + ";"
422 }
423 }
424 +""
425 }
426 }
427
428 val Parcel = classRef("android.os.Parcel")
429 if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
430 +"@Override"
431 +GENERATED_MEMBER_HEADER
432 "public void writeToParcel(@$NonNull $Parcel dest, int flags)" {
433 +"// You can override field parcelling by defining methods like:"
434 +"// void parcelFieldName(Parcel dest, int flags) { ... }"
435 +""
436
437 if (extendsParcelableClass) {
438 +"super.writeToParcel(dest, flags);\n"
439 }
440
441 if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
442 +"$flagStorageType flg = 0;"
443 booleanFields.forEachApply {
444 +"if ($internalGetter) flg |= $fieldBit;"
445 }
446 nullableFields.forEachApply {
447 +"if ($internalGetter != null) flg |= $fieldBit;"
448 }
449 +"dest.write$FlagStorageType(flg);"
450 }
451
452 nonBooleanFields.forEachApply {
453 val customParcellingMethod = "parcel$NameUpperCamel"
454 when {
455 hasMethod(customParcellingMethod, Parcel, "int") ->
456 +"$customParcellingMethod(dest, flags);"
457 customParcellingClass != null -> +"$sParcelling.parcel($name, dest, flags);"
458 hasAnnotation("@$DataClassEnum") ->
459 +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());"
460 else -> {
461 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) !"if ($internalGetter != null) "
462 var args = internalGetter
463 if (ParcelMethodsSuffix.startsWith("Parcelable")
464 || ParcelMethodsSuffix.startsWith("TypedObject")
465 || ParcelMethodsSuffix == "TypedArray") {
466 args += ", flags"
467 }
468 +"dest.write$ParcelMethodsSuffix($args);"
469 }
470 }
471 }
472 }
473 }
474
475 if (!isMethodGenerationSuppressed("describeContents")) {
476 +"@Override"
477 +GENERATED_MEMBER_HEADER
478 +"public int describeContents() { return 0; }"
479 +""
480 }
481
482 if (!hasMethod(ClassName, Parcel)) {
483 val visibility = if (classAst.isFinal) "/* package-private */" else "protected"
484
485 +"/** @hide */"
486 +"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
487 +GENERATED_MEMBER_HEADER
488 "$visibility $ClassName(@$NonNull $Parcel in) {" {
489 +"// You can override field unparcelling by defining methods like:"
490 +"// static FieldType unparcelFieldName(Parcel in) { ... }"
491 +""
492
493 if (extendsParcelableClass) {
494 +"super(in);\n"
495 }
496
497 if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
498 +"$flagStorageType flg = in.read$FlagStorageType();"
499 }
500 booleanFields.forEachApply {
501 +"$Type $_name = (flg & $fieldBit) != 0;"
502 }
503 nonBooleanFields.forEachApply {
504
505 // Handle customized parceling
506 val customParcellingMethod = "unparcel$NameUpperCamel"
507 if (hasMethod(customParcellingMethod, Parcel)) {
508 +"$Type $_name = $customParcellingMethod(in);"
509 } else if (customParcellingClass != null) {
510 +"$Type $_name = $sParcelling.unparcel(in);"
511 } else if (hasAnnotation("@$DataClassEnum")) {
512 val ordinal = "${_name}Ordinal"
513 +"int $ordinal = in.readInt();"
514 +"$Type $_name = $ordinal < 0 ? null : $FieldClass.values()[$ordinal];"
515 } else {
516 val methodArgs = mutableListOf<String>()
517
518 // Create container if any
519 val containerInitExpr = when {
520 FieldClass == "Map" -> "new $LinkedHashMap<>()"
521 isMap -> "new $FieldClass()"
522 FieldClass == "List" || FieldClass == "ArrayList" ->
523 "new ${classRef("java.util.ArrayList")}<>()"
524 else -> ""
525 }
526 val passContainer = containerInitExpr.isNotEmpty()
527
528 // nullcheck +
529 // "FieldType fieldName = (FieldType)"
530 if (passContainer) {
531 methodArgs.add(_name)
532 !"$Type $_name = "
533 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) {
534 +"null;"
535 !"if ((flg & $fieldBit) != 0) {"
536 pushIndent()
537 +""
538 !"$_name = "
539 }
540 +"$containerInitExpr;"
541 } else {
542 !"$Type $_name = "
543 if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) {
544 !"(flg & $fieldBit) == 0 ? null : "
545 }
546 if (ParcelMethodsSuffix == "StrongInterface") {
547 !"$FieldClass.Stub.asInterface("
548 } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" &&
549 (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") &&
550 ParcelMethodsSuffix != "Parcelable") {
551 !"($FieldClass) "
552 }
553 }
554
555 // Determine method args
556 when {
557 ParcelMethodsSuffix == "Parcelable" ->
558 methodArgs += "$FieldClass.class.getClassLoader()"
559 ParcelMethodsSuffix == "SparseArray" ->
560 methodArgs += "$FieldInnerClass.class.getClassLoader()"
561 ParcelMethodsSuffix == "TypedObject" ->
562 methodArgs += "$FieldClass.CREATOR"
563 ParcelMethodsSuffix == "TypedArray" ->
564 methodArgs += "$FieldInnerClass.CREATOR"
565 ParcelMethodsSuffix == "Map" ->
566 methodArgs += "${fieldTypeGenegicArgs[1].substringBefore("<")}.class.getClassLoader()"
567 ParcelMethodsSuffix.startsWith("Parcelable")
568 || (isList || isArray)
569 && FieldInnerType !in PRIMITIVE_TYPES + "String" ->
570 methodArgs += "$FieldInnerClass.class.getClassLoader()"
571 }
572
573 // ...in.readFieldType(args...);
574 when {
575 ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder"
576 isArray -> !"in.create$ParcelMethodsSuffix"
577 else -> !"in.read$ParcelMethodsSuffix"
578 }
579 !"(${methodArgs.joinToString(", ")})"
580 if (ParcelMethodsSuffix == "StrongInterface") !")"
581 +";"
582
583 // Cleanup if passContainer
584 if (passContainer && mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) {
585 popIndent()
586 rmEmptyLine()
587 +"\n}"
588 }
589 }
590 }
591
592 +""
593 fields.forEachApply {
594 !"this."
595 generateSetFrom(_name)
596 }
597
598 generateOnConstructedCallback()
599 }
600 }
601
602 if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) {
603 val Creator = classRef("android.os.Parcelable.Creator")
604
605 +GENERATED_MEMBER_HEADER
606 "public static final @$NonNull $Creator<$ClassName> CREATOR" {
607 +"= new $Creator<$ClassName>()"
608 }; " {" {
609
610 +"@Override"
611 "public $ClassName[] newArray(int size)" {
612 +"return new $ClassName[size];"
613 }
614
615 +"@Override"
616 "public $ClassName createFromParcel(@$NonNull $Parcel in)" {
617 +"return new $ClassName(in);"
618 }
619 rmEmptyLine()
620 } + ";"
621 +""
622 }
623 }
624
generateEqualsHashcodenull625 fun ClassPrinter.generateEqualsHashcode() {
626 if (!isMethodGenerationSuppressed("equals", "Object")) {
627 +"@Override"
628 +GENERATED_MEMBER_HEADER
629 "public boolean equals(@$Nullable Object o)" {
630 +"// You can override field equality logic by defining either of the methods like:"
631 +"// boolean fieldNameEquals($ClassName other) { ... }"
632 +"// boolean fieldNameEquals(FieldType otherValue) { ... }"
633 +""
634 """if (this == o) return true;
635 if (o == null || getClass() != o.getClass()) return false;
636 @SuppressWarnings("unchecked")
637 $ClassType that = ($ClassType) o;
638 //noinspection PointlessBooleanExpression
639 return true""" {
640 fields.forEachApply {
641 val sfx = if (isLast) ";" else ""
642 val customEquals = "${nameLowerCamel}Equals"
643 when {
644 hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx"
645 hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx"
646 else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx"
647 }
648 }
649 }
650 }
651 }
652
653 if (!isMethodGenerationSuppressed("hashCode")) {
654 +"@Override"
655 +GENERATED_MEMBER_HEADER
656 "public int hashCode()" {
657 +"// You can override field hashCode logic by defining methods like:"
658 +"// int fieldNameHashCode() { ... }"
659 +""
660 +"int _hash = 1;"
661 fields.forEachApply {
662 !"_hash = 31 * _hash + "
663 val customHashCode = "${nameLowerCamel}HashCode"
664 when {
665 hasMethod(customHashCode) -> +"$customHashCode();"
666 Type == "int" || Type == "byte" -> +"$internalGetter;"
667 Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);"
668 isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);"
669 else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);"
670 }
671 }
672 +"return _hash;"
673 }
674 }
675 }
676
677 //TODO support IntDef flags?
ClassPrinternull678 fun ClassPrinter.generateToString() {
679 if (!isMethodGenerationSuppressed("toString")) {
680 +"@Override"
681 +GENERATED_MEMBER_HEADER
682 "public String toString()" {
683 +"// You can override field toString logic by defining methods like:"
684 +"// String fieldNameToString() { ... }"
685 +""
686 "return \"$ClassName { \" +" {
687 fields.forEachApply {
688 val customToString = "${nameLowerCamel}ToString"
689 val expr = when {
690 hasMethod(customToString) -> "$customToString()"
691 isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)"
692 intOrStringDef?.type?.isInt == true ->
693 "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)"
694 else -> internalGetter
695 }
696 +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +"
697 }
698 }
699 +"\" }\";"
700 }
701 }
702 }
703
704 fun ClassPrinter.generateSetters() {
705 fields.forEachApply {
706 if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type)
707 && !fieldAst.isPublic
708 && !isFinal) {
709
710 generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
711 +GENERATED_MEMBER_HEADER
712 "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
713 generateSetFrom("value")
714 +"return this;"
715 }
716 }
717 }
718 }
719
720 fun ClassPrinter.generateGetters() {
721 (fields + lazyTransientFields).forEachApply {
722 val methodPrefix = if (Type == "boolean") "is" else "get"
723 val methodName = methodPrefix + NameUpperCamel
724
725 if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) {
726
727 generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
728 +GENERATED_MEMBER_HEADER
729 "public $annotationsAndType $methodName()" {
730 if (lazyInitializer == null) {
731 +"return $name;"
732 } else {
733 +"$Type $_name = $name;"
734 "if ($_name == null)" {
735 if (fieldAst.isVolatile) {
736 "synchronized(this)" {
737 +"$_name = $name;"
738 "if ($_name == null)" {
739 +"$_name = $name = $lazyInitializer();"
740 }
741 }
742 } else {
743 +"// You can mark field as volatile for thread-safe double-check init"
744 +"$_name = $name = $lazyInitializer();"
745 }
746 }
747 +"return $_name;"
748 }
749 }
750 }
751 }
752 }
753
754 fun FieldInfo.isHidden(): Boolean {
755 if (javadocFull != null) {
756 (javadocFull ?: "/**\n */").lines().forEach {
757 if (it.contains("@hide")) return true
758 }
759 }
760 return false
761 }
762
763 fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
764 if (javadocFull != null || forceHide) {
765 var hidden = false
766 (javadocFull ?: "/**\n */").lines().forEach {
767 if (it.contains("@hide")) hidden = true
768 if (it.contains("*/") && forceHide && !hidden) {
769 if (javadocFull != null) +" *"
770 +" * @hide"
771 }
772 +it
773 }
774 }
775 }
776
777 fun FieldInfo.generateSetFrom(source: String) = classPrinter {
778 +"$name = $source;"
779 generateFieldValidation(field = this@generateSetFrom)
780 }
781
782 fun ClassPrinter.generateConstructor(visibility: String = "public") {
783 if (visibility == "public") {
784 generateConstructorJavadoc()
785 }
786 +GENERATED_MEMBER_HEADER
787 "$visibility $ClassName(" {
788 fields.forEachApply {
789 +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}"
790 }
791 }
792 " {" {
793 fields.forEachApply {
794 !"this."
795 generateSetFrom(nameLowerCamel)
796 }
797
798 generateOnConstructedCallback()
799 }
800 }
801
802 private fun ClassPrinter.generateConstructorJavadoc(
803 fields: List<FieldInfo> = this.fields,
804 ClassName: String = this.ClassName,
805 hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) {
806 if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return
807 +"/**"
808 +" * Creates a new $ClassName."
809 +" *"
810 fields.filter { it.javadoc != null }.forEachApply {
811 javadocTextNoAnnotationLines?.apply {
812 +" * @param $nameLowerCamel"
813 forEach {
814 +" * $it"
815 }
816 }
817 }
818 if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide"
819 +" */"
820 }
821
822 private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) {
823 val lines = text.lines()
824 if (lines.isNotEmpty()) {
825 !lines[0]
826 }
827 if (lines.size >= 2) {
828 "" {
829 lines.drop(1).forEach {
830 +it
831 }
832 }
833 }
834 }
835
836 private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run {
837 if (isNonEmpty) {
838 "if ($isEmptyExpr)" {
839 +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
840 }
841 }
842 if (intOrStringDef != null) {
843 if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
844 +""
845 "$Preconditions.checkFlagsArgument(" {
846 +"$name, "
847 appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| "))
848 }
849 +";"
850 } else {
851 +""
852 !"if ("
853 appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") {
854 "!(${isEqualToExpr(it)})"
855 })
856 rmEmptyLine(); ") {" {
857 "throw new ${classRef<IllegalArgumentException>()}(" {
858 "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
859
860 intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
861 +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
862 }
863 }
864 }
865 +";"
866 }
867 }
868 }
869
870 val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
871 val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
872 it.nameAsString != Each &&
873 it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
874 }
875
876 val Size = classRef("android.annotation.Size")
877 fieldAst.annotations.filterNot {
878 it.nameAsString == intOrStringDef?.AnnotationName
879 || it.nameAsString in knownNonValidationAnnotations
880 || it in perElementValidations
881 || it.args.any { (_, value) -> value is ArrayInitializerExpr }
882 }.forEach { annotation ->
883 appendValidateCall(annotation,
884 valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
885 }
886
887 if (perElementValidations.isNotEmpty()) {
888 +"int ${nameLowerCamel}Size = $sizeExpr;"
889 "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
890 perElementValidations.forEach { annotation ->
891 appendValidateCall(annotation,
892 valueToValidate = elemAtIndexExpr("i"))
893 }
894 }
895 }
896 }
897
898 fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
899 val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
900 "$validate(" {
901 !"${annotation.nameAsString}.class, null, $valueToValidate"
902 annotation.args.forEach { name, value ->
903 !",\n\"$name\", $value"
904 }
905 }
906 +";"
907 }
908
909 private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
910 +""
911 val call = "${prefix}onConstructed();"
912 if (hasMethod("onConstructed")) {
913 +call
914 } else {
915 +"// $call // You can define this method to get a callback"
916 }
917 }
918
919 fun ClassPrinter.generateForEachField() {
920 val specializations = listOf("Object", "int")
921 val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" }
922 val usedSpecializationsSet = usedSpecializations.toSet()
923
924 val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction")
925
926 +GENERATED_MEMBER_HEADER
927 "void forEachField(" {
928 usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
929 val SpecType = specType.capitalize()
930 val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
931 +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
932 }
933 }; " {" {
934 usedSpecializations.forEachIndexed { i, specType ->
935 val SpecType = specType.capitalize()
936 fields[i].apply {
937 +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);"
938 }
939 }
940 }
941
942 if (usedSpecializationsSet.size > 1) {
943 +"/** @deprecated May cause boxing allocations - use with caution! */"
944 +"@Deprecated"
945 +GENERATED_MEMBER_HEADER
946 "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" {
947 fields.forEachApply {
948 +"action.acceptObject(this, \"$nameLowerCamel\", $name);"
949 }
950 }
951 }
952 }
953
954 fun ClassPrinter.generateMetadata(file: File) {
955 "@$DataClassGenerated(" {
956 +"time = ${System.currentTimeMillis()}L,"
957 +"codegenVersion = \"$CODEGEN_VERSION\","
958 +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
959 +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
960 }
961 +""
962 +"@Deprecated"
963 +"private void __metadata() {}\n"
964 }
965