• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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 = "/** @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 
generateBuilderBuildnull371 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 
generateParcelablenull393 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 
generateEqualsHashcodenull624 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?
ClassPrinternull677 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