Skip to content

Commit

Permalink
Merge pull request #86 from jansigi/feature/use-typeconversion-in-sch…
Browse files Browse the repository at this point in the history
…emas

Feature/use typeconversion in schemas
  • Loading branch information
jansigi authored Aug 29, 2024
2 parents edde4bd + f303054 commit 67cb9c2
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 54 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package com.schwarz.crystalapi.schema

interface CMType {
import com.schwarz.crystalapi.ITypeConverter

sealed interface CMType {
val path: String
}

class CMField<T : Any>(val name: String, override val path: String) : CMType
open class CMJsonField<T>(val name: String, override val path: String) : CMType

class CMList<T : Any>(val name: String, override val path: String) : CMType
open class CMJsonList<T>(val name: String, override val path: String) : CMType

class CMObject<out T : Schema>(val element: T, override val path: String) : CMType

class CMObjectList<out T : Schema>(val element: T, val name: String, override val path: String) : CMType

class CMConverterField<KotlinType, MapType>(
name: String,
path: String,
val typeConverter: ITypeConverter<KotlinType, MapType>
) : CMJsonField<MapType>(name, path)

class CMConverterList<KotlinType, MapType>(
name: String,
path: String,
val typeConverter: ITypeConverter<KotlinType, MapType>
) : CMJsonList<MapType>(name, path)
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
package com.schwarz.crystalprocessor.generation.model

import com.schwarz.crystalapi.schema.CMField
import com.schwarz.crystalapi.schema.CMList
import com.schwarz.crystalapi.schema.CMObject
import com.schwarz.crystalapi.schema.CMObjectList
import com.schwarz.crystalapi.schema.Schema
import com.schwarz.crystalapi.schema.*
import com.schwarz.crystalprocessor.model.entity.SchemaClassHolder
import com.schwarz.crystalprocessor.model.field.CblBaseFieldHolder
import com.schwarz.crystalprocessor.model.field.CblFieldHolder
import com.schwarz.crystalprocessor.model.typeconverter.TypeConverterHolderForEntityGeneration
import com.schwarz.crystalprocessor.util.ConversionUtil
import com.schwarz.crystalprocessor.util.TypeUtil
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName

/**
* This class is responsible for generating the Schema classes.
Expand All @@ -31,13 +21,17 @@ import com.squareup.kotlinpoet.asTypeName
*/
class SchemaGeneration {
private val pathAttributeName = "path"
fun generateModel(holder: SchemaClassHolder, schemaClassPaths: List<String>): FileSpec {
fun generateModel(
holder: SchemaClassHolder,
schemaClassPaths: List<String>,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>
): FileSpec {
val packageName = holder.sourcePackage
val schemaClassName = holder.entitySimpleName

val schemaClass: TypeSpec.Builder = buildSchemaClass(schemaClassName)

buildAndAddFieldProperties(holder, schemaClass, schemaClassPaths)
buildAndAddFieldProperties(holder, schemaClass, schemaClassPaths, typeConvertersByConvertedClass)

return FileSpec.builder(packageName, schemaClassName).addType(schemaClass.build()).build()
}
Expand All @@ -58,16 +52,18 @@ class SchemaGeneration {
private fun buildAndAddFieldProperties(
holder: SchemaClassHolder,
schemaClass: TypeSpec.Builder,
schemaClassPaths: List<String>
schemaClassPaths: List<String>,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>
) {
buildAndAddConstantFieldProperties(holder, schemaClass, schemaClassPaths)
buildAndAddNormalFieldProperties(holder, schemaClass, schemaClassPaths)
buildAndAddConstantFieldProperties(holder, schemaClass, schemaClassPaths, typeConvertersByConvertedClass)
buildAndAddNormalFieldProperties(holder, schemaClass, schemaClassPaths, typeConvertersByConvertedClass)
}

private fun buildAndAddConstantFieldProperties(
holder: SchemaClassHolder,
schemaClass: TypeSpec.Builder,
schemaClassPaths: List<String>
schemaClassPaths: List<String>,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>
) {
holder.fieldConstants.forEach { (fieldName, fieldObject) ->
val defaultVariableName = "DEFAULT_${fieldObject.constantName}"
Expand All @@ -88,22 +84,25 @@ class SchemaGeneration {
schemaClass,
fieldName,
fieldObject,
schemaClassPaths
schemaClassPaths,
typeConvertersByConvertedClass
)
}
}

private fun buildAndAddNormalFieldProperties(
holder: SchemaClassHolder,
schemaClass: TypeSpec.Builder,
schemaClassPaths: List<String>
schemaClassPaths: List<String>,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>
) {
holder.fields.forEach { (fieldName, fieldObject) ->
buildAndAddFieldProperty(
schemaClass,
fieldName,
fieldObject,
schemaClassPaths
schemaClassPaths,
typeConvertersByConvertedClass
)
}
}
Expand All @@ -112,31 +111,54 @@ class SchemaGeneration {
schemaClass: TypeSpec.Builder,
fieldName: String,
fieldObject: CblBaseFieldHolder,
schemaClassPaths: List<String>
): TypeSpec.Builder = schemaClass.addProperty(
buildFieldProperty(fieldObject, fieldName, schemaClassPaths)
)
schemaClassPaths: List<String>,
typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>
): TypeSpec.Builder {
val propertyType = typeConvertersByConvertedClass[fieldObject.typeMirror.asTypeName()]
val isObject = schemaClassPaths.contains(fieldObject.typeMirror.toString())
val hasProperty = propertyType != null
val fieldType = getFieldType(fieldObject.isIterable, isObject, hasProperty)
return schemaClass.addProperty(
if (propertyType != null) {
buildConverterFieldProperty(fieldObject, fieldName, propertyType, fieldType)
} else {
buildFieldProperty(fieldObject, fieldName, fieldType, isObject)
}
)
}

private fun buildFieldProperty(
fieldObject: CblBaseFieldHolder,
fieldName: String,
schemaClassPaths: List<String>
outerType: ClassName,
isObject: Boolean
): PropertySpec {
val isObject = schemaClassPaths.contains(fieldObject.typeMirror.toString())

val outerType = getOuterPropertyType(fieldObject.isIterable, isObject)

val innerType: TypeName = getInnerPropertyType(fieldObject)
val genericFieldType = getGenericFieldType(fieldObject)

return PropertySpec.builder(
fieldName,
outerType.parameterizedBy(innerType)
fieldObject.accessorSuffix(),
outerType.parameterizedBy(genericFieldType)
).initializer(
createPropertyFormat(fieldName, innerType, fieldObject.isIterable, isObject),
createPropertyFormat(fieldName, genericFieldType, fieldObject.isIterable, isObject),
outerType
).build()
}

private fun buildConverterFieldProperty(
fieldObject: CblBaseFieldHolder,
fieldName: String,
propertyType: TypeConverterHolderForEntityGeneration,
fieldType: ClassName
): PropertySpec {
return PropertySpec.builder(
fieldObject.accessorSuffix(),
fieldType.parameterizedBy(propertyType.domainClassTypeName, propertyType.mapClassTypeName)
).initializer(
buildConverterFormat(fieldName, propertyType),
fieldType
).build()
}

private fun createPropertyFormat(
fieldName: String,
propertyType: TypeName,
Expand All @@ -153,6 +175,9 @@ class SchemaGeneration {
}
}

private fun buildConverterFormat(fieldName: String, propertyType: TypeConverterHolderForEntityGeneration): String =
"""%T("$fieldName", $pathAttributeName, ${propertyType.instanceClassTypeName})"""

private fun buildObjectListFormat(propertyType: TypeName, fieldName: String, propertyAccessPath: String): String =
"""%T(
$propertyType($propertyAccessPath),
Expand All @@ -169,17 +194,20 @@ class SchemaGeneration {
$pathAttributeName,
)"""

private fun getOuterPropertyType(
private fun getFieldType(
isIterable: Boolean,
isObject: Boolean
isObject: Boolean,
hasProperty: Boolean
) = when {
hasProperty && isIterable -> CMConverterList::class.asTypeName()
hasProperty -> CMConverterField::class.asTypeName()
isIterable && isObject -> CMObjectList::class.asTypeName()
isIterable -> CMList::class.asTypeName()
isIterable -> CMJsonList::class.asTypeName()
isObject -> CMObject::class.asTypeName()
else -> CMField::class.asTypeName()
else -> CMJsonField::class.asTypeName()
}

private fun getInnerPropertyType(field: CblBaseFieldHolder): TypeName {
private fun getGenericFieldType(field: CblBaseFieldHolder): TypeName {
val subEntity = (field as? CblFieldHolder)?.subEntitySimpleName

return TypeUtil.parseMetaType(field.typeMirror, false, subEntity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class ModelWorker(override val logger: Logger, override val codeGenerator: CodeG
}

process(workSet.schemas, generatedInterfaces, useSuspend, typeConvertersByConvertedClass) {
SchemaGeneration().generateModel(it, workSet.schemaClassPaths)
SchemaGeneration().generateModel(it, workSet.schemaClassPaths, typeConvertersByConvertedClass)
}

documentationGenerator?.generate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,31 +207,43 @@ class CouchbaseBaseBinderProcessorKotlinTest {
val expected = File("src/test/resources/ExpectedSchema.txt").readLines()
val testObject = SourceFile.kotlin(
"TestObject.kt",
"package com.kaufland.testModels\n" +
"import com.schwarz.crystalapi.Field\n" +
"import com.schwarz.crystalapi.Fields\n" +
PACKAGE_HEADER +
"import com.schwarz.crystalapi.SchemaClass\n" +
"@SchemaClass\n" +
"class TestObject"
)
val sub = SourceFile.kotlin(
"Sub.kt",
"package com.kaufland.testModels\n" +
PACKAGE_HEADER +
"import com.kaufland.testModels.TestObject\n" +
"import com.schwarz.crystalapi.Field\n" +
"import com.schwarz.crystalapi.Fields\n" +
"import com.schwarz.crystalapi.SchemaClass\n" +
"import java.time.OffsetDateTime\n" +
"@SchemaClass\n" +
"@Fields(\n" +
"Field(name = \"test_test_test\", type = Number::class),\n" +
"Field(name = \"type\", type = String::class, defaultValue = \"test\", readonly = true),\n" +
"Field(name = \"list\", type = String::class, list = true),\n" +
"Field(name = \"someObject\", type = TestObject::class),\n" +
"Field(name = \"objects\", type = TestObject::class, list = true),\n" +
"Field(name = \"date_converter_field\", type = OffsetDateTime::class),\n" +
"Field(name = \"date_converter_list\", type = OffsetDateTime::class, list = true),\n" +
")\n" +
"class Sub"
)
val compilation = compileKotlin(testObject, sub)
val typeConverter = SourceFile.kotlin(
"DateTypeConverter.kt",
PACKAGE_HEADER +
TYPE_CONVERTER_HEADER +
"import java.time.OffsetDateTime\n" +
"@TypeConverter\n" +
"abstract class DateTypeConverter : ITypeConverter<OffsetDateTime, String> {\n" +
"override fun write(value: OffsetDateTime?): String? = value?.toString()\n" +
"override fun read(value: String?): OffsetDateTime? = value?.let { OffsetDateTime.parse(it) }\n" +
"}"
)
val compilation = compileKotlin(typeConverter, testObject, sub)

val actual = compilation.generatedFiles.find { it.name == "SubSchema.kt" }!!.readLines()

Expand Down
21 changes: 16 additions & 5 deletions crystal-map-processor/src/test/resources/ExpectedSchema.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
//
package com.kaufland.testModels

import com.schwarz.crystalapi.schema.CMField
import com.schwarz.crystalapi.schema.CMList
import com.schwarz.crystalapi.schema.CMConverterField
import com.schwarz.crystalapi.schema.CMConverterList
import com.schwarz.crystalapi.schema.CMJsonField
import com.schwarz.crystalapi.schema.CMJsonList
import com.schwarz.crystalapi.schema.CMObject
import com.schwarz.crystalapi.schema.CMObjectList
import com.schwarz.crystalapi.schema.Schema
import java.time.OffsetDateTime
import kotlin.Number
import kotlin.String

Expand All @@ -18,11 +21,11 @@ public open class SubSchema(
) : Schema {
public val DEFAULT_TYPE: String = "test"

public val type: CMField<String> = CMField("type", path)
public val type: CMJsonField<String> = CMJsonField("type", path)

public val test_test_test: CMField<Number> = CMField("test_test_test", path)
public val testTestTest: CMJsonField<Number> = CMJsonField("test_test_test", path)

public val list: CMList<String> = CMList("list", path)
public val list: CMJsonList<String> = CMJsonList("list", path)

public val someObject: CMObject<TestObjectSchema> = CMObject(
com.kaufland.testModels.TestObjectSchema(if (path.isBlank()) "someObject" else
Expand All @@ -36,4 +39,12 @@ public open class SubSchema(
"objects",
path,
)

public val dateConverterField: CMConverterField<OffsetDateTime, String> =
CMConverterField("date_converter_field", path,
com.kaufland.testModels.DateTypeConverterInstance)

public val dateConverterList: CMConverterList<OffsetDateTime, String> =
CMConverterList("date_converter_list", path,
com.kaufland.testModels.DateTypeConverterInstance)
}

0 comments on commit 67cb9c2

Please sign in to comment.