• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

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