1 package com.android.codegen 2 3 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration 4 import com.github.javaparser.ast.expr.* 5 import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations 6 import com.github.javaparser.ast.type.ClassOrInterfaceType 7 import com.github.javaparser.ast.type.Type 8 9 10 fun ClassPrinter.getInputSignatures(): List<String> { 11 return generateInputSignaturesForClass(classAst) + 12 annotationToString(classAst.annotations.find { it.nameAsString == DataClass }) + 13 generateInputSignaturesForClass(customBaseBuilderAst) 14 } 15 16 private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterfaceDeclaration?): List<String> { 17 if (classAst == null) return emptyList() 18 19 return classAst.fields.map { fieldAst -> 20 buildString { 21 append(fieldAst.modifiers.joinToString(" ") { it.keyword.asString() }) 22 append(" ") 23 append(annotationsToString(fieldAst)) 24 append(" ") 25 append(getFullClassName(fieldAst.commonType)) 26 append(" ") 27 append(fieldAst.variables.joinToString(", ") { it.nameAsString }) 28 } 29 } + classAst.methods.map { methodAst -> 30 buildString { 31 append(methodAst.modifiers.joinToString(" ") { it.keyword.asString() }) 32 append(" ") 33 append(annotationsToString(methodAst)) 34 append(" ") 35 append(getFullClassName(methodAst.type)) 36 append(" ") 37 append(methodAst.nameAsString) 38 append("(") 39 append(methodAst.parameters.joinToString(",") { getFullClassName(it.type) }) 40 append(")") 41 } 42 } + ("class ${classAst.nameAsString}" + 43 " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" + 44 " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") + 45 classAst.nestedNonDataClasses.flatMap { nestedClass -> 46 generateInputSignaturesForClass(nestedClass) 47 } 48 } 49 50 private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String { 51 return annotatedAst 52 .annotations 53 .groupBy { it.nameAsString } // dedupe annotations by name (javaparser bug?) 54 .values 55 .joinToString(" ") { 56 annotationToString(it[0]) 57 } 58 } 59 60 private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String { 61 if (ann == null) return "" 62 return buildString { 63 append("@") 64 append(getFullClassName(ann.nameAsString)) 65 if (ann is MarkerAnnotationExpr) return@buildString 66 if (!ann.nameAsString.startsWith("DataClass")) return@buildString 67 68 append("(") 69 70 when (ann) { 71 is SingleMemberAnnotationExpr -> { 72 appendExpr(this, ann.memberValue) 73 } 74 is NormalAnnotationExpr -> { 75 ann.pairs.forEachLastAware { pair, isLast -> 76 append(pair.nameAsString) 77 append("=") 78 appendExpr(this, pair.value) 79 if (!isLast) append(", ") 80 } 81 } 82 } 83 84 append(")") 85 }.replace("\"", "\\\"") 86 } 87 88 private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) { 89 when (ex) { 90 is ClassExpr -> sb.append(getFullClassName(ex.typeAsString)).append(".class") 91 is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L") 92 is LongLiteralExpr -> sb.append(ex.asLong()).append("L") 93 is DoubleLiteralExpr -> sb.append(ex.asDouble()) 94 is ArrayInitializerExpr -> { 95 sb.append("{") 96 ex.values.forEachLastAware { arrayElem, isLast -> 97 appendExpr(sb, arrayElem) 98 if (!isLast) sb.append(", ") 99 } 100 sb.append("}") 101 } 102 else -> sb.append(ex) 103 } 104 } 105 106 private fun ClassPrinter.getFullClassName(type: Type): String { 107 return if (type is ClassOrInterfaceType) { 108 109 getFullClassName(buildString { 110 type.scope.ifPresent { append(it).append(".") } 111 append(type.nameAsString) 112 }) + (type.typeArguments.orElse(null)?.let { args -> args.joinToString(",") {getFullClassName(it)}}?.let { "<$it>" } ?: "") 113 } else getFullClassName(type.asString()) 114 } 115 116 private fun ClassPrinter.getFullClassName(className: String): String { 117 if (className.endsWith("[]")) return getFullClassName(className.removeSuffix("[]")) + "[]" 118 119 if (className.matches("\\.[a-z]".toRegex())) return className //qualified name 120 121 if ("." in className) return getFullClassName(className.substringBeforeLast(".")) + "." + className.substringAfterLast(".") 122 123 fileAst.imports.find { imp -> 124 imp.nameAsString.endsWith(".$className") 125 }?.nameAsString?.let { return it } 126 127 val thisPackagePrefix = fileAst.packageDeclaration.map { it.nameAsString + "." }.orElse("") 128 val thisClassPrefix = thisPackagePrefix + classAst.nameAsString + "." 129 130 if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString 131 132 nestedTypes.find { 133 it.nameAsString == className 134 }?.let { return thisClassPrefix + it.nameAsString } 135 136 if (className == CANONICAL_BUILDER_CLASS || className == BASE_BUILDER_CLASS) { 137 return thisClassPrefix + className 138 } 139 140 constDefs.find { it.AnnotationName == className }?.let { return thisClassPrefix + className } 141 142 if (tryOrNull { Class.forName("java.lang.$className") } != null) { 143 return "java.lang.$className" 144 } 145 146 if (className[0].isLowerCase()) return className //primitive 147 148 if (className[0] == '?') return className //wildcard 149 150 return thisPackagePrefix + className 151 } 152 153 private inline fun <T> tryOrNull(f: () -> T?) = try { 154 f() 155 } catch (e: Exception) { 156 null 157 } 158