Fixes: 183547536 Test: . frameworks/base/tests/Codegen/runTest.sh Change-Id: Ifffac8c560a8e2e9e0543d523738ad529af633e9
231 lines
8.7 KiB
Kotlin
231 lines
8.7 KiB
Kotlin
package com.android.codegen
|
|
|
|
import com.github.javaparser.JavaParser
|
|
import com.github.javaparser.ast.body.FieldDeclaration
|
|
import com.github.javaparser.ast.expr.ClassExpr
|
|
import com.github.javaparser.ast.expr.Name
|
|
import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
|
|
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
|
|
|
|
data class FieldInfo(
|
|
val index: Int,
|
|
val fieldAst: FieldDeclaration,
|
|
private val classInfo: ClassInfo
|
|
) {
|
|
|
|
val classPrinter = classInfo as ClassPrinter
|
|
|
|
// AST
|
|
internal val variableAst = fieldAst.variables[0]
|
|
val typeAst = variableAst.type
|
|
|
|
// Field type
|
|
val Type = typeAst.asString()
|
|
val FieldClass = Type.takeWhile { it != '<' }
|
|
val isPrimitive = Type in PRIMITIVE_TYPES
|
|
|
|
// Javadoc
|
|
val javadoc: Javadoc? = fieldAst.javadoc.orElse(null)
|
|
private val javadocText = javadoc?.toText()?.let {
|
|
// Workaround for a bug in Javaparser for javadocs starting with {
|
|
if (it.hasUnbalancedCurlyBrace()) "{$it" else it
|
|
}
|
|
val javadocTextNoAnnotationLines = javadocText
|
|
?.lines()
|
|
?.dropLastWhile { it.startsWith("@") || it.isBlank() }
|
|
?.let { if (it.isEmpty()) null else it }
|
|
val javadocFull = javadocText
|
|
?.trimBlankLines()
|
|
?.mapLines { " * $this" }
|
|
?.let { "/**\n$it\n */" }
|
|
|
|
|
|
// Field name
|
|
val name = variableAst.name.asString()!!
|
|
private val isNameHungarian = name[0] == 'm' && name[1].isUpperCase()
|
|
val NameUpperCamel = if (isNameHungarian) name.substring(1) else name.capitalize()
|
|
val nameLowerCamel = if (isNameHungarian) NameUpperCamel.decapitalize() else name
|
|
val _name = if (name != nameLowerCamel) nameLowerCamel else "_$nameLowerCamel"
|
|
val SingularNameOrNull by lazy {
|
|
classPrinter {
|
|
fieldAst.annotations
|
|
.find { it.nameAsString == PluralOf }
|
|
?.let { it as? SingleMemberAnnotationExpr }
|
|
?.memberValue
|
|
?.let { it as? StringLiteralExpr }
|
|
?.value
|
|
?.toLowerCamel()
|
|
?.capitalize()
|
|
}
|
|
}
|
|
val SingularName by lazy { SingularNameOrNull ?: NameUpperCamel }
|
|
|
|
|
|
// Field value
|
|
val mayBeNull: Boolean
|
|
get() = when {
|
|
isPrimitive -> false
|
|
"@${classPrinter.NonNull}" in annotations -> false
|
|
"@${classPrinter.NonEmpty}" in annotations -> false
|
|
isNullable -> true
|
|
lazyInitializer != null -> true
|
|
else -> classPrinter { !FeatureFlag.IMPLICIT_NONNULL() }
|
|
}
|
|
val lazyInitializer
|
|
get() = classInfo.classAst.methods.find { method ->
|
|
method.nameAsString == "lazyInit$NameUpperCamel" && method.parameters.isEmpty()
|
|
}?.nameAsString
|
|
val internalGetter get() = if (lazyInitializer != null) "get$NameUpperCamel()" else name
|
|
val defaultExpr: Any?
|
|
get() {
|
|
variableAst.initializer.orElse(null)?.let { return it }
|
|
classInfo.classAst.methods.find {
|
|
it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty()
|
|
}?.run { return "$nameAsString()" }
|
|
return null
|
|
}
|
|
val hasDefault get() = defaultExpr != null
|
|
|
|
|
|
// Generic args
|
|
val isArray = Type.endsWith("[]")
|
|
val isList = FieldClass == "List" || FieldClass == "ArrayList"
|
|
val isMap = FieldClass == "Map" || FieldClass == "ArrayMap"
|
|
|| FieldClass == "HashMap" || FieldClass == "LinkedHashMap"
|
|
val fieldBit = bitAtExpr(index)
|
|
var isLast = false
|
|
val isFinal = fieldAst.isFinal
|
|
val fieldTypeGenegicArgs = when (typeAst) {
|
|
is ArrayType -> listOf(fieldAst.elementType.asString())
|
|
is ClassOrInterfaceType -> {
|
|
typeAst.typeArguments.orElse(null)?.map { it.asString() } ?: emptyList()
|
|
}
|
|
else -> emptyList()
|
|
}
|
|
val FieldInnerType = fieldTypeGenegicArgs.firstOrNull()
|
|
val FieldInnerClass = FieldInnerType?.takeWhile { it != '<' }
|
|
|
|
|
|
// Annotations
|
|
var intOrStringDef = null as ConstDef?
|
|
val annotations by lazy {
|
|
if (FieldClass in BUILTIN_SPECIAL_PARCELLINGS) {
|
|
classPrinter {
|
|
fileInfo.apply {
|
|
fieldAst.addAnnotation(SingleMemberAnnotationExpr(
|
|
Name(ParcelWith),
|
|
ClassExpr(parseJava(JavaParser::parseClassOrInterfaceType,
|
|
"$Parcelling.BuiltIn.For$FieldClass"))))
|
|
}
|
|
}
|
|
}
|
|
fieldAst.annotations.map { it.removeComment().toString() }
|
|
}
|
|
val annotationsNoInternal by lazy {
|
|
annotations.filterNot { ann ->
|
|
classPrinter {
|
|
internalAnnotations.any {
|
|
it in ann
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fun hasAnnotation(a: String) = annotations.any { it.startsWith(a) }
|
|
val isNullable by lazy { hasAnnotation("@Nullable") }
|
|
val isNonEmpty by lazy { hasAnnotation("@${classPrinter.NonEmpty}") }
|
|
val customParcellingClass by lazy {
|
|
fieldAst.annotations.find { it.nameAsString == classPrinter.ParcelWith }
|
|
?.singleArgAs<ClassExpr>()
|
|
?.type
|
|
?.asString()
|
|
}
|
|
val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") }
|
|
val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } }
|
|
|
|
val SetterParamType = if (isArray) "$FieldInnerType..." else Type
|
|
val annotationsForSetterParam by lazy {
|
|
buildList<String> {
|
|
addAll(annotationsNoInternal)
|
|
classPrinter {
|
|
if ("@$Nullable" in annotations
|
|
&& "@$MaySetToNull" !in annotations) {
|
|
remove("@$Nullable")
|
|
add("@$NonNull")
|
|
}
|
|
}
|
|
}.joinToString(" ")
|
|
}
|
|
val annotatedTypeForSetterParam by lazy { "$annotationsForSetterParam $SetterParamType" }
|
|
|
|
// Utilities
|
|
|
|
/**
|
|
* `mFoo.size()`
|
|
*/
|
|
val ClassPrinter.sizeExpr get() = when {
|
|
isArray && FieldInnerClass !in PRIMITIVE_TYPES ->
|
|
memberRef("com.android.internal.util.ArrayUtils.size") + "($name)"
|
|
isArray -> "$name.length"
|
|
listOf("List", "Set", "Map").any { FieldClass.endsWith(it) } ->
|
|
memberRef("com.android.internal.util.CollectionUtils.size") + "($name)"
|
|
Type == "String" -> memberRef("android.text.TextUtils.length") + "($name)"
|
|
Type == "CharSequence" -> "$name.length()"
|
|
else -> "$name.size()"
|
|
}
|
|
/**
|
|
* `mFoo.get(0)`
|
|
*/
|
|
fun elemAtIndexExpr(indexExpr: String) = when {
|
|
isArray -> "$name[$indexExpr]"
|
|
FieldClass == "ArraySet" -> "$name.valueAt($indexExpr)"
|
|
else -> "$name.get($indexExpr)"
|
|
}
|
|
/**
|
|
* `mFoo.isEmpty()`
|
|
*/
|
|
val ClassPrinter.isEmptyExpr get() = when {
|
|
isArray || Type == "CharSequence" -> "$sizeExpr == 0"
|
|
else -> "$name.isEmpty()"
|
|
}
|
|
|
|
/**
|
|
* `mFoo == that` or `Objects.equals(mFoo, that)`, etc.
|
|
*/
|
|
fun ClassPrinter.isEqualToExpr(that: String) = when {
|
|
Type in PRIMITIVE_TYPES -> "$internalGetter == $that"
|
|
isArray -> "${memberRef("java.util.Arrays.equals")}($internalGetter, $that)"
|
|
else -> "${memberRef("java.util.Objects.equals")}($internalGetter, $that)"
|
|
}
|
|
|
|
/**
|
|
* Parcel.write* and Parcel.read* method name wildcard values
|
|
*/
|
|
val ParcelMethodsSuffix = when {
|
|
FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES +
|
|
listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
|
|
"FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
|
|
FieldClass
|
|
isMap && fieldTypeGenegicArgs[0] == "String" -> "Map"
|
|
isArray -> when {
|
|
FieldInnerType!! in (PRIMITIVE_TYPES + "String") -> FieldInnerType + "Array"
|
|
isBinder(FieldInnerType) -> "BinderArray"
|
|
else -> "TypedArray"
|
|
}
|
|
isList -> when {
|
|
FieldInnerType == "String" -> "StringList"
|
|
isBinder(FieldInnerType!!) -> "BinderList"
|
|
else -> "ParcelableList"
|
|
}
|
|
isStrongBinder(Type) -> "StrongBinder"
|
|
isIInterface(Type) -> "StrongInterface"
|
|
else -> "TypedObject"
|
|
}.capitalize()
|
|
|
|
private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder"
|
|
private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
|
|
private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
|
|
} |