Merge "Add inheritance support for parcelable dataclasses"

This commit is contained in:
TreeHugger Robot
2019-10-05 06:51:40 +00:00
committed by Android (Google) Code Review
13 changed files with 600 additions and 194 deletions

View File

@@ -38,6 +38,11 @@ open class ClassInfo(val sourceLines: List<String>) {
val superInterfaces = (fileAst.types[0] as ClassOrInterfaceDeclaration)
.implementedTypes.map { it.asString() }
val superClass = run {
val superClasses = (fileAst.types[0] as ClassOrInterfaceDeclaration).extendedTypes
if (superClasses.isNonEmpty) superClasses[0] else null
}
val ClassName = classAst.nameAsString
private val genericArgsAst = classAst.typeParameters
val genericArgs = if (genericArgsAst.isEmpty()) "" else {

View File

@@ -1,6 +1,7 @@
package com.android.codegen
import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.body.CallableDeclaration
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.TypeDeclaration
import com.github.javaparser.ast.expr.*
@@ -37,6 +38,7 @@ class ClassPrinter(
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 Parcel by lazy { classRef("android.os.Parcel") }
val UnsupportedAppUsage by lazy { classRef("android.annotation.UnsupportedAppUsage") }
init {
@@ -354,7 +356,9 @@ class ClassPrinter(
}
fun hasMethod(name: String, vararg argTypes: String): Boolean {
return classAst.methods.any {
val members: List<CallableDeclaration<*>> =
if (name == ClassName) classAst.constructors else classAst.methods
return members.any {
it.name.asString() == name &&
it.parameters.map { it.type.asString() } == argTypes.toList()
}
@@ -365,6 +369,10 @@ class ClassPrinter(
.mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
.filter { hasMethod("lazyInit${it.NameUpperCamel}") }
val extendsParcelableClass by lazy {
Parcelable !in superInterfaces && superClass != null
}
init {
val builderFactoryOverride = classAst.methods.find {
it.isStatic && it.nameAsString == "builder"

View File

@@ -422,6 +422,10 @@ fun ClassPrinter.generateParcelable() {
+"// void parcelFieldName(Parcel dest, int flags) { ... }"
+""
if (extendsParcelableClass) {
+"super.writeToParcel(dest, flags);\n"
}
if (booleanFields.isNotEmpty() || nullableFields.isNotEmpty()) {
+"$flagStorageType flg = 0;"
booleanFields.forEachApply {
@@ -463,6 +467,123 @@ fun ClassPrinter.generateParcelable() {
+""
}
if (!hasMethod(ClassName, Parcel)) {
val visibility = if (classAst.isFinal) "/* package-private */" else "protected"
+"/** @hide */"
+"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
+GENERATED_MEMBER_HEADER
"$visibility $ClassName($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<String>()
// Create container if any
val containerInitExpr = when {
FieldClass.endsWith("Map") -> "new $LinkedHashMap<>()"
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) {
+"null;"
!"if ((flg & $fieldBit) != 0) {"
pushIndent()
+""
!"$_name = "
}
+"$containerInitExpr;"
} else {
!"$Type $_name = "
if (mayBeNull) !"(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) {
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")
@@ -477,107 +598,8 @@ fun ClassPrinter.generateParcelable() {
}
+"@Override"
+"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
"public $ClassName createFromParcel($Parcel in)" {
+"// You can override field unparcelling by defining methods like:"
+"// static FieldType unparcelFieldName(Parcel in) { ... }"
+""
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<String>()
// Create container if any
val containerInitExpr = when {
FieldClass.endsWith("Map") -> "new $LinkedHashMap<>()"
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) {
+"null;"
!"if ((flg & $fieldBit) != 0) {"
pushIndent()
+""
!"$_name = "
}
+"$containerInitExpr;"
} else {
!"$Type $_name = "
if (mayBeNull) !"(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) {
popIndent()
rmEmptyLine()
+"\n}"
}
}
}
"return new $ClassType(" {
fields.forEachTrimmingTrailingComma {
+"$_name,"
}
} + ";"
+"return new $ClassName(in);"
}
rmEmptyLine()
} + ";"

View File

@@ -152,8 +152,7 @@ fun main(args: Array<String>) {
generateConstructor("public")
} else if (FeatureFlag.BUILDER()
|| FeatureFlag.COPY_CONSTRUCTOR()
|| FeatureFlag.WITHERS()
|| FeatureFlag.PARCELABLE()) {
|| FeatureFlag.WITHERS()) {
generateConstructor("/* package-private */")
}
if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()

View File

@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
const val CODEGEN_VERSION = "1.0.4"
const val CODEGEN_VERSION = "1.0.5"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"