1 package com.android.codegen 2 3 import com.github.javaparser.JavaParser 4 import com.github.javaparser.ast.body.FieldDeclaration 5 import com.github.javaparser.ast.expr.ClassExpr 6 import com.github.javaparser.ast.expr.Name 7 import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr 8 import com.github.javaparser.ast.expr.StringLiteralExpr 9 import com.github.javaparser.ast.type.ArrayType 10 import com.github.javaparser.ast.type.ClassOrInterfaceType 11 import com.github.javaparser.javadoc.Javadoc 12 13 data class FieldInfo( 14 val index: Int, 15 val fieldAst: FieldDeclaration, 16 private val classInfo: ClassInfo 17 ) { 18 19 val classPrinter = classInfo as ClassPrinter 20 21 // AST 22 internal val variableAst = fieldAst.variables[0] 23 val typeAst = variableAst.type 24 25 // Field type 26 val Type = typeAst.asString() 27 val FieldClass = Type.takeWhile { it != '<' } 28 val isPrimitive = Type in PRIMITIVE_TYPES 29 30 // Javadoc 31 val javadoc: Javadoc? = fieldAst.javadoc.orElse(null) 32 private val javadocText = javadoc?.toText()?.let { 33 // Workaround for a bug in Javaparser for javadocs starting with { 34 if (it.hasUnbalancedCurlyBrace()) "{$it" else it 35 } 36 val javadocTextNoAnnotationLines = javadocText 37 ?.lines() 38 ?.dropLastWhile { it.startsWith("@") || it.isBlank() } 39 ?.let { if (it.isEmpty()) null else it } 40 val javadocFull = javadocText 41 ?.trimBlankLines() 42 ?.mapLines { " * $this" } 43 ?.let { "/**\n$it\n */" } 44 45 46 // Field name 47 val name = variableAst.name.asString()!! 48 private val isNameHungarian = name[0] == 'm' && name[1].isUpperCase() 49 val NameUpperCamel = if (isNameHungarian) name.substring(1) else name.capitalize() 50 val nameLowerCamel = if (isNameHungarian) NameUpperCamel.decapitalize() else name 51 val _name = if (name != nameLowerCamel) nameLowerCamel else "_$nameLowerCamel" 52 val SingularNameOrNull by lazy { 53 classPrinter { 54 fieldAst.annotations 55 .find { it.nameAsString == PluralOf } 56 ?.let { it as? SingleMemberAnnotationExpr } 57 ?.memberValue 58 ?.let { it as? StringLiteralExpr } 59 ?.value 60 ?.toLowerCamel() 61 ?.capitalize() 62 } 63 } 64 val SingularName by lazy { SingularNameOrNull ?: NameUpperCamel } 65 66 67 // Field value 68 val mayBeNull: Boolean 69 get() = when { 70 isPrimitive -> false 71 "@${classPrinter.NonNull}" in annotations -> false 72 "@${classPrinter.NonEmpty}" in annotations -> false 73 isNullable -> true 74 lazyInitializer != null -> true 75 else -> classPrinter { !FeatureFlag.IMPLICIT_NONNULL() } 76 } 77 val lazyInitializer 78 get() = classInfo.classAst.methods.find { method -> 79 method.nameAsString == "lazyInit$NameUpperCamel" && method.parameters.isEmpty() 80 }?.nameAsString 81 val internalGetter get() = if (lazyInitializer != null) "get$NameUpperCamel()" else name 82 val defaultExpr: Any? 83 get() { 84 variableAst.initializer.orElse(null)?.let { return it } 85 classInfo.classAst.methods.find { 86 it.nameAsString == "default$NameUpperCamel" && it.parameters.isEmpty() 87 }?.run { return "$nameAsString()" } 88 return null 89 } 90 val hasDefault get() = defaultExpr != null 91 92 93 // Generic args 94 val isArray = Type.endsWith("[]") 95 val isList = FieldClass == "List" || FieldClass == "ArrayList" 96 val isMap = FieldClass == "Map" || FieldClass == "ArrayMap" 97 || FieldClass == "HashMap" || FieldClass == "LinkedHashMap" 98 val fieldBit = bitAtExpr(index) 99 var isLast = false 100 val isFinal = fieldAst.isFinal 101 val fieldTypeGenegicArgs = when (typeAst) { 102 is ArrayType -> listOf(fieldAst.elementType.asString()) 103 is ClassOrInterfaceType -> { 104 typeAst.typeArguments.orElse(null)?.map { it.asString() } ?: emptyList() 105 } 106 else -> emptyList() 107 } 108 val FieldInnerType = fieldTypeGenegicArgs.firstOrNull() 109 val FieldInnerClass = FieldInnerType?.takeWhile { it != '<' } 110 111 112 // Annotations 113 var intOrStringDef = null as ConstDef? 114 val annotations by lazy { 115 if (FieldClass in BUILTIN_SPECIAL_PARCELLINGS) { 116 classPrinter { 117 fileInfo.apply { 118 fieldAst.addAnnotation(SingleMemberAnnotationExpr( 119 Name(ParcelWith), 120 ClassExpr(parseJava(JavaParser::parseClassOrInterfaceType, 121 "$Parcelling.BuiltIn.For$FieldClass")))) 122 } 123 } 124 } 125 fieldAst.annotations.map { it.removeComment().toString() } 126 } 127 val annotationsNoInternal by lazy { 128 annotations.filterNot { ann -> 129 classPrinter { 130 internalAnnotations.any { 131 it in ann 132 } 133 } 134 } 135 } 136 137 fun hasAnnotation(a: String) = annotations.any { it.startsWith(a) } 138 val isNullable by lazy { hasAnnotation("@Nullable") } 139 val isNonEmpty by lazy { hasAnnotation("@${classPrinter.NonEmpty}") } 140 val customParcellingClass by lazy { 141 fieldAst.annotations.find { it.nameAsString == classPrinter.ParcelWith } 142 ?.singleArgAs<ClassExpr>() 143 ?.type 144 ?.asString() 145 } 146 val annotationsAndType by lazy { (annotationsNoInternal + Type).joinToString(" ") } 147 val sParcelling by lazy { customParcellingClass?.let { "sParcellingFor$NameUpperCamel" } } 148 149 val SetterParamType = if (isArray) "$FieldInnerType..." else Type 150 val annotationsForSetterParam by lazy { 151 buildList<String> { 152 addAll(annotationsNoInternal) 153 classPrinter { 154 if ("@$Nullable" in annotations 155 && "@$MaySetToNull" !in annotations) { 156 remove("@$Nullable") 157 add("@$NonNull") 158 } 159 } 160 }.joinToString(" ") 161 } 162 val annotatedTypeForSetterParam by lazy { "$annotationsForSetterParam $SetterParamType" } 163 164 // Utilities 165 166 /** 167 * `mFoo.size()` 168 */ 169 val ClassPrinter.sizeExpr get() = when { 170 isArray && FieldInnerClass !in PRIMITIVE_TYPES -> 171 memberRef("com.android.internal.util.ArrayUtils.size") + "($name)" 172 isArray -> "$name.length" 173 listOf("List", "Set", "Map").any { FieldClass.endsWith(it) } -> 174 memberRef("com.android.internal.util.CollectionUtils.size") + "($name)" 175 Type == "String" -> memberRef("android.text.TextUtils.length") + "($name)" 176 Type == "CharSequence" -> "$name.length()" 177 else -> "$name.size()" 178 } 179 /** 180 * `mFoo.get(0)` 181 */ 182 fun elemAtIndexExpr(indexExpr: String) = when { 183 isArray -> "$name[$indexExpr]" 184 FieldClass == "ArraySet" -> "$name.valueAt($indexExpr)" 185 else -> "$name.get($indexExpr)" 186 } 187 /** 188 * `mFoo.isEmpty()` 189 */ 190 val ClassPrinter.isEmptyExpr get() = when { 191 isArray || Type == "CharSequence" -> "$sizeExpr == 0" 192 else -> "$name.isEmpty()" 193 } 194 195 /** 196 * `mFoo == that` or `Objects.equals(mFoo, that)`, etc. 197 */ 198 fun ClassPrinter.isEqualToExpr(that: String) = when { 199 Type in PRIMITIVE_TYPES -> "$internalGetter == $that" 200 isArray -> "${memberRef("java.util.Arrays.equals")}($internalGetter, $that)" 201 else -> "${memberRef("java.util.Objects.equals")}($internalGetter, $that)" 202 } 203 204 /** 205 * Parcel.write* and Parcel.read* method name wildcard values 206 */ 207 val ParcelMethodsSuffix = when { 208 FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES + 209 listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle", 210 "FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") -> 211 FieldClass 212 isMap && fieldTypeGenegicArgs[0] == "String" -> "Map" 213 isArray -> when { 214 FieldInnerType!! in (PRIMITIVE_TYPES + "String") -> FieldInnerType + "Array" 215 isBinder(FieldInnerType) -> "BinderArray" 216 else -> "TypedArray" 217 } 218 isList -> when { 219 FieldInnerType == "String" -> "StringList" 220 isBinder(FieldInnerType!!) -> "BinderList" 221 else -> "ParcelableList" 222 } 223 isStrongBinder(Type) -> "StrongBinder" 224 isIInterface(Type) -> "StrongInterface" 225 else -> "TypedObject" 226 }.capitalize() 227 228 private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder" 229 private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type) 230 private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase() 231 }