Skip to content

Commit

Permalink
Added generation of builders that will be used for shallow copying in…
Browse files Browse the repository at this point in the history
… persist method

TODO: Generate shallow copy extension function
  • Loading branch information
kurbaniec committed Jan 13, 2024
1 parent f4c3abc commit 3996aab
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
val dlsPackageName = input.options
.getOrDefault(BeePersistentBlazeOptions.subPackageDSL, "dsl")
.let { "$packageName.$it" }
val builderPackageName = input.options
.getOrDefault(BeePersistentBlazeOptions.subPackageBuilder, "builder")
.let { "$packageName.$it" }
val depth = input.options
.getOrDefault(BeePersistentBlazeOptions.depth, "2").toInt()

Expand All @@ -205,7 +208,8 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
depth,
viewPackageName,
repositoryPackageName,
dlsPackageName
dlsPackageName,
builderPackageName,
)
val resources = ResourcesCodegen(input.codeGenerator)

Expand Down Expand Up @@ -243,6 +247,16 @@ class BeePersistentBlazeFeature : BeeGenerativeFeature {
repoCodeGen.processRepo(repo)
}

val builderCodegen = BeePersistentBuilderCodegen(
input.codeGenerator,
input.dependencies,
input.logger,
views,
inheritedEntities,
config
)
builderCodegen.processRepoBuilder(repos)

