Skip to content

Commit 3996aab

Browse files
committed
Added generation of builders that will be used for shallow copying in persist method
TODO: Generate shallow copy extension function
1 parent f4c3abc commit 3996aab

File tree

6 files changed

+299
-17
lines changed

6 files changed

+299
-17
lines changed

bee.persistent/src/blaze-processor/kotlin/com/beeproduced/bee/persistent/blaze/processor/BeePersistentBlazeFeature.kt

+15-1
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
197197
val dlsPackageName = input.options
198198
.getOrDefault(BeePersistentBlazeOptions.subPackageDSL, "dsl")
199199
.let { "$packageName.$it" }
200+
val builderPackageName = input.options
201+
.getOrDefault(BeePersistentBlazeOptions.subPackageBuilder, "builder")
202+
.let { "$packageName.$it" }
200203
val depth = input.options
201204
.getOrDefault(BeePersistentBlazeOptions.depth, "2").toInt()
202205

@@ -205,7 +208,8 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
205208
depth,
206209
viewPackageName,
207210
repositoryPackageName,
208-
dlsPackageName
211+
dlsPackageName,
212+
builderPackageName,
209213
)
210214
val resources = ResourcesCodegen(input.codeGenerator)
211215

@@ -243,6 +247,16 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
243247
repoCodeGen.processRepo(repo)
244248
}
245249

