<lambda>null1 package com.android.codegen
2
3 import com.github.javaparser.ast.Modifier
4 import com.github.javaparser.ast.body.CallableDeclaration
5 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
6 import com.github.javaparser.ast.body.TypeDeclaration
7 import com.github.javaparser.ast.expr.*
8 import com.github.javaparser.ast.type.ClassOrInterfaceType
9
10 /**
11 * [ClassInfo] + utilities for printing out new class code with proper indentation and imports
12 */
13 class ClassPrinter(
14 classAst: ClassOrInterfaceDeclaration,
15 fileInfo: FileInfo
16 ) : ClassInfo(classAst, fileInfo), Printer<ClassPrinter>, ImportsProvider {
17
18 val GENERATED_MEMBER_HEADER by lazy { "@$GeneratedMember" }
19
20 init {
21 val fieldsWithMissingNullablity = fields.filter { field ->
22 !field.isPrimitive
23 && field.fieldAst.modifiers.none { it.keyword == Modifier.Keyword.TRANSIENT }
24 && "@$Nullable" !in field.annotations
25 && "@$NonNull" !in field.annotations
26 }
27 if (fieldsWithMissingNullablity.isNotEmpty()) {
28 abort("Non-primitive fields must have @$Nullable or @$NonNull annotation.\n" +
29 "Missing nullability annotations on: "
30 + fieldsWithMissingNullablity.joinToString(", ") { it.name })
31 }
32
33 if (!classAst.isFinal &&
34 classAst.extendedTypes.any { it.nameAsString == Parcelable }) {
35 abort("Parcelable classes must be final")
36 }
37 }
38
39 val cliArgs get() = fileInfo.cliArgs
40
41 fun print() {
42 currentIndent = fileInfo.sourceLines
43 .find { "class $ClassName" in it }!!
44 .takeWhile { it.isWhitespace() }
45 .plus(INDENT_SINGLE)
46
47 +fileInfo.generatedWarning
48
49 if (FeatureFlag.CONST_DEFS()) generateConstDefs()
50
51
52 if (FeatureFlag.CONSTRUCTOR()) {
53 generateConstructor("public")
54 } else if (FeatureFlag.BUILDER()
55 || FeatureFlag.COPY_CONSTRUCTOR()
56 || FeatureFlag.WITHERS()) {
57 generateConstructor("/* package-private */")
58 }
59 if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor()
60
61 if (FeatureFlag.GETTERS()) generateGetters()
62 if (FeatureFlag.SETTERS()) generateSetters()
63 if (FeatureFlag.TO_STRING()) generateToString()
64 if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode()
65
66 if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField()
67
68 if (FeatureFlag.WITHERS()) generateWithers()
69
70 if (FeatureFlag.PARCELABLE()) generateParcelable()
71
72 if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon()
73 if (FeatureFlag.BUILDER()) generateBuilder()
74
75 if (FeatureFlag.AIDL()) fileInfo.generateAidl() //TODO guard against nested classes requesting aidl
76
77 generateMetadata(fileInfo.file)
78
79 +"""
80 //@formatter:on
81 $GENERATED_END
82
83 """
84
85 rmEmptyLine()
86 }
87
88 override var currentIndent: String
89 get() = fileInfo.currentIndent
90 set(value) { fileInfo.currentIndent = value }
91 override val stringBuilder get() = fileInfo.stringBuilder
92
93
94 val dataClassAnnotationFeatures = classAst.annotations
95 .find { it.nameAsString == DataClass }
96 ?.let { it as? NormalAnnotationExpr }
97 ?.pairs
98 ?.map { pair -> pair.nameAsString to (pair.value as BooleanLiteralExpr).value }
99 ?.toMap()
100 ?: emptyMap()
101
102 val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, UnsupportedAppUsage,
103 DataClassSuppressConstDefs, MaySetToNull, Each, DataClass)
104 val knownNonValidationAnnotations = internalAnnotations + Each + Nullable
105
106 /**
107 * @return whether the given feature is enabled
108 */
109 operator fun FeatureFlag.invoke(): Boolean {
110 if (cliArgs.contains("--no-$kebabCase")) return false
111 if (cliArgs.contains("--$kebabCase")) return true
112
113 val annotationKey = "gen$upperCamelCase"
114 val annotationHiddenKey = "genHidden$upperCamelCase"
115 if (dataClassAnnotationFeatures.containsKey(annotationKey)) {
116 return dataClassAnnotationFeatures[annotationKey]!!
117 }
118 if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) {
119 return dataClassAnnotationFeatures[annotationHiddenKey]!!
120 }
121
122 if (cliArgs.contains("--all")) return true
123 if (hidden) return true
124
125 return when (this) {
126 FeatureFlag.SETTERS ->
127 !FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal }
128 FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS)
129 || fields.any { it.hasDefault }
130 || onByDefault
131 FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER()
132 FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces
133 FeatureFlag.AIDL -> fileInfo.mainClass.nameAsString == ClassName && FeatureFlag.PARCELABLE()
134 FeatureFlag.IMPLICIT_NONNULL -> fields.any { it.isNullable }
135 && fields.none { "@$NonNull" in it.annotations }
136 else -> onByDefault
137 }
138 }
139
140 val FeatureFlag.hidden: Boolean
141 get(): Boolean {
142 val annotationHiddenKey = "genHidden$upperCamelCase"
143 if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) {
144 return dataClassAnnotationFeatures[annotationHiddenKey]!!
145 }
146 return when {
147 cliArgs.contains("--hidden-$kebabCase") -> true
148 else -> false
149 }
150 }
151
152
153
154 inline operator fun <R> invoke(f: ClassPrinter.() -> R): R = run(f)
155
156 var BuilderClass = CANONICAL_BUILDER_CLASS
157 var BuilderType = BuilderClass + genericArgs
158 val customBaseBuilderAst: ClassOrInterfaceDeclaration? by lazy {
159 nestedClasses.find { it.nameAsString == BASE_BUILDER_CLASS }
160 }
161
162 val suppressedMembers by lazy {
163 getSuppressedMembers(classAst)
164 }
165 val builderSuppressedMembers by lazy {
166 getSuppressedMembers(customBaseBuilderAst) + suppressedMembers.mapNotNull {
167 if (it.startsWith("$CANONICAL_BUILDER_CLASS.")) {
168 it.removePrefix("$CANONICAL_BUILDER_CLASS.")
169 } else {
170 null
171 }
172 }
173 }
174
175 private fun getSuppressedMembers(clazz: ClassOrInterfaceDeclaration?): List<String> {
176 return clazz
177 ?.annotations
178 ?.find { it.nameAsString == DataClassSuppress }
179 ?.as_<SingleMemberAnnotationExpr>()
180 ?.memberValue
181 ?.run {
182 when (this) {
183 is ArrayInitializerExpr -> values.map { it.asLiteralStringValueExpr().value }
184 is StringLiteralExpr -> listOf(value)
185 else -> abort("Can't parse annotation arg: $this")
186 }
187 }
188 ?: emptyList()
189 }
190
191 fun isMethodGenerationSuppressed(name: String, vararg argTypes: String): Boolean {
192 return name in suppressedMembers || hasMethod(name, *argTypes)
193 }
194
195 fun hasMethod(name: String, vararg argTypes: String): Boolean {
196 val members: List<CallableDeclaration<*>> =
197 if (name == ClassName) classAst.constructors else classAst.methods
198 return members.any {
199 it.name.asString() == name &&
200 it.parameters.map { it.type.asString() } == argTypes.toList()
201 }
202 }
203
204 val lazyTransientFields = classAst.fields
205 .filter { it.isTransient && !it.isStatic }
206 .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) }
207 .filter { hasMethod("lazyInit${it.NameUpperCamel}") }
208
209 val extendsParcelableClass by lazy {
210 Parcelable !in superInterfaces && superClass != null
211 }
212
213 init {
214 val builderFactoryOverride = classAst.methods.find {
215 it.isStatic && it.nameAsString == "builder"
216 }
217 if (builderFactoryOverride != null) {
218 BuilderClass = (builderFactoryOverride.type as ClassOrInterfaceType).nameAsString
219 BuilderType = builderFactoryOverride.type.asString()
220 } else {
221 val builderExtension = classAst
222 .childNodes
223 .filterIsInstance(TypeDeclaration::class.java)
224 .find { it.nameAsString == CANONICAL_BUILDER_CLASS }
225 if (builderExtension != null) {
226 BuilderClass = BASE_BUILDER_CLASS
227 val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters
228 BuilderType = if (tp.isEmpty()) BuilderClass
229 else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>"
230 }
231 }
232 }
233 }