val dslCodegen = BeePersistentDSLCodegen(
input.codeGenerator,
resources,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ object BeePersistentBlazeOptions {
const val subPackageRepository = "persistentSubPackageRepository"
const val subPackageView = "persistentSubPackageView"
const val subPackageDSL = "persistentSubPackageDSL"
const val subPackageBuilder = "persistentSubPackageBuilder"
}

data class BeePersistentBlazeConfig(
val packageName: String,
val depth: Int,
val viewPackageName: String,
val repositoryPackageName: String,
val dslPackageName: String
val dslPackageName: String,
val builderPackageName: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.beeproduced.bee.persistent.blaze.processor.codegen

import com.beeproduced.bee.generative.util.PoetMap
import com.beeproduced.bee.generative.util.PoetMap.Companion.addNStatementBuilder
import com.beeproduced.bee.generative.util.toPoetClassName
import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentBuilderCodegen.PoetConstants.CLAZZ
import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentBuilderCodegen.PoetConstants.FIELD
import com.beeproduced.bee.persistent.blaze.processor.info.EntityInfo
import com.beeproduced.bee.persistent.blaze.processor.info.EntityProperty
import com.beeproduced.bee.persistent.blaze.processor.info.RepoInfo
import com.beeproduced.bee.persistent.blaze.processor.utils.*
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo

/**
*
*
* @author Kacper Urbaniec
* @version 2024-01-13
*/
typealias FullyQualifiedName = String

class BeePersistentBuilderCodegen(
private val codeGenerator: CodeGenerator,
private val dependencies: Dependencies,
private val logger: KSPLogger,
private val views: ViewInfo,
private val entities: Map<FullyQualifiedName, EntityInfo>,
private val config: BeePersistentBlazeConfig
) {
private val packageName = config.builderPackageName

private val poetMap: PoetMap = PoetMap()
private fun FunSpec.Builder.addNamedStmt(format: String)
= addNStatementBuilder(format, poetMap)
private fun CodeBlock.Builder.addNamedStmt(format: String)
= addNStatementBuilder(format, poetMap)

object PoetConstants {
const val FIELD = "%field:T"
const val CLAZZ = "%clazz:T"
}

init {
poetMap.addMapping(FIELD, ClassName("java.lang.reflect", "Field"))
}

fun processRepoBuilder(repos: List<RepoInfo>) {
for (repo in repos)
processRepoBuilder(repo)
}

private fun processRepoBuilder(repo: RepoInfo) {
logger.info("processRepoBuilder($repo)")
val view = views.findViewFromRepo(repo)
val entity = view.entity


val className = "${entity.uniqueName}Builder"

val file = FileSpec.builder(packageName, className)
if (entity.subClasses == null) file.buildBuilderModule(entity)
else {
val subEntities = entity.subClasses.map { entities.getValue(it) }
for (subEntity in subEntities) file.buildBuilderModule(subEntity)
}

file
.build()
.writeTo(codeGenerator, dependencies)
}

private fun FileSpec.Builder.buildBuilderModule(entity: EntityInfo) = apply {
poetMap.addMapping(CLAZZ, entity.qualifiedName.toPoetClassName())
buildInfo(entity) // Generate reflection info
buildBuilder(entity) // Generate builder for cloning
// Gerate clone extension function
}

private fun FileSpec.Builder.buildBuilder(entity: EntityInfo) = apply {
val entityBuilder = TypeSpec.classBuilder(entity.builderName())

val instance = "instance"
val constructor = FunSpec.constructorBuilder()
.addParameter(instance, poetMap.classMapping(CLAZZ))

entityBuilder.primaryConstructor(constructor.build())

val accessInfo = entity.accessInfo(false)

for (prop in accessInfo.props) {
val propType = prop.type.toTypeName()
val builderProperty = PropertySpec.builder(prop.simpleName, propType)
.initializer("$instance.${prop.simpleName}")
.mutable(true)
entityBuilder.addProperty(builderProperty.build())
}

val infoObjName = entity.infoName()
for (prop in accessInfo.reflectionProps) {
val propType = prop.type.toTypeName()
val getterName = prop.type.reflectionGettterName()
val builderProperty = PropertySpec.builder(prop.simpleName, propType)
.initializer("${infoObjName}.${prop.infoField()}.${getterName}(instance) as %T", propType)
.mutable(true)
entityBuilder.addProperty(builderProperty.build())
}

addType(entityBuilder.build())
}

private fun FileSpec.Builder.buildInfo(entity: EntityInfo) = apply {
val construction = entity.accessInfo(false)
if (construction.reflectionProps.isEmpty()) return@apply

val infoObj = TypeSpec.objectBuilder(entity.infoName())
val entityClassName = entity.declaration.toClassName()
for (property in construction.reflectionProps) {
val reflectionField = PropertySpec
.builder(property.infoField(), poetMap.classMapping(FIELD))
.initializer(
"%T::class.java.getDeclaredField(\"${property.simpleName}\").apply { isAccessible = true }",
entityClassName
)
infoObj.addProperty(reflectionField.build())
}
addType(infoObj.build())
}

private fun EntityInfo.builderName(): String = "${uniqueName}Builder"

private fun EntityInfo.infoName(): String = "${uniqueName}BuilderInfo"

private fun EntityProperty.infoField(): String = "${simpleName}Field"

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentInsta
import com.beeproduced.bee.persistent.blaze.processor.codegen.BeePersistentInstantiatorCodegen.PoetConstants.TCI
import com.beeproduced.bee.persistent.blaze.processor.info.*
import com.beeproduced.bee.persistent.blaze.processor.utils.buildUniqueClassName
import com.beeproduced.bee.persistent.blaze.processor.utils.reflectionSetterName
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.KSPLogger
Expand Down Expand Up @@ -302,6 +303,7 @@ class BeePersistentInstantiatorCodegen(
resourcesCodegen.addSpringListener("$packageName.$registrationName")
}

// TODO: Use Utils Construction
data class EntityConstruction(
val constructorProps: List<EntityProperty>,
val setterProps: List<EntityProperty>,
Expand Down Expand Up @@ -388,19 +390,4 @@ class BeePersistentInstantiatorCodegen(
}
}

private fun KSType.reflectionSetterName(): String {
val rpType = declaration.simpleName.asString()
return when (rpType) {
"Double" -> "setDouble"
"Float" -> "setFloat"
"Long" -> "setLong"
"Int" -> "setInt"
"Short" -> "setShort"
"Byte" -> "setByte"
"Boolean" -> "setBoolean"
"Char" -> "setChar"
else -> "set"
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.beeproduced.bee.persistent.blaze.processor.utils

import com.beeproduced.bee.persistent.blaze.processor.info.BaseInfo
import com.beeproduced.bee.persistent.blaze.processor.info.EntityProperty
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.Modifier

/**
*
*
* @author Kacper Urbaniec
* @version 2024-01-13
*/


data class AccessInfo(
val props: List<EntityProperty>,
val reflectionProps: List<EntityProperty>
)

fun BaseInfo.accessInfo(jpaPropsOnly: Boolean = true): AccessInfo {
val propsToUse = if (jpaPropsOnly) jpaProperties else properties

val props = mutableListOf<EntityProperty>()
val reflectionProps = mutableListOf<EntityProperty>()

for (prop in propsToUse) {
val modifiers = prop.declaration.modifiers
if (
modifiers.contains(Modifier.PRIVATE) ||
modifiers.contains(Modifier.PROTECTED) ||
modifiers.contains(Modifier.INTERNAL)
) {
reflectionProps.add(prop)
} else
props.add(prop)
}

return AccessInfo(props, reflectionProps)
}


data class ConstructionInfo(
val constructorProps: List<EntityProperty>,
val setterProps: List<EntityProperty>,
val reflectionProps: List<EntityProperty>
)

fun BaseInfo.constructionInfo(jpaPropsOnly: Boolean = true): ConstructionInfo {
val constructor = declaration.primaryConstructor
?: throw IllegalArgumentException("Class [${qualifiedName}] has no primary constructor.")

val propsToUse = if (jpaPropsOnly) jpaProperties else properties
val props = propsToUse
.associateByTo(HashMap(propsToUse.count())) {
it.simpleName
}

val constructorProperties = constructor.parameters.map { parameter ->
val pName = requireNotNull(parameter.name).asString()
if (!parameter.isVal && !parameter.isVar) {
throw IllegalArgumentException("Class [${qualifiedName}] has non managed constructor parameter type [$pName]")
}
val jpaProp = props.remove(pName)
?: throw IllegalArgumentException("Class [${qualifiedName}] has non managed constructor parameter type [$pName]")

jpaProp
}

val setterProperties = mutableListOf<EntityProperty>()
val reflectionProperties = mutableListOf<EntityProperty>()
for (jpaProp in props.values) {
val modifiers = jpaProp.declaration.modifiers
if (
modifiers.contains(Modifier.PRIVATE) ||
modifiers.contains(Modifier.PROTECTED) ||
modifiers.contains(Modifier.INTERNAL)
) {
reflectionProperties.add(jpaProp)
} else if (!jpaProp.declaration.isMutable) {
reflectionProperties.add(jpaProp)
} else setterProperties.add(jpaProp)
}

return ConstructionInfo(
constructorProperties,
setterProperties,
reflectionProperties
)
}

fun KSType.reflectionSetterName(): String {
val rpType = declaration.simpleName.asString()
return when (rpType) {
"Double" -> "setDouble"
"Float" -> "setFloat"
"Long" -> "setLong"
"Int" -> "setInt"
"Short" -> "setShort"
"Byte" -> "setByte"
"Boolean" -> "setBoolean"
"Char" -> "setChar"
else -> "set"
}
}

fun KSType.reflectionGettterName(): String {
val rpType = declaration.simpleName.asString()
return when (rpType) {
"Double" -> "getDouble"
"Float" -> "getFloat"
"Long" -> "getLong"
"Int" -> "getInt"
"Short" -> "getShort"
"Byte" -> "getByte"
"Boolean" -> "getBoolean"
"Char" -> "getChar"
else -> "get"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.beeproduced.bee.persistent.blaze.processor.utils

import com.beeproduced.bee.persistent.blaze.processor.codegen.EntityViewInfo
import com.beeproduced.bee.persistent.blaze.processor.codegen.ViewInfo
import com.beeproduced.bee.persistent.blaze.processor.info.RepoInfo

/**
*
*
* @author Kacper Urbaniec
* @version 2024-01-13
*/

fun ViewInfo.findViewFromRepo(repo: RepoInfo) : EntityViewInfo {
val qualifiedName = repo.entityType.declaration.qualifiedName?.asString()
return coreEntityViewsByQualifiedName[qualifiedName]
?: throw IllegalArgumentException("No view for [$qualifiedName] found")
}

0 comments on commit 3996aab

Please sign in to comment.