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