<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