| package com.android.codegen |
| |
| import com.github.javaparser.ast.Modifier |
| import com.github.javaparser.ast.body.CallableDeclaration |
| import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration |
| import com.github.javaparser.ast.body.TypeDeclaration |
| import com.github.javaparser.ast.expr.* |
| import com.github.javaparser.ast.type.ClassOrInterfaceType |
| |
| /** |
| * [ClassInfo] + utilities for printing out new class code with proper indentation and imports |
| */ |
| class ClassPrinter( |
| classAst: ClassOrInterfaceDeclaration, |
| fileInfo: FileInfo |
| ) : ClassInfo(classAst, fileInfo), Printer<ClassPrinter>, ImportsProvider { |
| |
| val GENERATED_MEMBER_HEADER by lazy { "@$GeneratedMember" } |
| |
| init { |
| val fieldsWithMissingNullablity = fields.filter { field -> |
| !field.isPrimitive |
| && field.fieldAst.modifiers.none { it.keyword == Modifier.Keyword.TRANSIENT } |
| && "@$Nullable" !in field.annotations |
| && "@$NonNull" !in field.annotations |
| } |
| if (fieldsWithMissingNullablity.isNotEmpty()) { |
| abort("Non-primitive fields must have @$Nullable or @$NonNull annotation.\n" + |
| "Missing nullability annotations on: " |
| + fieldsWithMissingNullablity.joinToString(", ") { it.name }) |
| } |
| |
| if (!classAst.isFinal && |
| classAst.extendedTypes.any { it.nameAsString == Parcelable }) { |
| abort("Parcelable classes must be final") |
| } |
| } |
| |
| val cliArgs get() = fileInfo.cliArgs |
| |
| fun print() { |
| currentIndent = fileInfo.sourceLines |
| .find { "class $ClassName" in it }!! |
| .takeWhile { it.isWhitespace() } |
| .plus(INDENT_SINGLE) |
| |
| +fileInfo.generatedWarning |
| |
| if (FeatureFlag.CONST_DEFS()) generateConstDefs() |
| |
| |
| if (FeatureFlag.CONSTRUCTOR()) { |
| generateConstructor("public") |
| } else if (FeatureFlag.BUILDER() |
| || FeatureFlag.COPY_CONSTRUCTOR() |
| || FeatureFlag.WITHERS()) { |
| generateConstructor("/* package-private */") |
| } |
| if (FeatureFlag.COPY_CONSTRUCTOR()) generateCopyConstructor() |
| |
| if (FeatureFlag.GETTERS()) generateGetters() |
| if (FeatureFlag.SETTERS()) generateSetters() |
| if (FeatureFlag.TO_STRING()) generateToString() |
| if (FeatureFlag.EQUALS_HASH_CODE()) generateEqualsHashcode() |
| |
| if (FeatureFlag.FOR_EACH_FIELD()) generateForEachField() |
| |
| if (FeatureFlag.WITHERS()) generateWithers() |
| |
| if (FeatureFlag.PARCELABLE()) generateParcelable() |
| |
| if (FeatureFlag.BUILDER() && FeatureFlag.BUILD_UPON()) generateBuildUpon() |
| if (FeatureFlag.BUILDER()) generateBuilder() |
| |
| if (FeatureFlag.AIDL()) fileInfo.generateAidl() //TODO guard against nested classes requesting aidl |
| |
| generateMetadata(fileInfo.file) |
| |
| +""" |
| //@formatter:on |
| $GENERATED_END |
| |
| """ |
| |
| rmEmptyLine() |
| } |
| |
| override var currentIndent: String |
| get() = fileInfo.currentIndent |
| set(value) { fileInfo.currentIndent = value } |
| override val stringBuilder get() = fileInfo.stringBuilder |
| |
| |
| val dataClassAnnotationFeatures = classAst.annotations |
| .find { it.nameAsString == DataClass } |
| ?.let { it as? NormalAnnotationExpr } |
| ?.pairs |
| ?.map { pair -> pair.nameAsString to (pair.value as BooleanLiteralExpr).value } |
| ?.toMap() |
| ?: emptyMap() |
| |
| val internalAnnotations = setOf(ParcelWith, DataClassEnum, PluralOf, UnsupportedAppUsage, |
| DataClassSuppressConstDefs, MaySetToNull, Each, DataClass) |
| val knownNonValidationAnnotations = internalAnnotations + Each + Nullable |
| |
| /** |
| * @return whether the given feature is enabled |
| */ |
| operator fun FeatureFlag.invoke(): Boolean { |
| if (cliArgs.contains("--no-$kebabCase")) return false |
| if (cliArgs.contains("--$kebabCase")) return true |
| |
| val annotationKey = "gen$upperCamelCase" |
| val annotationHiddenKey = "genHidden$upperCamelCase" |
| if (dataClassAnnotationFeatures.containsKey(annotationKey)) { |
| return dataClassAnnotationFeatures[annotationKey]!! |
| } |
| if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) { |
| return dataClassAnnotationFeatures[annotationHiddenKey]!! |
| } |
| |
| if (cliArgs.contains("--all")) return true |
| if (hidden) return true |
| |
| return when (this) { |
| FeatureFlag.SETTERS -> |
| !FeatureFlag.CONSTRUCTOR() && !FeatureFlag.BUILDER() && fields.any { !it.isFinal } |
| FeatureFlag.BUILDER -> cliArgs.contains(FLAG_BUILDER_PROTECTED_SETTERS) |
| || fields.any { it.hasDefault } |
| || onByDefault |
| FeatureFlag.CONSTRUCTOR -> !FeatureFlag.BUILDER() |
| FeatureFlag.PARCELABLE -> "Parcelable" in superInterfaces |
| FeatureFlag.AIDL -> fileInfo.mainClass.nameAsString == ClassName && FeatureFlag.PARCELABLE() |
| FeatureFlag.IMPLICIT_NONNULL -> fields.any { it.isNullable } |
| && fields.none { "@$NonNull" in it.annotations } |
| else -> onByDefault |
| } |
| } |
| |
| val FeatureFlag.hidden: Boolean |
| get(): Boolean { |
| val annotationHiddenKey = "genHidden$upperCamelCase" |
| if (dataClassAnnotationFeatures.containsKey(annotationHiddenKey)) { |
| return dataClassAnnotationFeatures[annotationHiddenKey]!! |
| } |
| return when { |
| cliArgs.contains("--hidden-$kebabCase") -> true |
| else -> false |
| } |
| } |
| |
| |
| |
| inline operator fun <R> invoke(f: ClassPrinter.() -> R): R = run(f) |
| |
| var BuilderClass = CANONICAL_BUILDER_CLASS |
| var BuilderType = BuilderClass + genericArgs |
| val customBaseBuilderAst: ClassOrInterfaceDeclaration? by lazy { |
| nestedClasses.find { it.nameAsString == BASE_BUILDER_CLASS } |
| } |
| |
| val suppressedMembers by lazy { |
| getSuppressedMembers(classAst) |
| } |
| val builderSuppressedMembers by lazy { |
| getSuppressedMembers(customBaseBuilderAst) + suppressedMembers.mapNotNull { |
| if (it.startsWith("$CANONICAL_BUILDER_CLASS.")) { |
| it.removePrefix("$CANONICAL_BUILDER_CLASS.") |
| } else { |
| null |
| } |
| } |
| } |
| |
| private fun getSuppressedMembers(clazz: ClassOrInterfaceDeclaration?): List<String> { |
| return clazz |
| ?.annotations |
| ?.find { it.nameAsString == DataClassSuppress } |
| ?.as_<SingleMemberAnnotationExpr>() |
| ?.memberValue |
| ?.run { |
| when (this) { |
| is ArrayInitializerExpr -> values.map { it.asLiteralStringValueExpr().value } |
| is StringLiteralExpr -> listOf(value) |
| else -> abort("Can't parse annotation arg: $this") |
| } |
| } |
| ?: emptyList() |
| } |
| |
| fun isMethodGenerationSuppressed(name: String, vararg argTypes: String): Boolean { |
| return name in suppressedMembers || hasMethod(name, *argTypes) |
| } |
| |
| fun hasMethod(name: String, vararg argTypes: String): Boolean { |
| val members: List<CallableDeclaration<*>> = |
| if (name == ClassName) classAst.constructors else classAst.methods |
| return members.any { |
| it.name.asString() == name && |
| it.parameters.map { it.type.asString() } == argTypes.toList() |
| } |
| } |
| |
| val lazyTransientFields = classAst.fields |
| .filter { it.isTransient && !it.isStatic } |
| .mapIndexed { i, node -> FieldInfo(index = i, fieldAst = node, classInfo = this) } |
| .filter { hasMethod("lazyInit${it.NameUpperCamel}") } |
| |
| val extendsParcelableClass by lazy { |
| Parcelable !in superInterfaces && superClass != null |
| } |
| |
| init { |
| val builderFactoryOverride = classAst.methods.find { |
| it.isStatic && it.nameAsString == "builder" |
| } |
| if (builderFactoryOverride != null) { |
| BuilderClass = (builderFactoryOverride.type as ClassOrInterfaceType).nameAsString |
| BuilderType = builderFactoryOverride.type.asString() |
| } else { |
| val builderExtension = classAst |
| .childNodes |
| .filterIsInstance(TypeDeclaration::class.java) |
| .find { it.nameAsString == CANONICAL_BUILDER_CLASS } |
| if (builderExtension != null) { |
| BuilderClass = BASE_BUILDER_CLASS |
| val tp = (builderExtension as ClassOrInterfaceDeclaration).typeParameters |
| BuilderType = if (tp.isEmpty()) BuilderClass |
| else "$BuilderClass<${tp.map { it.nameAsString }.joinToString(", ")}>" |
| } |
| } |
| } |
| } |