1
+ package com.beeproduced.bee.persistent.blaze.processor.codegen
2
+
3
+ import com.beeproduced.bee.generative.util.PoetMap
4
+ import com.beeproduced.bee.generative.util.PoetMap.Companion.addNStatementBuilder
5
+ import com.beeproduced.bee.generative.util.toPoetClassName
6
+ import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentBuilderCodegen.PoetConstants.CLAZZ
7
+ import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentBuilderCodegen.PoetConstants.FIELD
8
+ import com.beeproduced.bee.persistent.blaze.processor.info.EntityInfo
9
+ import com.beeproduced.bee.persistent.blaze.processor.info.EntityProperty
10
+ import com.beeproduced.bee.persistent.blaze.processor.info.RepoInfo
11
+ import com.beeproduced.bee.persistent.blaze.processor.utils.*
12
+ import com.google.devtools.ksp.processing.CodeGenerator
13
+ import com.google.devtools.ksp.processing.Dependencies
14
+ import com.google.devtools.ksp.processing.KSPLogger
15
+ import com.squareup.kotlinpoet.*
16
+ import com.squareup.kotlinpoet.ksp.toClassName
17
+ import com.squareup.kotlinpoet.ksp.toTypeName
18
+ import com.squareup.kotlinpoet.ksp.writeTo
19
+
20
+ /* *
21
+ *
22
+ *
23
+ * @author Kacper Urbaniec
24
+ * @version 2024-01-13
25
+ */
26
+ typealias FullyQualifiedName = String
27
+
28
+ class BeePersistentBuilderCodegen (
29
+ private val codeGenerator : CodeGenerator ,
30
+ private val dependencies : Dependencies ,
31
+ private val logger : KSPLogger ,
32
+ private val views : ViewInfo ,
33
+ private val entities : Map <FullyQualifiedName , EntityInfo >,
34
+ private val config : BeePersistentBlazeConfig
35
+ ) {
36
+ private val packageName = config.builderPackageName
37
+
38
+ private val poetMap: PoetMap = PoetMap ()
39
+ private fun FunSpec.Builder.addNamedStmt (format : String )
40
+ = addNStatementBuilder(format, poetMap)
41
+ private fun CodeBlock.Builder.addNamedStmt (format : String )
42
+ = addNStatementBuilder(format, poetMap)
43
+
44
+ object PoetConstants {
45
+ const val FIELD = " %field:T"
46
+ const val CLAZZ = " %clazz:T"
47
+ }
48
+
49
+ init {
50
+ poetMap.addMapping(FIELD , ClassName (" java.lang.reflect" , " Field" ))
51
+ }
52
+
53
+ fun processRepoBuilder (repos : List <RepoInfo >) {
54
+ for (repo in repos)
55
+ processRepoBuilder(repo)
56
+ }
57
+
58
+ private fun processRepoBuilder (repo : RepoInfo ) {
59
+ logger.info(" processRepoBuilder($repo )" )
60
+ val view = views.findViewFromRepo(repo)
61
+ val entity = view.entity
62
+
63
+
64
+ val className = " ${entity.uniqueName} Builder"
65
+
66
+ val file = FileSpec .builder(packageName, className)
67
+ if (entity.subClasses == null ) file.buildBuilderModule(entity)
68
+ else {
69
+ val subEntities = entity.subClasses.map { entities.getValue(it) }
70
+ for (subEntity in subEntities) file.buildBuilderModule(subEntity)
71
+ }
72
+
73
+ file
74
+ .build()
75
+ .writeTo(codeGenerator, dependencies)
76
+ }
77
+
78
+ private fun FileSpec.Builder.buildBuilderModule (entity : EntityInfo ) = apply {
79
+ poetMap.addMapping(CLAZZ , entity.qualifiedName.toPoetClassName())
80
+ buildInfo(entity) // Generate reflection info
81
+ buildBuilder(entity) // Generate builder for cloning
82
+ // Gerate clone extension function
83
+ }
84
+
85
+ private fun FileSpec.Builder.buildBuilder (entity : EntityInfo ) = apply {
86
+ val entityBuilder = TypeSpec .classBuilder(entity.builderName())
87
+
88
+ val instance = " instance"
89
+ val constructor = FunSpec .constructorBuilder()
90
+ .addParameter(instance, poetMap.classMapping(CLAZZ ))
91
+
92
+ entityBuilder.primaryConstructor(constructor .build())
93
+
94
+ val accessInfo = entity.accessInfo(false )
95
+
96
+ for (prop in accessInfo.props) {
97
+ val propType = prop.type.toTypeName()
98
+ val builderProperty = PropertySpec .builder(prop.simpleName, propType)
99
+ .initializer(" $instance .${prop.simpleName} " )
100
+ .mutable(true )
101
+ entityBuilder.addProperty(builderProperty.build())
102
+ }
103
+
104
+ val infoObjName = entity.infoName()
105
+ for (prop in accessInfo.reflectionProps) {
106
+ val propType = prop.type.toTypeName()
107
+ val getterName = prop.type.reflectionGettterName()
108
+ val builderProperty = PropertySpec .builder(prop.simpleName, propType)
109
+ .initializer(" ${infoObjName} .${prop.infoField()} .${getterName} (instance) as %T" , propType)
110
+ .mutable(true )
111
+ entityBuilder.addProperty(builderProperty.build())
112
+ }
113
+
114
+ addType(entityBuilder.build())
115
+ }
116
+
117
+ private fun FileSpec.Builder.buildInfo (entity : EntityInfo ) = apply {
118
+ val construction = entity.accessInfo(false )
119
+ if (construction.reflectionProps.isEmpty()) return @apply
120
+
121
+ val infoObj = TypeSpec .objectBuilder(entity.infoName())
122
+ val entityClassName = entity.declaration.toClassName()
123
+ for (property in construction.reflectionProps) {
124
+ val reflectionField = PropertySpec
125
+ .builder(property.infoField(), poetMap.classMapping(FIELD ))
126
+ .initializer(
127
+ " %T::class.java.getDeclaredField(\" ${property.simpleName} \" ).apply { isAccessible = true }" ,
128
+ entityClassName
129
+ )
130
+ infoObj.addProperty(reflectionField.build())
131
+ }
132
+ addType(infoObj.build())
133
+ }
134
+
135
+ private fun EntityInfo.builderName (): String = " ${uniqueName} Builder"
136
+
137
+ private fun EntityInfo.infoName (): String = " ${uniqueName} BuilderInfo"
138
+
139
+ private fun EntityProperty.infoField (): String = " ${simpleName} Field"
140
+
141
+ }
0 commit comments