• 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 }
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) !"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) {
533                             +"null;"
534                             !"if ((flg & $fieldBit) != 0) {"
535                             pushIndent()
536                             +""
537                             !"$_name = "
538                         }
539                         +"$containerInitExpr;"
540                     } else {
541                         !"$Type $_name = "
542                         if (mayBeNull) !"(flg & $fieldBit) == 0 ? null : "
543                         if (ParcelMethodsSuffix == "StrongInterface") {
544                             !"$FieldClass.Stub.asInterface("
545                         } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" &&
546                                 (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") &&
547                                 ParcelMethodsSuffix != "Parcelable") {
548                             !"($FieldClass) "
549                         }
550                     }
551 
552                     // Determine method args
553                     when {
554                         ParcelMethodsSuffix == "Parcelable" ->
555                             methodArgs += "$FieldClass.class.getClassLoader()"
556                         ParcelMethodsSuffix == "SparseArray" ->
557                             methodArgs += "$FieldInnerClass.class.getClassLoader()"
558                         ParcelMethodsSuffix == "TypedObject" ->
559                             methodArgs += "$FieldClass.CREATOR"
560                         ParcelMethodsSuffix == "TypedArray" ->
561                             methodArgs += "$FieldInnerClass.CREATOR"
562                         ParcelMethodsSuffix == "Map" ->
563                             methodArgs += "${fieldTypeGenegicArgs[1].substringBefore("<")}.class.getClassLoader()"
564                         ParcelMethodsSuffix.startsWith("Parcelable")
565                                 || (isList || isArray)
566                                 && FieldInnerType !in PRIMITIVE_TYPES + "String" ->
567                             methodArgs += "$FieldInnerClass.class.getClassLoader()"
568                     }
569 
570                     // ...in.readFieldType(args...);
571                     when {
572                         ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder"
573                         isArray -> !"in.create$ParcelMethodsSuffix"
574                         else -> !"in.read$ParcelMethodsSuffix"
575                     }
576                     !"(${methodArgs.joinToString(", ")})"
577                     if (ParcelMethodsSuffix == "StrongInterface") !")"
578                     +";"
579 
580                     // Cleanup if passContainer
581                     if (passContainer && mayBeNull) {
582                         popIndent()
583                         rmEmptyLine()
584                         +"\n}"
585                     }
586                 }
587             }
588 
589             +""
590             fields.forEachApply {
591                 !"this."
592                 generateSetFrom(_name)
593             }
594 
595             generateOnConstructedCallback()
596         }
597     }
598 
599     if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) {
600         val Creator = classRef("android.os.Parcelable.Creator")
601 
602         +GENERATED_MEMBER_HEADER
603         "public static final @$NonNull $Creator<$ClassName> CREATOR" {
604             +"= new $Creator<$ClassName>()"
605         }; " {" {
606 
607             +"@Override"
608             "public $ClassName[] newArray(int size)" {
609                 +"return new $ClassName[size];"
610             }
611 
612             +"@Override"
613             "public $ClassName createFromParcel(@$NonNull $Parcel in)" {
614                 +"return new $ClassName(in);"
615             }
616             rmEmptyLine()
617         } + ";"
618         +""
619     }
620 }
621 
generateEqualsHashcodenull622 fun ClassPrinter.generateEqualsHashcode() {
623     if (!isMethodGenerationSuppressed("equals", "Object")) {
624         +"@Override"
625         +GENERATED_MEMBER_HEADER
626         "public boolean equals(@$Nullable Object o)" {
627             +"// You can override field equality logic by defining either of the methods like:"
628             +"// boolean fieldNameEquals($ClassName other) { ... }"
629             +"// boolean fieldNameEquals(FieldType otherValue) { ... }"
630             +""
631             """if (this == o) return true;
632                         if (o == null || getClass() != o.getClass()) return false;
633                         @SuppressWarnings("unchecked")
634                         $ClassType that = ($ClassType) o;
635                         //noinspection PointlessBooleanExpression
636                         return true""" {
637                 fields.forEachApply {
638                     val sfx = if (isLast) ";" else ""
639                     val customEquals = "${nameLowerCamel}Equals"
640                     when {
641                         hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx"
642                         hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx"
643                         else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx"
644                     }
645                 }
646             }
647         }
648     }
649 
650     if (!isMethodGenerationSuppressed("hashCode")) {
651         +"@Override"
652         +GENERATED_MEMBER_HEADER
653         "public int hashCode()" {
654             +"// You can override field hashCode logic by defining methods like:"
655             +"// int fieldNameHashCode() { ... }"
656             +""
657             +"int _hash = 1;"
658             fields.forEachApply {
659                 !"_hash = 31 * _hash + "
660                 val customHashCode = "${nameLowerCamel}HashCode"
661                 when {
662                     hasMethod(customHashCode) -> +"$customHashCode();"
663                     Type == "int" || Type == "byte" -> +"$internalGetter;"
664                     Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);"
665                     isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);"
666                     else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);"
667                 }
668             }
669             +"return _hash;"
670         }
671     }
672 }
673 
674 //TODO support IntDef flags?
ClassPrinternull675 fun ClassPrinter.generateToString() {
676     if (!isMethodGenerationSuppressed("toString")) {
677         +"@Override"
678         +GENERATED_MEMBER_HEADER
679         "public String toString()" {
680             +"// You can override field toString logic by defining methods like:"
681             +"// String fieldNameToString() { ... }"
682             +""
683             "return \"$ClassName { \" +" {
684                 fields.forEachApply {
685                     val customToString = "${nameLowerCamel}ToString"
686                     val expr = when {
687                         hasMethod(customToString) -> "$customToString()"
688                         isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)"
689                         intOrStringDef?.type?.isInt == true ->
690                             "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)"
691                         else -> internalGetter
692                     }
693                     +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +"
694                 }
695             }
696             +"\" }\";"
697         }
698     }
699 }
700 
701 fun ClassPrinter.generateSetters() {
702     fields.forEachApply {
703         if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type)
704                 && !fieldAst.isPublic
705                 && !isFinal) {
706 
707             generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden)
708             +GENERATED_MEMBER_HEADER
709             "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" {
710                 generateSetFrom("value")
711                 +"return this;"
712             }
713         }
714     }
715 }
716 
717 fun ClassPrinter.generateGetters() {
718     (fields + lazyTransientFields).forEachApply {
719         val methodPrefix = if (Type == "boolean") "is" else "get"
720         val methodName = methodPrefix + NameUpperCamel
721 
722         if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) {
723 
724             generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
725             +GENERATED_MEMBER_HEADER
726             "public $annotationsAndType $methodName()" {
727                 if (lazyInitializer == null) {
728                     +"return $name;"
729                 } else {
730                     +"$Type $_name = $name;"
731                     "if ($_name == null)" {
732                         if (fieldAst.isVolatile) {
733                             "synchronized(this)" {
734                                 +"$_name = $name;"
735                                 "if ($_name == null)" {
736                                     +"$_name = $name = $lazyInitializer();"
737                                 }
738                             }
739                         } else {
740                             +"// You can mark field as volatile for thread-safe double-check init"
741                             +"$_name = $name = $lazyInitializer();"
742                         }
743                     }
744                     +"return $_name;"
745                 }
746             }
747         }
748     }
749 }
750 
751 fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
752     if (javadocFull != null || forceHide) {
753         var hidden = false
754         (javadocFull ?: "/**\n */").lines().forEach {
755             if (it.contains("@hide")) hidden = true
756             if (it.contains("*/") && forceHide && !hidden) {
757                 if (javadocFull != null) +" *"
758                 +" * @hide"
759             }
760             +it
761         }
762     }
763 }
764 
765 fun FieldInfo.generateSetFrom(source: String) = classPrinter {
766     +"$name = $source;"
767     generateFieldValidation(field = this@generateSetFrom)
768 }
769 
770 fun ClassPrinter.generateConstructor(visibility: String = "public") {
771     if (visibility == "public") {
772         generateConstructorJavadoc()
773     }
774     +GENERATED_MEMBER_HEADER
775     "$visibility $ClassName(" {
776         fields.forEachApply {
777             +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}"
778         }
779     }
780     " {" {
781         fields.forEachApply {
782             !"this."
783             generateSetFrom(nameLowerCamel)
784         }
785 
786         generateOnConstructedCallback()
787     }
788 }
789 
790 private fun ClassPrinter.generateConstructorJavadoc(
791         fields: List<FieldInfo> = this.fields,
792         ClassName: String = this.ClassName,
793         hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) {
794     if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return
795     +"/**"
796     +" * Creates a new $ClassName."
797     +" *"
798     fields.filter { it.javadoc != null }.forEachApply {
799         javadocTextNoAnnotationLines?.apply {
800             +" * @param $nameLowerCamel"
801             forEach {
802                 +" *   $it"
803             }
804         }
805     }
806     if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide"
807     +" */"
808 }
809 
810 private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) {
811     val lines = text.lines()
812     if (lines.isNotEmpty()) {
813         !lines[0]
814     }
815     if (lines.size >= 2) {
816         "" {
817             lines.drop(1).forEach {
818                 +it
819             }
820         }
821     }
822 }
823 
824 private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run {
825     if (isNonEmpty) {
826         "if ($isEmptyExpr)" {
827             +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
828         }
829     }
830     if (intOrStringDef != null) {
831         if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
832             +""
833             "$Preconditions.checkFlagsArgument(" {
834                 +"$name, "
835                 appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| "))
836             }
837             +";"
838         } else {
839             +""
840             !"if ("
841             appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") {
842                 "!(${isEqualToExpr(it)})"
843             })
844             rmEmptyLine(); ") {" {
845                 "throw new ${classRef<IllegalArgumentException>()}(" {
846                     "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
847 
848                         intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
849                             +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
850                         }
851                     }
852                 }
853                 +";"
854             }
855         }
856     }
857 
858     val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line
859     val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter {
860         it.nameAsString != Each &&
861                 it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false
862     }
863 
864     val Size = classRef("android.annotation.Size")
865     fieldAst.annotations.filterNot {
866         it.nameAsString == intOrStringDef?.AnnotationName
867                 || it.nameAsString in knownNonValidationAnnotations
868                 || it in perElementValidations
869                 || it.args.any { (_, value) -> value is ArrayInitializerExpr }
870     }.forEach { annotation ->
871         appendValidateCall(annotation,
872                 valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
873     }
874 
875     if (perElementValidations.isNotEmpty()) {
876         +"int ${nameLowerCamel}Size = $sizeExpr;"
877         "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" {
878             perElementValidations.forEach { annotation ->
879                 appendValidateCall(annotation,
880                         valueToValidate = elemAtIndexExpr("i"))
881             }
882         }
883     }
884 }
885 
886 fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
887     val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
888     "$validate(" {
889         !"${annotation.nameAsString}.class, null, $valueToValidate"
890         annotation.args.forEach { name, value ->
891             !",\n\"$name\", $value"
892         }
893     }
894     +";"
895 }
896 
897 private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
898     +""
899     val call = "${prefix}onConstructed();"
900     if (hasMethod("onConstructed")) {
901         +call
902     } else {
903         +"// $call // You can define this method to get a callback"
904     }
905 }
906 
907 fun ClassPrinter.generateForEachField() {
908     val specializations = listOf("Object", "int")
909     val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" }
910     val usedSpecializationsSet = usedSpecializations.toSet()
911 
912     val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction")
913 
914     +GENERATED_MEMBER_HEADER
915     "void forEachField(" {
916         usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
917             val SpecType = specType.capitalize()
918             val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
919             +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
920         }
921     }; " {" {
922         usedSpecializations.forEachIndexed { i, specType ->
923             val SpecType = specType.capitalize()
924             fields[i].apply {
925                 +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);"
926             }
927         }
928     }
929 
930     if (usedSpecializationsSet.size > 1) {
931         +"/** @deprecated May cause boxing allocations - use with caution! */"
932         +"@Deprecated"
933         +GENERATED_MEMBER_HEADER
934         "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" {
935             fields.forEachApply {
936                 +"action.acceptObject(this, \"$nameLowerCamel\", $name);"
937             }
938         }
939     }
940 }
941 
942 fun ClassPrinter.generateMetadata(file: File) {
943     "@$DataClassGenerated(" {
944         +"time = ${System.currentTimeMillis()}L,"
945         +"codegenVersion = \"$CODEGEN_VERSION\","
946         +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
947         +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
948     }
949     +""
950     +"@Deprecated"
951     +"private void __metadata() {}\n"
952 }