250+
val builderCodegen = BeePersistentBuilderCodegen(
251+
input.codeGenerator,
252+
input.dependencies,
253+
input.logger,
254+
views,
255+
inheritedEntities,
256+
config
257+
)
258+
builderCodegen.processRepoBuilder(repos)
259+
246260
val dslCodegen = BeePersistentDSLCodegen(
247261
input.codeGenerator,
248262
resources,

bee.persistent/src/blaze-processor/kotlin/com/beeproduced/bee/persistent/blaze/processor/codegen/BeePersistentBlazeOptions.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ object BeePersistentBlazeOptions {
1212
const val subPackageRepository = "persistentSubPackageRepository"
1313
const val subPackageView = "persistentSubPackageView"
1414
const val subPackageDSL = "persistentSubPackageDSL"
15+
const val subPackageBuilder = "persistentSubPackageBuilder"
1516
}
1617

1718
data class BeePersistentBlazeConfig(
1819
val packageName: String,
1920
val depth: Int,
2021
val viewPackageName: String,
2122
val repositoryPackageName: String,
22-
val dslPackageName: String
23+
val dslPackageName: String,
24+
val builderPackageName: String
2325
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
}

bee.persistent/src/blaze-processor/kotlin/com/beeproduced/bee/persistent/blaze/processor/codegen/BeePersistentInstantiatorCodegen.kt

+2-15
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentInsta
1414
import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentInstantiatorCodegen.PoetConstants.TCI
1515
import com.beeproduced.bee.persistent.blaze.processor.info.*
1616
import com.beeproduced.bee.persistent.blaze.processor.utils.buildUniqueClassName
17+
import com.beeproduced.bee.persistent.blaze.processor.utils.reflectionSetterName
1718
import com.google.devtools.ksp.processing.CodeGenerator
1819
import com.google.devtools.ksp.processing.Dependencies
1920
import com.google.devtools.ksp.processing.KSPLogger
@@ -302,6 +303,7 @@ class BeePersistentInstantiatorCodegen(
302303
resourcesCodegen.addSpringListener("$packageName.$registrationName")
303304
}
304305

306+
// TODO: Use Utils Construction
305307
data class EntityConstruction(
306308
val constructorProps: List<EntityProperty>,
307309
val setterProps: List<EntityProperty>,
@@ -388,19 +390,4 @@ class BeePersistentInstantiatorCodegen(
388390
}
389391
}
390392

391-
private fun KSType.reflectionSetterName(): String {
392-
val rpType = declaration.simpleName.asString()
393-
return when (rpType) {
394-
"Double" -> "setDouble"
395-
"Float" -> "setFloat"
396-
"Long" -> "setLong"
397-
"Int" -> "setInt"
398-
"Short" -> "setShort"
399-
"Byte" -> "setByte"
400-
"Boolean" -> "setBoolean"
401-
"Char" -> "setChar"
402-
else -> "set"
403-
}
404-
}
405-
406393
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.beeproduced.bee.persistent.blaze.processor.utils
2+
3+
import com.beeproduced.bee.persistent.blaze.processor.info.BaseInfo
4+
import com.beeproduced.bee.persistent.blaze.processor.info.EntityProperty
5+
import com.google.devtools.ksp.symbol.KSType
6+
import com.google.devtools.ksp.symbol.Modifier
7+
8+
/**
9+
*
10+
*
11+
* @author Kacper Urbaniec
12+
* @version 2024-01-13
13+
*/
14+
15+
16+
data class AccessInfo(
17+
val props: List<EntityProperty>,
18+
val reflectionProps: List<EntityProperty>
19+
)
20+
21+
fun BaseInfo.accessInfo(jpaPropsOnly: Boolean = true): AccessInfo {
22+
val propsToUse = if (jpaPropsOnly) jpaProperties else properties
23+
24+
val props = mutableListOf<EntityProperty>()
25+
val reflectionProps = mutableListOf<EntityProperty>()
26+
27+
for (prop in propsToUse) {
28+
val modifiers = prop.declaration.modifiers
29+
if (
30+
modifiers.contains(Modifier.PRIVATE) ||
31+
modifiers.contains(Modifier.PROTECTED) ||
32+
modifiers.contains(Modifier.INTERNAL)
33+
) {
34+
reflectionProps.add(prop)
35+
} else
36+
props.add(prop)
37+
}
38+
39+
return AccessInfo(props, reflectionProps)
40+
}
41+
42+
43+
data class ConstructionInfo(
44+
val constructorProps: List<EntityProperty>,
45+
val setterProps: List<EntityProperty>,
46+
val reflectionProps: List<EntityProperty>
47+
)
48+
49+
fun BaseInfo.constructionInfo(jpaPropsOnly: Boolean = true): ConstructionInfo {
50+
val constructor = declaration.primaryConstructor
51+
?: throw IllegalArgumentException("Class [${qualifiedName}] has no primary constructor.")
52+
53+
val propsToUse = if (jpaPropsOnly) jpaProperties else properties
54+
val props = propsToUse
55+
.associateByTo(HashMap(propsToUse.count())) {
56+
it.simpleName
57+
}
58+
59+
val constructorProperties = constructor.parameters.map { parameter ->
60+
val pName = requireNotNull(parameter.name).asString()
61+
if (!parameter.isVal && !parameter.isVar) {
62+
throw IllegalArgumentException("Class [${qualifiedName}] has non managed constructor parameter type [$pName]")
63+
}
64+
val jpaProp = props.remove(pName)
65+
?: throw IllegalArgumentException("Class [${qualifiedName}] has non managed constructor parameter type [$pName]")
66+
67+
jpaProp
68+
}
69+
70+
val setterProperties = mutableListOf<EntityProperty>()
71+
val reflectionProperties = mutableListOf<EntityProperty>()
72+
for (jpaProp in props.values) {
73+
val modifiers = jpaProp.declaration.modifiers
74+
if (
75+
modifiers.contains(Modifier.PRIVATE) ||
76+
modifiers.contains(Modifier.PROTECTED) ||
77+
modifiers.contains(Modifier.INTERNAL)
78+
) {
79+
reflectionProperties.add(jpaProp)
80+
} else if (!jpaProp.declaration.isMutable) {
81+
reflectionProperties.add(jpaProp)
82+
} else setterProperties.add(jpaProp)
83+
}
84+
85+
return ConstructionInfo(
86+
constructorProperties,
87+
setterProperties,
88+
reflectionProperties
89+
)
90+
}
91+
92+
fun KSType.reflectionSetterName(): String {
93+
val rpType = declaration.simpleName.asString()
94+
return when (rpType) {
95+
"Double" -> "setDouble"
96+
"Float" -> "setFloat"
97+
"Long" -> "setLong"
98+
"Int" -> "setInt"
99+
"Short" -> "setShort"
100+
"Byte" -> "setByte"
101+
"Boolean" -> "setBoolean"
102+
"Char" -> "setChar"
103+
else -> "set"
104+
}
105+
}
106+
107+
fun KSType.reflectionGettterName(): String {
108+
val rpType = declaration.simpleName.asString()
109+
return when (rpType) {
110+
"Double" -> "getDouble"
111+
"Float" -> "getFloat"
112+
"Long" -> "getLong"
113+
"Int" -> "getInt"
114+
"Short" -> "getShort"
115+
"Byte" -> "getByte"
116+
"Boolean" -> "getBoolean"
117+
"Char" -> "getChar"
118+
else -> "get"
119+
}
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.beeproduced.bee.persistent.blaze.processor.utils
2+
3+
import com.beeproduced.bee.persistent.blaze.processor.codegen.EntityViewInfo
4+
import com.beeproduced.bee.persistent.blaze.processor.codegen.ViewInfo
5+
import com.beeproduced.bee.persistent.blaze.processor.info.RepoInfo
6+
7+
/**
8+
*
9+
*
10+
* @author Kacper Urbaniec
11+
* @version 2024-01-13
12+
*/
13+
14+
fun ViewInfo.findViewFromRepo(repo: RepoInfo) : EntityViewInfo {
15+
val qualifiedName = repo.entityType.declaration.qualifiedName?.asString()
16+
return coreEntityViewsByQualifiedName[qualifiedName]
17+
?: throw IllegalArgumentException("No view for [$qualifiedName] found")
18+
}

0 commit comments

Comments
 (0)