Merge "Add inheritance support for parcelable dataclasses"
This commit is contained in:
committed by
Android (Google) Code Review
commit
68e5e264e7
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
} + ";"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user