1 import com.google.devtools.ksp.processing.*
2 import com.google.devtools.ksp.symbol.*
3 import com.google.devtools.ksp.validate
4 import java.io.File
5 import java.io.OutputStream
6
OutputStreamnull7 fun OutputStream.appendText(str: String) {
8 this.write(str.toByteArray())
9 }
10 class BuilderProcessor(
11 val codeGenerator: CodeGenerator,
12 val logger: KSPLogger
13 ) : SymbolProcessor {
processnull14 override fun process(resolver: Resolver): List<KSAnnotated> {
15 val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
16 val ret = symbols.filter { !it.validate() }.toList()
17 symbols
18 .filter { it is KSClassDeclaration && it.validate() }
19 .forEach { it.accept(BuilderVisitor(), Unit) }
20 return ret
21 }
22
23 inner class BuilderVisitor : KSVisitorVoid() {
visitClassDeclarationnull24 override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
25 classDeclaration.primaryConstructor!!.accept(this, data)
26 }
27
visitFunctionDeclarationnull28 override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
29 val parent = function.parentDeclaration as KSClassDeclaration
30 val packageName = parent.containingFile!!.packageName.asString()
31 val className = "${parent.simpleName.asString()}Builder"
32 val file = codeGenerator.createNewFile(Dependencies(true, function.containingFile!!), packageName , className)
33 file.appendText("package $packageName\n\n")
34 file.appendText("import HELLO\n\n")
35 file.appendText("class $className{\n")
36 function.parameters.forEach {
37 val name = it.name!!.asString()
38 val typeName = StringBuilder(it.type.resolve().declaration.qualifiedName?.asString() ?: "<ERROR>")
39 val typeArgs = it.type.element!!.typeArguments
40 if (it.type.element!!.typeArguments.isNotEmpty()) {
41 typeName.append("<")
42 typeName.append(
43 typeArgs.map {
44 val type = it.type?.resolve()
45 "${it.variance.label} ${type?.declaration?.qualifiedName?.asString() ?: "ERROR"}" +
46 if (type?.nullability == Nullability.NULLABLE) "?" else ""
47 }.joinToString(", ")
48 )
49 typeName.append(">")
50 }
51 file.appendText(" private var $name: $typeName? = null\n")
52 file.appendText(" internal fun with${name.replaceFirstChar { it.uppercase() } }($name: $typeName): $className {\n")
53 file.appendText(" this.$name = $name\n")
54 file.appendText(" return this\n")
55 file.appendText(" }\n\n")
56 }
57 file.appendText(" internal fun build(): ${parent.qualifiedName!!.asString()} {\n")
58 file.appendText(" return ${parent.qualifiedName!!.asString()}(")
59 file.appendText(
60 function.parameters.map {
61 "${it.name!!.asString()}!!"
62 }.joinToString(", ")
63 )
64 file.appendText(")\n")
65 file.appendText(" }\n")
66 file.appendText("}\n")
67 file.close()
68 }
69 }
70
71 }
72
73 class BuilderProcessorProvider : SymbolProcessorProvider {
createnull74 override fun create(
75 environment: SymbolProcessorEnvironment
76 ): SymbolProcessor {
77 return BuilderProcessor(environment.codeGenerator, environment.logger)
78 }
79 }