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