<lambda>null1 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 }