summaryrefslogtreecommitdiff
path: root/tools/codegen/src
diff options
context:
space:
mode:
authorEugene Susla <eugenesusla@google.com>2019-07-25 14:05:12 -0700
committerEugene Susla <eugenesusla@google.com>2019-08-05 16:54:41 -0700
commit3156a4ce21cb4de46f84b8c7264a3dc31dd8db8b (patch)
treecf2a361a56d6f45de239da3ff5f4fa58e1d59431 /tools/codegen/src
parent2eaec69928b0394b7e6979c71a707d1b2418365c (diff)
Addresses further review comments from ag/8000041
Including: - An API to opt out of Int/StringDefs generation on per-field basis - A way to customize Builder - Non-optional fields are passed in Builder constructor - Various adjustments to SampleDataclass examples, as requested Test: . $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/runTest.sh Change-Id: I32d2eec52f05d505ff07779d923e4793d3036579
Diffstat (limited to 'tools/codegen/src')
-rw-r--r--tools/codegen/src/com/android/codegen/ClassInfo.kt12
-rw-r--r--tools/codegen/src/com/android/codegen/ClassPrinter.kt79
-rw-r--r--tools/codegen/src/com/android/codegen/FieldInfo.kt9
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt344
-rw-r--r--tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt43
-rwxr-xr-xtools/codegen/src/com/android/codegen/Main.kt22
-rw-r--r--tools/codegen/src/com/android/codegen/SharedConstants.kt5
-rw-r--r--tools/codegen/src/com/android/codegen/Utils.kt17
8 files changed, 343 insertions, 188 deletions
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
index 7ee79f651274..578fb2898480 100644
--- a/tools/codegen/src/com/android/codegen/ClassInfo.kt
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -18,13 +18,7 @@ open class ClassInfo(val sourceLines: List<String>) {
e)
}
val classAst = fileAst.types[0] as ClassOrInterfaceDeclaration
-
- fun hasMethod(name: String, vararg argTypes: String): Boolean {
- return classAst.methods.any {
- it.name.asString() == name &&
- it.parameters.map { it.type.asString() } == argTypes.toList()
- }
- }
+ val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
val superInterfaces = (fileAst.types[0] as ClassOrInterfaceDeclaration)
.implementedTypes.map { it.asString() }
@@ -42,8 +36,4 @@ open class ClassInfo(val sourceLines: List<String>) {
.filterNot { it.isTransient || it.isStatic }
.mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
.apply { lastOrNull()?.isLast = true }
- val lazyTransientFields = classAst.fields
- .filter { it.isTransient && !it.isStatic }
- .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
- .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
index 33256b787964..f1645ea9a3bb 100644
--- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt
+++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt
@@ -1,9 +1,9 @@
package com.android.codegen
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.TypeDeclaration
-import com.github.javaparser.ast.expr.BooleanLiteralExpr
-import com.github.javaparser.ast.expr.NormalAnnotationExpr
+import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.type.ClassOrInterfaceType
/**
@@ -32,10 +32,31 @@ class ClassPrinter(
val PluralOf by lazy { classRef("com.android.internal.util.DataClass.PluralOf") }
val Each by lazy { classRef("com.android.internal.util.DataClass.Each") }
val DataClassGenerated by lazy { classRef("com.android.internal.util.DataClass.Generated") }
+ val DataClassSuppressConstDefs by lazy { classRef("com.android.internal.util.DataClass.SuppressConstDefsGeneration") }
+ val DataClassSuppress by lazy { classRef("com.android.internal.util.DataClass.Suppress") }
val GeneratedMember by lazy { classRef("com.android.internal.util.DataClass.Generated.Member") }
val Parcelling by lazy { classRef("com.android.internal.util.Parcelling") }
+ val Parcelable by lazy { classRef("android.os.Parcelable") }
val UnsupportedAppUsage by lazy { classRef("android.annotation.UnsupportedAppUsage") }
+ init {
+ val fieldsWithMissingNullablity = fields.filter { field ->
+ !field.isPrimitive
+ && Modifier.TRANSIENT !in field.fieldAst.modifiers
+ && "@$Nullable" !in field.annotations
+ && "@$NonNull" !in field.annotations
+ }
+ if (fieldsWithMissingNullablity.isNotEmpty()) {
+ abort("Non-primitive fields must have @$Nullable or @$NonNull annotation.\n" +
+ "Missing nullability annotations on: "
+ + fieldsWithMissingNullablity.joinToString(", ") { it.name })
+ }
+
+ if (!classAst.isFinal &&
+ classAst.extendedTypes.any { it.nameAsString == Parcelable }) {
+ abort("Parcelable classes must be final")
+ }
+ }
/**
* Optionally shortens a class reference if there's a corresponding import present
@@ -54,7 +75,7 @@ class ClassPrinter(
return simpleName
} else {
val outerClass = pkg.substringAfterLast(".", "")
- if (outerClass.firstOrNull()?.isUpperCase() ?: false) {
+ if (outerClass.firstOrNull()?.isUpperCase() == true) {
return classRef(pkg) + "." + simpleName
}
}
@@ -89,7 +110,9 @@ class ClassPrinter(
?.toMap()
?: emptyMap()
- val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, Each, UnsupportedAppUsage)
+ val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, UnsupportedAppUsage,
+ DataClassSuppressConstDefs)
+ val knownNonValidationAnnotations = internalAnnotations + Each + Nullable
/**
* @return whether the given feature is enabled
@@ -109,7 +132,9 @@ class ClassPrinter(
return when (this) {
FeatureFlag.SETTERS ->
!FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal }
- FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS) || onByDefault
+ FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)
+ || fields.any { it.hasDefault }
+ || onByDefault
FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
FeatureFlag.AIDL -> FeatureFlag.PARCELABLE()
@@ -287,6 +312,48 @@ class ClassPrinter(
var BuilderClass = CANONICAL_BUILDER_CLASS
var BuilderType = BuilderClass + genericArgs
+ val customBaseBuilderAst: ClassOrInterfaceDeclaration? by lazy {
+ nestedClasses.find { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val suppressedMembers by lazy {
+ getSuppressedMembers(classAst)
+ }
+ val builderSuppressedMembers by lazy {
+ getSuppressedMembers(customBaseBuilderAst)
+ }
+
+ private fun getSuppressedMembers(clazz: ClassOrInterfaceDeclaration?): List<String> {
+ return clazz
+ ?.annotations
+ ?.find { it.nameAsString == DataClassSuppress }
+ ?.as_<SingleMemberAnnotationExpr>()
+ ?.memberValue
+ ?.run {
+ when (this) {
+ is ArrayInitializerExpr -> values.map { it.asLiteralStringValueExpr().value }
+ is StringLiteralExpr -> listOf(value)
+ else -> abort("Can't parse annotation arg: $this")
+ }
+ }
+ ?: emptyList()
+ }
+
+ fun isMethodGenerationSuppressed(name: String, vararg argTypes: String): Boolean {
+ return name in suppressedMembers || hasMethod(name, *argTypes)
+ }
+
+ fun hasMethod(name: String, vararg argTypes: String): Boolean {
+ return classAst.methods.any {
+ it.name.asString() == name &&
+ it.parameters.map { it.type.asString() } == argTypes.toList()
+ }
+ }
+
+ val lazyTransientFields = classAst.fields
+ .filter { it.isTransient && !it.isStatic }
+ .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
+ .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
init {
val builderFactoryOverride = classAst.methods.find {
@@ -301,7 +368,7 @@ class ClassPrinter(
it.nameAsString == CANONICAL_BUILDER_CLASS
}
if (builderExtension != null) {
- BuilderClass = GENERATED_BUILDER_CLASS
+ BuilderClass = BASE_BUILDER_CLASS
val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters
BuilderType = if (tp.isEmpty()) BuilderClass
else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>"
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index f326fd5601fe..74e79489ad7d 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -9,7 +9,6 @@ import com.github.javaparser.ast.expr.StringLiteralExpr
import com.github.javaparser.ast.type.ArrayType
import com.github.javaparser.ast.type.ClassOrInterfaceType
import com.github.javaparser.javadoc.Javadoc
-import java.lang.Long
data class FieldInfo(
val index: Int,
@@ -85,7 +84,7 @@ data class FieldInfo(
variableAst.initializer.orElse(null)?.let { return it }
classInfo.classAst.methods.find {
it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty()
- }?.run { "$nameAsString()" }?.let { return it }
+ }?.run { return "$nameAsString()" }
if (FieldClass == "List") return "${classPrinter.memberRef("java.util.Collections.emptyList")}()"
return null
}
@@ -95,7 +94,7 @@ data class FieldInfo(
// Generic args
val isArray = Type.endsWith("[]")
val isList = FieldClass == "List" || FieldClass == "ArrayList"
- val fieldBit = "0x${Long.toHexString(1L shl index)}"
+ val fieldBit = bitAtExpr(index)
var isLast = false
val isFinal = fieldAst.isFinal
val fieldTypeGenegicArgs = when (typeAst) {
@@ -143,8 +142,10 @@ data class FieldInfo(
}
val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") }
val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } }
+
+ val SetterParamType = if (isArray) "$FieldInnerType..." else Type
val annotatedTypeForSetterParam by lazy {
- (annotationsNoInternal + if (isArray) "$FieldInnerType..." else Type).joinToString(" ")
+ (annotationsNoInternal + SetterParamType).joinToString(" ")
}
// Utilities
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index ab64f4efc8d8..c6e0a064f9b4 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -1,6 +1,7 @@
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.*
import java.io.File
@@ -16,7 +17,7 @@ fun ClassPrinter.generateConstDefs() {
val isLiteral = initializer is LiteralExpr
|| (initializer is UnaryExpr && initializer.expression is LiteralExpr)
isLiteral && 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" }
@@ -131,7 +132,7 @@ fun ClassPrinter.generateAidl(javaFile: File) {
fun ClassPrinter.generateWithers() {
fields.forEachApply {
val metodName = "with$NameUpperCamel"
- if (!hasMethod(metodName, Type)) {
+ if (!isMethodGenerationSuppressed(metodName, Type)) {
generateFieldJavadoc(forceHide = FeatureFlag.WITHERS.hidden)
"""@$NonNull
$GENERATED_MEMBER_HEADER
@@ -171,7 +172,7 @@ fun ClassPrinter.generateCopyConstructor() {
* ```
*/
fun ClassPrinter.generateBuildUpon() {
- if (hasMethod("buildUpon")) return
+ if (isMethodGenerationSuppressed("buildUpon")) return
+"/**"
+" * Provides an instance of {@link $BuilderClass} with state corresponding to this instance."
@@ -195,7 +196,15 @@ fun ClassPrinter.generateBuilder() {
val constructorVisibility = if (BuilderClass == CANONICAL_BUILDER_CLASS)
"public" else "/* package-*/"
- val OneTimeUseBuilder = classRef("android.provider.OneTimeUseBuilder")
+ val providedSubclassAst = nestedClasses.find {
+ it.extendedTypes.any { it.nameAsString == BASE_BUILDER_CLASS }
+ }
+
+ val BuilderSupertype = if (customBaseBuilderAst != null) {
+ customBaseBuilderAst!!.nameAsString
+ } else {
+ "Object"
+ }
+"/**"
+" * A builder for {@link $ClassName}"
@@ -203,104 +212,155 @@ fun ClassPrinter.generateBuilder() {
+" */"
+"@SuppressWarnings(\"WeakerAccess\")"
+GENERATED_MEMBER_HEADER
- "public static class $BuilderClass$genericArgs" {
- +"extends $OneTimeUseBuilder<$ClassType>"
+ !"public static class $BuilderClass$genericArgs"
+ if (BuilderSupertype != "Object") {
+ appendSameLine(" extends $BuilderSupertype")
}
" {" {
+""
fields.forEachApply {
- +"protected $annotationsAndType $name;"
+ +"private $annotationsAndType $name;"
}
+""
- +"protected long mBuilderFieldsSet = 0L;"
- +""
- +"$constructorVisibility $BuilderClass() {};"
+ +"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<String>,
+ paramNames: List<String> = 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
+ "$visibility @$NonNull $ReturnType $name(${if_(!ParamAnnotations.isNullOrEmpty(), "$ParamAnnotations ")}${
+ paramTypes.zip(paramNames).joinToString(", ") { (Type, paramName) -> "$Type $paramName" }
+ })" {
+ genBody()
+ }
+ }
+}
+
private fun ClassPrinter.generateBuilderSetters(visibility: String) {
fields.forEachApply {
val maybeCast =
if_(BuilderClass != CANONICAL_BUILDER_CLASS, " ($CANONICAL_BUILDER_CLASS)")
- generateFieldJavadoc()
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS set$NameUpperCamel($annotatedTypeForSetterParam value)" {
+ val setterName = "set$NameUpperCamel"
+
+ generateBuilderMethod(
+ name = setterName,
+ defVisibility = visibility,
+ ParamAnnotations = annotationsNoInternal.joinToString(" "),
+ paramTypes = listOf(SetterParamType),
+ genJavadoc = { generateFieldJavadoc() }) {
+"checkNotUsed();"
+"mBuilderFieldsSet |= $fieldBit;"
+"$name = value;"
+"return$maybeCast this;"
}
+ val javadocSeeSetter = "/** @see #$setterName */"
+ val adderName = "add$SingularName"
- val javadocSeeSetter = "/** @see #set$NameUpperCamel */"
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,
+ paramTypes = listOf(FieldInnerType),
+ genJavadoc = { +javadocSeeSetter }) {
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS add$SingularName(@$NonNull $FieldInnerType value)" {
!singularNameCustomizationHint
- +"if ($name == null) set$NameUpperCamel(new $ArrayList<>());"
+ +"if ($name == null) $setterName(new $ArrayList<>());"
+"$name.add(value);"
+"return$maybeCast this;"
}
}
if (Type.contains("Map<")) {
- val (Key, Value) = fieldTypeGenegicArgs
-
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS add$SingularName($Key key, $Value value)" {
+ generateBuilderMethod(
+ name = adderName,
+ defVisibility = visibility,
+ paramTypes = fieldTypeGenegicArgs,
+ paramNames = listOf("key", "value"),
+ genJavadoc = { +javadocSeeSetter }) {
!singularNameCustomizationHint
- +"if ($name == null) set$NameUpperCamel(new $LinkedHashMap());"
+ +"if ($name == null) $setterName(new $LinkedHashMap());"
+"$name.put(key, value);"
+"return$maybeCast this;"
}
}
-
- if (Type == "boolean") {
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS mark$NameUpperCamel()" {
- +"return set$NameUpperCamel(true);"
- }
-
- +javadocSeeSetter
- +GENERATED_MEMBER_HEADER
- "$visibility $CANONICAL_BUILDER_CLASS markNot$NameUpperCamel()" {
- +"return set$NameUpperCamel(false);"
- }
- }
}
}
private fun ClassPrinter.generateBuilderBuild() {
+"/** Builds the instance. This builder should not be touched after calling this! */"
"public $ClassType build()" {
- +"markUsed();"
+ +"checkNotUsed();"
+ +"mBuilderFieldsSet |= ${bitAtExpr(fields.size)}; // Mark builder used"
+ +""
fields.forEachApply {
- if (!isNullable || hasDefault) {
+ if (hasDefault) {
"if ((mBuilderFieldsSet & $fieldBit) == 0)" {
- if (!isNullable && !hasDefault) {
- +"throw new IllegalStateException(\"Required field not set: $nameLowerCamel\");"
- } else {
- +"$name = $defaultExpr;"
- }
+ +"$name = $defaultExpr;"
}
}
}
@@ -348,7 +408,7 @@ fun ClassPrinter.generateParcelable() {
}
val Parcel = classRef("android.os.Parcel")
- if (!hasMethod("writeToParcel", Parcel, "int")) {
+ if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public void writeToParcel($Parcel dest, int flags)" {
@@ -390,7 +450,7 @@ fun ClassPrinter.generateParcelable() {
}
}
- if (!hasMethod("describeContents")) {
+ if (!isMethodGenerationSuppressed("describeContents")) {
+"@Override"
+GENERATED_MEMBER_HEADER
+"public int describeContents() { return 0; }"
@@ -442,8 +502,6 @@ fun ClassPrinter.generateParcelable() {
FieldClass.endsWith("Map") -> "new $LinkedHashMap<>()"
FieldClass == "List" || FieldClass == "ArrayList" ->
"new ${classRef("java.util.ArrayList")}<>()"
-// isArray && FieldInnerType in (PRIMITIVE_TYPES + "String") ->
-// "new $FieldInnerType[in.readInt()]"
else -> ""
}
val passContainer = containerInitExpr.isNotEmpty()
@@ -519,7 +577,7 @@ fun ClassPrinter.generateParcelable() {
}
fun ClassPrinter.generateEqualsHashcode() {
- if (!hasMethod("equals", "Object")) {
+ if (!isMethodGenerationSuppressed("equals", "Object")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public boolean equals(Object o)" {
@@ -546,7 +604,7 @@ fun ClassPrinter.generateEqualsHashcode() {
}
}
- if (!hasMethod("hashCode")) {
+ if (!isMethodGenerationSuppressed("hashCode")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public int hashCode()" {
@@ -572,7 +630,7 @@ fun ClassPrinter.generateEqualsHashcode() {
//TODO support IntDef flags?
fun ClassPrinter.generateToString() {
- if (!hasMethod("toString")) {
+ if (!isMethodGenerationSuppressed("toString")) {
+"@Override"
+GENERATED_MEMBER_HEADER
"public String toString()" {
@@ -599,7 +657,7 @@ fun ClassPrinter.generateToString() {
fun ClassPrinter.generateSetters() {
fields.forEachApply {
- if (!hasMethod("set$NameUpperCamel", Type)
+ if (!isMethodGenerationSuppressed("set$NameUpperCamel", Type)
&& !fieldAst.isPublic
&& !isFinal) {
@@ -618,7 +676,7 @@ fun ClassPrinter.generateGetters() {
val methodPrefix = if (Type == "boolean") "is" else "get"
val methodName = methodPrefix + NameUpperCamel
- if (!hasMethod(methodName) && !fieldAst.isPublic) {
+ if (!isMethodGenerationSuppressed(methodName) && !fieldAst.isPublic) {
generateFieldJavadoc(forceHide = FeatureFlag.GETTERS.hidden)
+GENERATED_MEMBER_HEADER
@@ -662,23 +720,8 @@ fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
}
fun FieldInfo.generateSetFrom(source: String) = classPrinter {
- !"$name = "
- if (Type in PRIMITIVE_TYPES || mayBeNull) {
- +"$source;"
- } else if (defaultExpr != null) {
- "$source != null" {
- +"? $source"
- +": $defaultExpr;"
- }
- } else {
- val checkNotNull = memberRef("com.android.internal.util.Preconditions.checkNotNull")
- +"$checkNotNull($source);"
- }
- if (isNonEmpty) {
- "if ($isEmptyExpr)" {
- +"throw new IllegalArgumentException(\"$nameLowerCamel cannot be empty\");"
- }
- }
+ +"$name = $source;"
+ generateFieldValidation(field = this@generateSetFrom)
}
fun ClassPrinter.generateConstructor(visibility: String = "public") {
@@ -697,15 +740,18 @@ fun ClassPrinter.generateConstructor(visibility: String = "public") {
generateSetFrom(nameLowerCamel)
}
- generateStateValidation()
-
generateOnConstructedCallback()
}
}
-private fun ClassPrinter.generateConstructorJavadoc() {
+private fun ClassPrinter.generateConstructorJavadoc(
+ fields: List<FieldInfo> = 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"
@@ -718,87 +764,97 @@ private fun ClassPrinter.generateConstructorJavadoc() {
+" */"
}
-private fun ClassPrinter.generateStateValidation() {
- val Size = classRef("android.annotation.Size")
- val knownNonValidationAnnotations = internalAnnotations + Nullable
-
- val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
- fun appendValidateCall(annotation: AnnotationExpr, valueToValidate: String) {
- "$validate(" {
- !"${annotation.nameAsString}.class, null, $valueToValidate"
- val params = when (annotation) {
- is MarkerAnnotationExpr -> emptyMap()
- is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
- is NormalAnnotationExpr ->
- annotation.pairs.map { it.name.asString() to it.value }.toMap()
- else -> throw IllegalStateException()
- }
- params.forEach { name, value ->
- !",\n\"$name\", $value"
+private fun ClassPrinter.appendLinesWithContinuationIndent(text: String) {
+ val lines = text.lines()
+ if (lines.isNotEmpty()) {
+ !lines[0]
+ }
+ if (lines.size >= 2) {
+ "" {
+ lines.drop(1).forEach {
+ +it
}
}
- +";"
}
+}
- fields.forEachApply {
- if (intOrStringDef != null) {
- if (intOrStringDef!!.type == ConstDef.Type.INT_FLAGS) {
- +""
- +"//noinspection PointlessBitwiseExpression"
- "$Preconditions.checkFlagsArgument(" {
- "$name, 0" {
- intOrStringDef!!.CONST_NAMES.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<IllegalArgumentException>()}(" {
+ "\"$nameLowerCamel was \" + $internalGetter + \" but must be one of: \"" {
+
+ intOrStringDef!!.CONST_NAMES.forEachLastAware { CONST_NAME, isLast ->
+ +"""+ "$CONST_NAME(" + $CONST_NAME + ")${if_(!isLast, ", ")}""""
}
}
}
+";"
- } else {
- +""
- +"//noinspection PointlessBooleanExpression"
- "if (true" {
- intOrStringDef!!.CONST_NAMES.forEach { CONST_NAME ->
- +"&& !(${isEqualToExpr(CONST_NAME)})"
- }
- }; rmEmptyLine(); ") {" {
- "throw new ${classRef<IllegalArgumentException>()}(" {
- "\"$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 &&
+ 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
- }
+ }
- fieldAst.annotations.filterNot {
- it.nameAsString == intOrStringDef?.AnnotationName
- || it.nameAsString in knownNonValidationAnnotations
- || it in perElementValidations
- }.forEach { annotation ->
- appendValidateCall(annotation,
- valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
+ val Size = classRef("android.annotation.Size")
+ fieldAst.annotations.filterNot {
+ it.nameAsString == intOrStringDef?.AnnotationName
+ || it.nameAsString in knownNonValidationAnnotations
+ || it in perElementValidations
+ }.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"))
+ }
}
+ }
+}
- 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"
+ val params = when (annotation) {
+ is MarkerAnnotationExpr -> emptyMap()
+ is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
+ is NormalAnnotationExpr ->
+ annotation.pairs.map { it.name.asString() to it.value }.toMap()
+ else -> throw IllegalStateException()
+ }
+ params.forEach { name, value ->
+ !",\n\"$name\", $value"
}
}
+ +";"
}
private fun ClassPrinter.generateOnConstructedCallback(prefix: String = "") {
@@ -845,3 +901,15 @@ fun ClassPrinter.generateForEachField() {
}
}
}
+
+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"
+} \ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index d1dc88f4a773..1e7a2674006b 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -1,6 +1,6 @@
package com.android.codegen
-import com.github.javaparser.ast.body.TypeDeclaration
+import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations
import com.github.javaparser.ast.type.ClassOrInterfaceType
@@ -8,9 +8,17 @@ import com.github.javaparser.ast.type.Type
fun ClassPrinter.getInputSignatures(): List<String> {
+ return generateInputSignaturesForClass(classAst) +
+ annotationToString(classAst.annotations.find { it.nameAsString == DataClass }) +
+ generateInputSignaturesForClass(customBaseBuilderAst)
+}
+
+private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterfaceDeclaration?): List<String> {
+ if (classAst == null) return emptyList()
+
return classAst.fields.map { fieldAst ->
buildString {
- append(fieldAst.modifiers.joinToString(" ") {it.asString()})
+ append(fieldAst.modifiers.joinToString(" ") { it.asString() })
append(" ")
append(annotationsToString(fieldAst))
append(" ")
@@ -20,7 +28,7 @@ fun ClassPrinter.getInputSignatures(): List<String> {
}
} + classAst.methods.map { methodAst ->
buildString {
- append(methodAst.modifiers.joinToString(" ") {it.asString()})
+ append(methodAst.modifiers.joinToString(" ") { it.asString() })
append(" ")
append(annotationsToString(methodAst))
append(" ")
@@ -28,19 +36,26 @@ fun ClassPrinter.getInputSignatures(): List<String> {
append(" ")
append(methodAst.nameAsString)
append("(")
- append(methodAst.parameters.joinToString(",") {getFullClassName(it.type)})
+ append(methodAst.parameters.joinToString(",") { getFullClassName(it.type) })
append(")")
}
- }
+ } + ("class ${classAst.nameAsString}" +
+ " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
+ " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]")
}
private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
- return annotatedAst.annotations.joinToString(" ") {
- annotationToString(it)
- }
+ return annotatedAst
+ .annotations
+ .groupBy { it.nameAsString } // dedupe annotations by name (javaparser bug?)
+ .values
+ .joinToString(" ") {
+ annotationToString(it[0])
+ }
}
-private fun ClassPrinter.annotationToString(ann: AnnotationExpr): String {
+private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String {
+ if (ann == null) return ""
return buildString {
append("@")
append(getFullClassName(ann.nameAsString))
@@ -78,9 +93,9 @@ private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) {
private fun ClassPrinter.getFullClassName(type: Type): String {
return if (type is ClassOrInterfaceType) {
+
getFullClassName(buildString {
type.scope.ifPresent { append(it).append(".") }
- type.isArrayType
append(type.nameAsString)
}) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(", ") {getFullClassName(it)}}?.let { "<$it>" } ?: "")
} else getFullClassName(type.asString())
@@ -100,10 +115,16 @@ private fun ClassPrinter.getFullClassName(className: String): String {
val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("")
val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "."
- classAst.childNodes.filterIsInstance<TypeDeclaration<*>>().find {
+ if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
+
+ nestedClasses.find {
it.nameAsString == className
}?.let { return thisClassPrefix + it.nameAsString }
+ if (className == CANONICAL_BUILDER_CLASS || className == BASE_BUILDER_CLASS) {
+ return thisClassPrefix + className
+ }
+
constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className }
if (tryOrNull { Class.forName("java.lang.$className") } != null) {
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 8fafa7ce9b1e..f71bfd302d2e 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -9,9 +9,6 @@ const val INDENT_SINGLE = " "
val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
-const val CANONICAL_BUILDER_CLASS = "Builder"
-const val GENERATED_BUILDER_CLASS = "GeneratedBuilder"
-
val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
const val FLAG_BUILDER_PROTECTED_SETTERS = "--builder-protected-setters"
@@ -66,10 +63,10 @@ Special methods/etc. you can define:
Will be called in constructor, after all the fields have been initialized.
This is a good place to put any custom validation logic that you may have
- static class $CANONICAL_BUILDER_CLASS extends $GENERATED_BUILDER_CLASS
- If a class extending $GENERATED_BUILDER_CLASS is specified, generated builder's setters will
+ static class $CANONICAL_BUILDER_CLASS extends $BASE_BUILDER_CLASS
+ If a class extending $BASE_BUILDER_CLASS is specified, generated builder's setters will
return the provided $CANONICAL_BUILDER_CLASS type.
- $GENERATED_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
+ $BASE_BUILDER_CLASS's constructor(s) will be package-private to encourage using $CANONICAL_BUILDER_CLASS instead
This allows you to extend the generated builder, adding or overriding any methods you may want
@@ -131,7 +128,6 @@ fun main(args: Array<String>) {
// $GENERATED_WARNING_PREFIX v$CODEGEN_VERSION.
- // on ${currentTimestamp()}
//
// DO NOT MODIFY!
//
@@ -143,14 +139,6 @@ fun main(args: Array<String>) {
if (FeatureFlag.CONST_DEFS()) generateConstDefs()
- "@$DataClassGenerated(" {
- +"time = ${System.currentTimeMillis()}L,"
- +"codegenVersion = \"$CODEGEN_VERSION\","
- +"sourceFile = \"${file.relativeTo(File(System.getenv("ANDROID_BUILD_TOP")))}\","
- +"inputSignatures = \"${getInputSignatures().joinToString("\\n")}\""
- }
- +"\n"
-
if (FeatureFlag.CONSTRUCTOR()) {
generateConstructor("public")
@@ -160,6 +148,7 @@ fun main(args: Array<String>) {
|| FeatureFlag.PARCELABLE()) {
generateConstructor("/* package-private */")
}
+ if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.GETTERS()) generateGetters()
if (FeatureFlag.SETTERS()) generateSetters()
@@ -168,7 +157,6 @@ fun main(args: Array<String>) {
if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
- if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
if (FeatureFlag.WITHERS()) generateWithers()
if (FeatureFlag.PARCELABLE()) generateParcelable()
@@ -178,6 +166,8 @@ fun main(args: Array<String>) {
if (FeatureFlag.AIDL()) generateAidl(file)
+ generateMetadata(file)
+
rmEmptyLine()
}
stringBuilder.append("\n}\n")
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 175eea6ef0d0..7d50ad10de00 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,4 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.0" \ No newline at end of file
+const val CODEGEN_VERSION = "1.0.0"
+
+const val CANONICAL_BUILDER_CLASS = "Builder"
+const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index 95c99092e2ab..73ceac41682e 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -1,8 +1,10 @@
package com.android.codegen
+import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.expr.AnnotationExpr
import com.github.javaparser.ast.expr.Expression
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@@ -22,6 +24,8 @@ fun Char.isWhitespaceNonNewline() = isWhitespace() && !isNewline()
fun if_(cond: Boolean, then: String) = if (cond) then else ""
+fun <T> Any?.as_(): T = this as T
+
inline infix fun Int.times(action: () -> Unit) {
for (i in 1..this) action()
}
@@ -73,4 +77,15 @@ inline operator fun <reified T> Array<T>.minus(item: T) = toList().minus(item).t
fun currentTimestamp() = DateTimeFormatter
.ofLocalizedDateTime(/* date */ FormatStyle.MEDIUM, /* time */ FormatStyle.LONG)
.withZone(ZoneId.systemDefault())
- .format(Instant.now()) \ No newline at end of file
+ .format(Instant.now())
+
+val NodeWithModifiers<*>.visibility get() = Modifier.getAccessSpecifier(modifiers)
+
+fun abort(msg: String): Nothing {
+ System.err.println("ERROR: $msg")
+ System.exit(1)
+ throw InternalError() // can't get here
+}
+
+fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+