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