blob: 83108e5ae109f9e068af744f9a58c18b6ddfa58f [file] [log] [blame]
package com.android.codegen
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations
import com.github.javaparser.ast.type.ClassOrInterfaceType
import com.github.javaparser.ast.type.Type
fun ClassPrinter.getInputSignatures(): List<String> {
return generateInputSignaturesForClass(classAst) +
annotationToString(classAst.annotations.find { it.nameAsString == DataClass }) +
generateInputSignaturesForClass(customBaseBuilderAst)
}
private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterfaceDeclaration?): List<String> {
if (classAst == null) return emptyList()
return classAst.fields.map { fieldAst ->
buildString {
append(fieldAst.modifiers.joinToString(" ") { it.keyword.asString() })
append(" ")
append(annotationsToString(fieldAst))
append(" ")
append(getFullClassName(fieldAst.commonType))
append(" ")
append(fieldAst.variables.joinToString(", ") { it.nameAsString })
}
} + classAst.methods.map { methodAst ->
buildString {
append(methodAst.modifiers.joinToString(" ") { it.keyword.asString() })
append(" ")
append(annotationsToString(methodAst))
append(" ")
append(getFullClassName(methodAst.type))
append(" ")
append(methodAst.nameAsString)
append("(")
append(methodAst.parameters.joinToString(",") { getFullClassName(it.type) })
append(")")
}
} + ("class ${classAst.nameAsString}" +
" extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" +
" implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") +
classAst.nestedNonDataClasses.flatMap { nestedClass ->
generateInputSignaturesForClass(nestedClass)
}
}
private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String {
return annotatedAst
.annotations
.groupBy { it.nameAsString } // dedupe annotations by name (javaparser bug?)
.values
.joinToString(" ") {
annotationToString(it[0])
}
}
private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String {
if (ann == null) return ""
return buildString {
append("@")
append(getFullClassName(ann.nameAsString))
if (ann is MarkerAnnotationExpr) return@buildString
if (!ann.nameAsString.startsWith("DataClass")) return@buildString
append("(")
when (ann) {
is SingleMemberAnnotationExpr -> {
appendExpr(this, ann.memberValue)
}
is NormalAnnotationExpr -> {
ann.pairs.forEachLastAware { pair, isLast ->
append(pair.nameAsString)
append("=")
appendExpr(this, pair.value)
if (!isLast) append(", ")
}
}
}
append(")")
}.replace("\"", "\\\"")
}
private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) {
when (ex) {
is ClassExpr -> sb.append(getFullClassName(ex.typeAsString)).append(".class")
is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
is DoubleLiteralExpr -> sb.append(ex.asDouble())
is ArrayInitializerExpr -> {
sb.append("{")
ex.values.forEachLastAware { arrayElem, isLast ->
appendExpr(sb, arrayElem)
if (!isLast) sb.append(", ")
}
sb.append("}")
}
else -> sb.append(ex)
}
}
private fun ClassPrinter.getFullClassName(type: Type): String {
return if (type is ClassOrInterfaceType) {
getFullClassName(buildString {
type.scope.ifPresent { append(it).append(".") }
append(type.nameAsString)
}) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(",") {getFullClassName(it)}}?.let { "<$it>" } ?: "")
} else getFullClassName(type.asString())
}
private fun ClassPrinter.getFullClassName(className: String): String {
if (className.endsWith("[]")) return getFullClassName(className.removeSuffix("[]")) + "[]"
if (className.matches("\\.[a-z]".toRegex())) return className //qualified name
if ("." in className) return getFullClassName(className.substringBeforeLast(".")) + "." + className.substringAfterLast(".")
fileAst.imports.find { imp ->
imp.nameAsString.endsWith(".$className")
}?.nameAsString?.let { return it }
val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("")
val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "."
if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
nestedTypes.find {
it.nameAsString == className
}?.let { return thisClassPrefix + it.nameAsString }
if (className == CANONICAL_BUILDER_CLASS || className == BASE_BUILDER_CLASS) {
return thisClassPrefix + className
}
constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className }
if (tryOrNull { Class.forName("java.lang.$className") } != null) {
return "java.lang.$className"
}
if (className[0].isLowerCase()) return className //primitive
if (className[0] == '?') return className //wildcard
return thisPackagePrefix + className
}
private inline fun <T> tryOrNull(f: () -> T?) = try {
f()
} catch (e: Exception) {
null
}