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