<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 }