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() ?.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 { 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() }