diff options
author | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2020-08-31 21:21:38 -0700 |
commit | 628590d7ec80e10a3fc24b1c18a1afb55cca10a8 (patch) | |
tree | 4b1c3f52d86d7fb53afbe9e9438468588fa489f8 /tools/codegen/src/com/android/codegen/ClassPrinter.kt | |
parent | b11b8ec3aec8bb42f2c07e1c5ac7942da293baa8 (diff) | |
parent | d2d3a20624d968199353ccf6ddbae6f3ac39c9af (diff) |
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507
Merged-In: I3d92a6de21a938f6b352ec26dc23420c0fe02b27
Change-Id: Ifdb80563ef042738778ebb8a7581a97c4e3d96e2
Diffstat (limited to 'tools/codegen/src/com/android/codegen/ClassPrinter.kt')
-rw-r--r-- | tools/codegen/src/com/android/codegen/ClassPrinter.kt | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt new file mode 100644 index 000000000000..b90e1bb3e7e7 --- /dev/null +++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt @@ -0,0 +1,234 @@ +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 + this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden + 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(", ")}>" + } + } + } +}
\ No newline at end of file |