package com.android.codegen import com.github.javaparser.ast.body.FieldDeclaration import com.github.javaparser.ast.body.MethodDeclaration import com.github.javaparser.ast.body.VariableDeclarator import com.github.javaparser.ast.expr.AnnotationExpr import com.github.javaparser.ast.expr.ArrayInitializerExpr import java.io.File /** * IntDefs and StringDefs based on constants */ fun ClassPrinter.generateConstDefs() { val consts = classAst.fields.filter { it.isStatic && it.isFinal && it.variables.all { variable -> variable.type.asString() in listOf("int", "String") } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs } }.flatMap { field -> field.variables.map { it to field } } val intConsts = consts.filter { it.first.type.asString() == "int" } val strConsts = consts.filter { it.first.type.asString() == "String" } val intGroups = intConsts.groupBy { it.first.nameAsString.split("_")[0] }.values val strGroups = strConsts.groupBy { it.first.nameAsString.split("_")[0] }.values intGroups.forEach { generateConstDef(it) } strGroups.forEach { generateConstDef(it) } } fun ClassPrinter.generateConstDef(consts: List>) { if (consts.size <= 1) return val names = consts.map { it.first.nameAsString!! } val prefix = names .reduce { a, b -> a.commonPrefixWith(b) } .dropLastWhile { it != '_' } .dropLast(1) if (prefix.isEmpty()) { println("Failed to generate const def for $names") return } var AnnotationName = prefix.split("_") .filterNot { it.isBlank() } .map { it.toLowerCase().capitalize() } .joinToString("") val annotatedConst = consts.find { it.second.annotations.isNonEmpty } if (annotatedConst != null) { AnnotationName = annotatedConst.second.annotations.first().nameAsString } val type = consts[0].first.type.asString() val flag = type == "int" && consts.all { it.first.initializer.get().toString().startsWith("0x") } val constDef = ConstDef(type = when { type == "String" -> ConstDef.Type.STRING flag -> ConstDef.Type.INT_FLAGS else -> ConstDef.Type.INT }, AnnotationName = AnnotationName, values = consts.map { it.second } ) constDefs += constDef fields.forEachApply { if (fieldAst.annotations.any { it.nameAsString == AnnotationName }) { this.intOrStringDef = constDef } } val visibility = if (consts[0].second.isPublic) "public" else "/* package-private */" val Retention = classRef("java.lang.annotation.Retention") val RetentionPolicySource = memberRef("java.lang.annotation.RetentionPolicy.SOURCE") val ConstDef = classRef("android.annotation.${type.capitalize()}Def") if (FeatureFlag.CONST_DEFS.hidden) { +"/** @hide */" } "@$ConstDef(${if_(flag, "flag = true, ")}prefix = \"${prefix}_\", value = {" { names.forEachLastAware { name, isLast -> +"$name${if_(!isLast, ",")}" } } + ")" +"@$Retention($RetentionPolicySource)" +GENERATED_MEMBER_HEADER +"$visibility @interface $AnnotationName {}" +"" if (type == "int") { if (FeatureFlag.CONST_DEFS.hidden) { +"/** @hide */" } +GENERATED_MEMBER_HEADER val methodDefLine = "$visibility static String ${AnnotationName.decapitalize()}ToString(" + "@$AnnotationName int value)" if (flag) { val flg2str = memberRef("com.android.internal.util.BitUtils.flagsToString") methodDefLine { "return $flg2str(" { +"value, $ClassName::single${AnnotationName}ToString" } + ";" } +GENERATED_MEMBER_HEADER !"static String single${AnnotationName}ToString(@$AnnotationName int value)" } else { !methodDefLine } " {" { "switch (value) {" { names.forEach { name -> "case $name:" { +"return \"$name\";" } } +"default: return Integer.toHexString(value);" } } } } fun FileInfo.generateAidl() { val aidl = File(file.path.substringBeforeLast(".java") + ".aidl") if (aidl.exists()) return aidl.writeText(buildString { sourceLines.dropLastWhile { !it.startsWith("package ") }.forEach { appendln(it) } append("\nparcelable ${mainClass.nameAsString};\n") }) } /** * ``` * Foo newFoo = oldFoo.withBar(newBar); * ``` */ fun ClassPrinter.generateWithers() { fields.forEachApply { val metodName = "with$NameUpperCamel" if (!isMethodGenerationSuppressed(metodName, Type)) { generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden) """@$NonNull $GENERATED_MEMBER_HEADER public $ClassType $metodName($annotatedTypeForSetterParam value)""" { val changedFieldName = name "return new $ClassType(" { fields.forEachTrimmingTrailingComma { if (name == changedFieldName) +"value," else +"$name," } } + ";" } } } } fun ClassPrinter.generateCopyConstructor() { if (classAst.constructors.any { it.parameters.size == 1 && it.parameters[0].type.asString() == ClassType }) { return } +"/**" +" * Copy constructor" if (FeatureFlag.COPY_CONSTRUCTOR.hidden) { +" * @hide" } +" */" +GENERATED_MEMBER_HEADER "public $ClassName(@$NonNull $ClassName orig)" { fields.forEachApply { +"$name = orig.$name;" } } } /** * ``` * Foo newFoo = oldFoo.buildUpon().setBar(newBar).build(); * ``` */ fun ClassPrinter.generateBuildUpon() { if (isMethodGenerationSuppressed("buildUpon")) return +"/**" +" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance." if (FeatureFlag.BUILD_UPON.hidden) { +" * @hide" } +" */" +GENERATED_MEMBER_HEADER "public $BuilderType buildUpon()" { "return new $BuilderType()" { fields.forEachApply { +".set$NameUpperCamel($internalGetter)" } + ";" } } } fun ClassPrinter.generateBuilder() { val setterVisibility = if (cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)) "protected" else "public" val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS) "public" else "/* package-*/" val providedSubclassAst = nestedClasses.find { it.extendedTypes.any { it.nameAsString == BASE_BUILDER_CLASS } } val BuilderSupertype = if (customBaseBuilderAst != null) { customBaseBuilderAst!!.nameAsString } else { "Object" } val maybeFinal = if_(classAst.isFinal, "final ") +"/**" +" * A builder for {@link $ClassName}" if (FeatureFlag.BUILDER.hidden) +" * @hide" +" */" +"@SuppressWarnings(\"WeakerAccess\")" +GENERATED_MEMBER_HEADER !"public static ${maybeFinal}class $BuilderClass$genericArgs" if (BuilderSupertype != "Object") { appendSameLine(" extends $BuilderSupertype") } " {" { +"" fields.forEachApply { +"private $annotationsAndType $name;" } +"" +"private long mBuilderFieldsSet = 0L;" +"" val requiredFields = fields.filter { !it.hasDefault } generateConstructorJavadoc( fields = requiredFields, ClassName = BuilderClass, hidden = false) "$constructorVisibility $BuilderClass(" { requiredFields.forEachLastAware { field, isLast -> +"${field.annotationsAndType} ${field._name}${if_(!isLast, ",")}" } }; " {" { requiredFields.forEachApply { generateSetFrom(_name) } } generateBuilderSetters(setterVisibility) generateBuilderBuild() "private void checkNotUsed() {" { "if ((mBuilderFieldsSet & ${bitAtExpr(fields.size)}) != 0)" { "throw new IllegalStateException(" { +"\"This Builder should not be reused. Use a new Builder instance instead\"" } +";" } } rmEmptyLine() } } private fun ClassPrinter.generateBuilderMethod( defVisibility: String, name: String, paramAnnotations: String? = null, paramTypes: List, paramNames: List = listOf("value"), genJavadoc: ClassPrinter.() -> Unit, genBody: ClassPrinter.() -> Unit) { val providedMethod = customBaseBuilderAst?.members?.find { it is MethodDeclaration && it.nameAsString == name && it.parameters.map { it.typeAsString } == paramTypes.toTypedArray().toList() } as? MethodDeclaration if ((providedMethod == null || providedMethod.isAbstract) && name !in builderSuppressedMembers) { val visibility = providedMethod?.visibility?.asString() ?: defVisibility val ReturnType = providedMethod?.typeAsString ?: CANONICAL_BUILDER_CLASS val Annotations = providedMethod?.annotations?.joinToString("\n") genJavadoc() +GENERATED_MEMBER_HEADER if (providedMethod?.isAbstract == true) +"@Override" if (!Annotations.isNullOrEmpty()) +Annotations val ParamAnnotations = if (!paramAnnotations.isNullOrEmpty()) "$paramAnnotations " else "" "$visibility @$NonNull $ReturnType $name(${ paramTypes.zip(paramNames).joinToString(", ") { (Type, paramName) -> "$ParamAnnotations$Type $paramName" } })" { genBody() } } } private fun ClassPrinter.generateBuilderSetters(visibility: String) { fields.forEachApply { val maybeCast = if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)") val setterName = "set$NameUpperCamel" generateBuilderMethod( name = setterName, defVisibility = visibility, paramAnnotations = annotationsForSetterParam, paramTypes = listOf(SetterParamType), genJavadoc = { generateFieldJavadoc() }) { +"checkNotUsed();" +"mBuilderFieldsSet |= $fieldBit;" +"$name = value;" +"return$maybeCast this;" } val javadocSeeSetter = "/** @see #$setterName */" val adderName = "add$SingularName" val singularNameCustomizationHint = if (SingularNameOrNull == null) { "// You can refine this method's name by providing item's singular name, e.g.:\n" + "// @DataClass.PluralOf(\"item\")) mItems = ...\n\n" } else "" if (isList && FieldInnerType != null) { generateBuilderMethod( name = adderName, defVisibility = visibility, paramAnnotations = "@$NonNull", paramTypes = listOf(FieldInnerType), genJavadoc = { +javadocSeeSetter }) { !singularNameCustomizationHint +"if ($name == null) $setterName(new $ArrayList<>());" +"$name.add(value);" +"return$maybeCast this;" } } if (isMap && FieldInnerType != null) { generateBuilderMethod( name = adderName, defVisibility = visibility, paramAnnotations = "@$NonNull", paramTypes = fieldTypeGenegicArgs, paramNames = listOf("key", "value"), genJavadoc = { +javadocSeeSetter }) { !singularNameCustomizationHint +"if ($name == null) $setterName(new ${if (FieldClass == "Map") LinkedHashMap else FieldClass}());" +"$name.put(key, value);" +"return$maybeCast this;" } } } } private fun ClassPrinter.generateBuilderBuild() { +"/** Builds the instance. This builder should not be touched after calling this! */" "public @$NonNull $ClassType build()" { +"checkNotUsed();" +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used" +"" fields.forEachApply { if (hasDefault) { "if ((mBuilderFieldsSet & $fieldBit) == 0)" { +"$name = $defaultExpr;" } } } "$ClassType o = new $ClassType(" { fields.forEachTrimmingTrailingComma { +"$name," } } + ";" +"return o;" } } fun ClassPrinter.generateParcelable() { val booleanFields = fields.filter { it.Type == "boolean" } val objectFields = fields.filter { it.Type !in PRIMITIVE_TYPES } val nullableFields = objectFields.filter { it.mayBeNull && it.Type !in PRIMITIVE_ARRAY_TYPES } val nonBooleanFields = fields - booleanFields val flagStorageType = when (fields.size) { in 0..7 -> "byte" in 8..15 -> "int" in 16..31 -> "long" else -> throw NotImplementedError("32+ field classes not yet supported") } val FlagStorageType = flagStorageType.capitalize() fields.forEachApply { if (sParcelling != null) { +GENERATED_MEMBER_HEADER "static $Parcelling<$Type> $sParcelling =" { "$Parcelling.Cache.get(" { +"$customParcellingClass.class" } + ";" } "static {" { "if ($sParcelling == null)" { "$sParcelling = $Parcelling.Cache.put(" { +"new $customParcellingClass()" } + ";" } } +"" } } val Parcel = classRef("android.os.Parcel") if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) { +"@Override" +GENERATED_MEMBER_HEADER "public void writeToParcel(@$NonNull $Parcel dest, int flags)" { +"// You can override field parcelling by defining methods like:" +"// void parcelFieldName(Parcel dest, int flags) { ... }" +"" if (extendsParcelableClass) { +"super.writeToParcel(dest, flags);\n" } if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) { +"$flagStorageType flg = 0;" booleanFields.forEachApply { +"if ($internalGetter) flg |= $fieldBit;" } nullableFields.forEachApply { +"if ($internalGetter != null) flg |= $fieldBit;" } +"dest.write$FlagStorageType(flg);" } nonBooleanFields.forEachApply { val customParcellingMethod = "parcel$NameUpperCamel" when { hasMethod(customParcellingMethod, Parcel, "int") -> +"$customParcellingMethod(dest, flags);" customParcellingClass != null -> +"$sParcelling.parcel($name, dest, flags);" hasAnnotation("@$DataClassEnum") -> +"dest.writeInt($internalGetter == null ? -1 : $internalGetter.ordinal());" else -> { if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) !"if ($internalGetter != null) " var args = internalGetter if (ParcelMethodsSuffix.startsWith("Parcelable") || ParcelMethodsSuffix.startsWith("TypedObject") || ParcelMethodsSuffix == "TypedArray") { args += ", flags" } +"dest.write$ParcelMethodsSuffix($args);" } } } } } if (!isMethodGenerationSuppressed("describeContents")) { +"@Override" +GENERATED_MEMBER_HEADER +"public int describeContents() { return 0; }" +"" } if (!hasMethod(ClassName, Parcel)) { val visibility = if (classAst.isFinal) "/* package-private */" else "protected" +"/** @hide */" +"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})" +GENERATED_MEMBER_HEADER "$visibility $ClassName(@$NonNull $Parcel in) {" { +"// You can override field unparcelling by defining methods like:" +"// static FieldType unparcelFieldName(Parcel in) { ... }" +"" if (extendsParcelableClass) { +"super(in);\n" } if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) { +"$flagStorageType flg = in.read$FlagStorageType();" } booleanFields.forEachApply { +"$Type $_name = (flg & $fieldBit) != 0;" } nonBooleanFields.forEachApply { // Handle customized parceling val customParcellingMethod = "unparcel$NameUpperCamel" if (hasMethod(customParcellingMethod, Parcel)) { +"$Type $_name = $customParcellingMethod(in);" } else if (customParcellingClass != null) { +"$Type $_name = $sParcelling.unparcel(in);" } else if (hasAnnotation("@$DataClassEnum")) { val ordinal = "${_name}Ordinal" +"int $ordinal = in.readInt();" +"$Type $_name = $ordinal < 0 ? null : $FieldClass.values()[$ordinal];" } else { val methodArgs = mutableListOf() // Create container if any val containerInitExpr = when { FieldClass == "Map" -> "new $LinkedHashMap<>()" isMap -> "new $FieldClass()" FieldClass == "List" || FieldClass == "ArrayList" -> "new ${classRef("java.util.ArrayList")}<>()" else -> "" } val passContainer = containerInitExpr.isNotEmpty() // nullcheck + // "FieldType fieldName = (FieldType)" if (passContainer) { methodArgs.add(_name) !"$Type $_name = " if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { +"null;" !"if ((flg & $fieldBit) != 0) {" pushIndent() +"" !"$_name = " } +"$containerInitExpr;" } else { !"$Type $_name = " if (mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { !"(flg & $fieldBit) == 0 ? null : " } if (ParcelMethodsSuffix == "StrongInterface") { !"$FieldClass.Stub.asInterface(" } else if (Type !in PRIMITIVE_TYPES + "String" + "Bundle" && (!isArray || FieldInnerType !in PRIMITIVE_TYPES + "String") && ParcelMethodsSuffix != "Parcelable") { !"($FieldClass) " } } // Determine method args when { ParcelMethodsSuffix == "Parcelable" -> methodArgs += "$FieldClass.class.getClassLoader()" ParcelMethodsSuffix == "SparseArray" -> methodArgs += "$FieldInnerClass.class.getClassLoader()" ParcelMethodsSuffix == "TypedObject" -> methodArgs += "$FieldClass.CREATOR" ParcelMethodsSuffix == "TypedArray" -> methodArgs += "$FieldInnerClass.CREATOR" ParcelMethodsSuffix == "Map" -> methodArgs += "${fieldTypeGenegicArgs[1].substringBefore("<")}.class.getClassLoader()" ParcelMethodsSuffix.startsWith("Parcelable") || (isList || isArray) && FieldInnerType !in PRIMITIVE_TYPES + "String" -> methodArgs += "$FieldInnerClass.class.getClassLoader()" } // ...in.readFieldType(args...); when { ParcelMethodsSuffix == "StrongInterface" -> !"in.readStrongBinder" isArray -> !"in.create$ParcelMethodsSuffix" else -> !"in.read$ParcelMethodsSuffix" } !"(${methodArgs.joinToString(", ")})" if (ParcelMethodsSuffix == "StrongInterface") !")" +";" // Cleanup if passContainer if (passContainer && mayBeNull && Type !in PRIMITIVE_ARRAY_TYPES) { popIndent() rmEmptyLine() +"\n}" } } } +"" fields.forEachApply { !"this." generateSetFrom(_name) } generateOnConstructedCallback() } } if (classAst.fields.none { it.variables[0].nameAsString == "CREATOR" }) { val Creator = classRef("android.os.Parcelable.Creator") +GENERATED_MEMBER_HEADER "public static final @$NonNull $Creator<$ClassName> CREATOR" { +"= new $Creator<$ClassName>()" }; " {" { +"@Override" "public $ClassName[] newArray(int size)" { +"return new $ClassName[size];" } +"@Override" "public $ClassName createFromParcel(@$NonNull $Parcel in)" { +"return new $ClassName(in);" } rmEmptyLine() } + ";" +"" } } fun ClassPrinter.generateEqualsHashcode() { if (!isMethodGenerationSuppressed("equals", "Object")) { +"@Override" +GENERATED_MEMBER_HEADER "public boolean equals(@$Nullable Object o)" { +"// You can override field equality logic by defining either of the methods like:" +"// boolean fieldNameEquals($ClassName other) { ... }" +"// boolean fieldNameEquals(FieldType otherValue) { ... }" +"" """if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @SuppressWarnings("unchecked") $ClassType that = ($ClassType) o; //noinspection PointlessBooleanExpression return true""" { fields.forEachApply { val sfx = if (isLast) ";" else "" val customEquals = "${nameLowerCamel}Equals" when { hasMethod(customEquals, Type) -> +"&& $customEquals(that.$internalGetter)$sfx" hasMethod(customEquals, ClassType) -> +"&& $customEquals(that)$sfx" else -> +"&& ${isEqualToExpr("that.$internalGetter")}$sfx" } } } } } if (!isMethodGenerationSuppressed("hashCode")) { +"@Override" +GENERATED_MEMBER_HEADER "public int hashCode()" { +"// You can override field hashCode logic by defining methods like:" +"// int fieldNameHashCode() { ... }" +"" +"int _hash = 1;" fields.forEachApply { !"_hash = 31 * _hash + " val customHashCode = "${nameLowerCamel}HashCode" when { hasMethod(customHashCode) -> +"$customHashCode();" Type == "int" || Type == "byte" -> +"$internalGetter;" Type in PRIMITIVE_TYPES -> +"${Type.capitalize()}.hashCode($internalGetter);" isArray -> +"${memberRef("java.util.Arrays.hashCode")}($internalGetter);" else -> +"${memberRef("java.util.Objects.hashCode")}($internalGetter);" } } +"return _hash;" } } } //TODO support IntDef flags? fun ClassPrinter.generateToString() { if (!isMethodGenerationSuppressed("toString")) { +"@Override" +GENERATED_MEMBER_HEADER "public String toString()" { +"// You can override field toString logic by defining methods like:" +"// String fieldNameToString() { ... }" +"" "return \"$ClassName { \" +" { fields.forEachApply { val customToString = "${nameLowerCamel}ToString" val expr = when { hasMethod(customToString) -> "$customToString()" isArray -> "${memberRef("java.util.Arrays.toString")}($internalGetter)" intOrStringDef?.type?.isInt == true -> "${intOrStringDef!!.AnnotationName.decapitalize()}ToString($name)" else -> internalGetter } +"\"$nameLowerCamel = \" + $expr${if_(!isLast, " + \", \"")} +" } } +"\" }\";" } } } fun ClassPrinter.generateSetters() { fields.forEachApply { if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type) && !fieldAst.isPublic && !isFinal) { generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden) +GENERATED_MEMBER_HEADER "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { generateSetFrom("value") +"return this;" } } } } fun ClassPrinter.generateGetters() { (fields + lazyTransientFields).forEachApply { val methodPrefix = if (Type == "boolean") "is" else "get" val methodName = methodPrefix + NameUpperCamel if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) { generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden) +GENERATED_MEMBER_HEADER "public $annotationsAndType $methodName()" { if (lazyInitializer == null) { +"return $name;" } else { +"$Type $_name = $name;" "if ($_name == null)" { if (fieldAst.isVolatile) { "synchronized(this)" { +"$_name = $name;" "if ($_name == null)" { +"$_name = $name = $lazyInitializer();" } } } else { +"// You can mark field as volatile for thread-safe double-check init" +"$_name = $name = $lazyInitializer();" } } +"return $_name;" } } } } } fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter { if (javadocFull != null || forceHide) { var hidden = false (javadocFull ?: "/**\n */").lines().forEach { if (it.contains("@hide")) hidden = true if (it.contains("*/") && forceHide && !hidden) { if (javadocFull != null) +" *" +" * @hide" } +it } } } fun FieldInfo.generateSetFrom(source: String) = classPrinter { +"$name = $source;" generateFieldValidation(field = this@generateSetFrom) } fun ClassPrinter.generateConstructor(visibility: String = "public") { if (visibility == "public") { generateConstructorJavadoc() } +GENERATED_MEMBER_HEADER "$visibility $ClassName(" { fields.forEachApply { +"$annotationsAndType $nameLowerCamel${if_(!isLast, ",")}" } } " {" { fields.forEachApply { !"this." generateSetFrom(nameLowerCamel) } generateOnConstructedCallback() } } private fun ClassPrinter.generateConstructorJavadoc( fields: List = this.fields, ClassName: String = this.ClassName, hidden: Boolean = FeatureFlag.CONSTRUCTOR.hidden) { if (fields.all { it.javadoc == null } && !FeatureFlag.CONSTRUCTOR.hidden) return +"/**" +" * Creates a new $ClassName." +" *" fields.filter { it.javadoc != null }.forEachApply { javadocTextNoAnnotationLines?.apply { +" * @param $nameLowerCamel" forEach { +" * $it" } } } if (FeatureFlag.CONSTRUCTOR.hidden) +" * @hide" +" */" } private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) { val lines = text.lines() if (lines.isNotEmpty()) { !lines[0] } if (lines.size >= 2) { "" { lines.drop(1).forEach { +it } } } } private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run { if (isNonEmpty) { "if ($isEmptyExpr)" { +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");" } } if (intOrStringDef != null) { if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) { +"" "$Preconditions.checkFlagsArgument(" { +"$name, " appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n| ")) } +";" } else { +"" !"if (" appendLinesWithContinuationIndent(intOrStringDef!!.CONST_NAMES.joinToString("\n&& ") { "!(${isEqualToExpr(it)})" }) rmEmptyLine(); ") {" { "throw new ${classRef()}(" { "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" { intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast -> +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}"""" } } } +";" } } } val eachLine = fieldAst.annotations.find { it.nameAsString == Each }?.range?.orElse(null)?.end?.line val perElementValidations = if (eachLine == null) emptyList() else fieldAst.annotations.filter { it.nameAsString != Each && it.range.orElse(null)?.begin?.line?.let { it >= eachLine } ?: false } val Size = classRef("android.annotation.Size") fieldAst.annotations.filterNot { it.nameAsString == intOrStringDef?.AnnotationName || it.nameAsString in knownNonValidationAnnotations || it in perElementValidations || it.args.any { (_, value) -> value is ArrayInitializerExpr } }.forEach { annotation -> appendValidateCall(annotation, valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name) } if (perElementValidations.isNotEmpty()) { +"int ${nameLowerCamel}Size = $sizeExpr;" "for (int i = 0; i < ${nameLowerCamel}Size; i++) {" { perElementValidations.forEach { annotation -> appendValidateCall(annotation, valueToValidate = elemAtIndexExpr("i")) } } } } fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) { val validate = memberRef("com.android.internal.util.AnnotationValidations.validate") "$validate(" { !"${annotation.nameAsString}.class, null, $valueToValidate" annotation.args.forEach { name, value -> !",\n\"$name\", $value" } } +";" } private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") { +"" val call = "${prefix}onConstructed();" if (hasMethod("onConstructed")) { +call } else { +"// $call // You can define this method to get a callback" } } fun ClassPrinter.generateForEachField() { val specializations = listOf("Object", "int") val usedSpecializations = fields.map { if (it.Type in specializations) it.Type else "Object" } val usedSpecializationsSet = usedSpecializations.toSet() val PerObjectFieldAction = classRef("com.android.internal.util.DataClass.PerObjectFieldAction") +GENERATED_MEMBER_HEADER "void forEachField(" { usedSpecializationsSet.toList().forEachLastAware { specType, isLast -> val SpecType = specType.capitalize() val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction") +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}" } }; " {" { usedSpecializations.forEachIndexed { i, specType -> val SpecType = specType.capitalize() fields[i].apply { +"action$SpecType.accept$SpecType(this, \"$nameLowerCamel\", $name);" } } } if (usedSpecializationsSet.size > 1) { +"/** @deprecated May cause boxing allocations - use with caution! */" +"@Deprecated" +GENERATED_MEMBER_HEADER "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" { fields.forEachApply { +"action.acceptObject(this, \"$nameLowerCamel\", $name);" } } } } fun ClassPrinter.generateMetadata(file: File) { "@$DataClassGenerated(" { +"time = ${System.currentTimeMillis()}L," +"codegenVersion = \"$CODEGEN_VERSION\"," +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\"," +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\"" } +"" +"@Deprecated" +"private void __metadata() {}\n" }