From bf23fe11db5b814d20e59477990bbd48ee9244d1 Mon Sep 17 00:00:00 2001
From: Cyrill Halter <cyrill.halter@ergon.ch>
Date: Wed, 24 Jul 2024 16:30:59 +0200
Subject: [PATCH] fix defaults and ensureTypes generation

---
 .../schwarz/crystalapi/util/CrystalWrap.kt    |  68 ++++++++--
 .../generation/model/CblDefaultGeneration.kt  |  30 +++--
 .../generation/model/EnsureTypesGeneration.kt |  44 +-----
 .../generation/model/EntityGeneration.kt      |   2 +-
 .../model/RebindMethodGeneration.kt           |   2 +-
 .../generation/model/WrapperGeneration.kt     |   2 +-
 .../model/field/CblFieldHolder.kt             | 127 ++++++++++--------
 7 files changed, 158 insertions(+), 117 deletions(-)

diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/util/CrystalWrap.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/util/CrystalWrap.kt
index a33e3cd4..4494c189 100644
--- a/crystal-map-api/src/main/java/com/schwarz/crystalapi/util/CrystalWrap.kt
+++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/util/CrystalWrap.kt
@@ -39,26 +39,32 @@ object CrystalWrap {
         }
     }
 
-    inline fun <T> getList(
+    inline fun <reified T> getList(
         changes: MutableMap<String, Any?>,
         doc: MutableMap<String, out Any?>,
         fieldName: String,
-        mapper: ((List<MutableMap<String, Any?>>?) -> List<T>)
+        mapper: ((MutableMap<String, Any?>?) -> T?)
     ): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value ->
         catchTypeConversionError(fieldName, value) {
-            mapper.invoke(value as List<MutableMap<String, Any?>>)
+            (value as List<Any>).mapNotNull {
+                catchTypeConversionError(fieldName, it) {
+                    mapper.invoke(it as MutableMap<String, Any?>)
+                }
+            }
         }
     }
 
-    inline fun <T, reified U> getList(
+    inline fun <reified T, reified U> getList(
         changes: MutableMap<String, Any?>,
         doc: MutableMap<String, out Any?>,
         fieldName: String,
         typeConverter: ITypeConverter<T, U>
     ): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value ->
         catchTypeConversionError(fieldName, value) {
-            ((value as List<Any>).map { it as U }).mapNotNull {
-                typeConverter.read(it)
+            (value as List<Any>).mapNotNull {
+                catchTypeConversionError(fieldName, it) {
+                    typeConverter.read(it as U)
+                }
             }
         }
     }
@@ -69,7 +75,11 @@ object CrystalWrap {
         fieldName: String
     ): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value ->
         catchTypeConversionError(fieldName, value) {
-            (value as List<Any>).map { it as T }
+            (value as List<Any>).mapNotNull {
+                catchTypeConversionError(fieldName, it) {
+                    it as T
+                }
+            }
         }
     }
 
@@ -134,12 +144,50 @@ object CrystalWrap {
         }
     }
 
-    inline fun <reified T> catchTypeConversionError(fieldName: String, value: Any, task: () -> T): T? = try {
+    inline fun <reified DomainType> ensureType(
+        map: HashMap<String, in Any>,
+        key: String,
+        typeConverter: ITypeConverter<DomainType, *>
+    ) {
+        val value = map[key]
+        catchTypeConversionError(key, value) {
+            if (value != null && value is DomainType) {
+                val converted = typeConverter.write(value)
+                converted?.let { map.replace(key, it) }
+            }
+        }
+    }
+
+    inline fun <reified DomainType, reified MapType> ensureListType(
+        map: HashMap<String, in Any>,
+        key: String,
+        typeConverter: ITypeConverter<DomainType, MapType>
+    ) {
+        val value = map[key]
+        if (value != null && value is List<*>) {
+            val converted = value.map {
+                if (it != null && it is DomainType) {
+                    catchTypeConversionError<MapType?>(key, it) {
+                        typeConverter.write(it)
+                    }
+                } else {
+                    it
+                }
+            }
+            map.replace(key, converted)
+        }
+    }
+
+    inline fun <reified T> catchTypeConversionError(
+        fieldName: String,
+        value: Any?,
+        task: () -> T
+    ): T? = try {
         task()
-    } catch (cce: ClassCastException) {
+    } catch (e: Exception) {
         PersistenceConfig.onTypeConversionError(
             com.schwarz.crystalapi.TypeConversionErrorWrapper(
-                cce,
+                e,
                 fieldName,
                 value,
                 T::class
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/CblDefaultGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/CblDefaultGeneration.kt
index de4fd648..9146fb63 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/CblDefaultGeneration.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/CblDefaultGeneration.kt
@@ -1,15 +1,17 @@
 package com.schwarz.crystalprocessor.generation.model
 
 import com.schwarz.crystalprocessor.model.entity.BaseEntityHolder
+import com.schwarz.crystalprocessor.model.typeconverter.TypeConverterHolderForEntityGeneration
 import com.schwarz.crystalprocessor.util.ConversionUtil
 import com.schwarz.crystalprocessor.util.TypeUtil
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.TypeName
 
 object CblDefaultGeneration {
 
-    fun addDefaults(holder: BaseEntityHolder, useNullableMap: Boolean): FunSpec {
+    fun addDefaults(holder: BaseEntityHolder, useNullableMap: Boolean, typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>): FunSpec {
         val type =
             if (useNullableMap) TypeUtil.mutableMapStringAnyNullable() else TypeUtil.mutableMapStringAny()
         val valueType =
@@ -19,23 +21,35 @@ object CblDefaultGeneration {
             if (useNullableMap) TypeUtil.anyNullable() else TypeUtil.any()
 
         val builder =
-            FunSpec.builder("addDefaults").addModifiers(KModifier.PRIVATE)
+            FunSpec.builder("addDefaults").addModifiers(KModifier.PRIVATE).addParameter("map", type)
+
+        builder.addStatement("val result = mutableMapOf<String, Any?>()")
 
         for (fieldHolder in holder.fields.values) {
             if (fieldHolder.isDefault) {
-                builder.addStatement(
-                    "this.%N = ${ConversionUtil.convertStringToDesiredFormat(
+                fieldHolder.crystalWrapSetStatement(
+                    builder,
+                    "result",
+                    typeConvertersByConvertedClass,
+                    ConversionUtil.convertStringToDesiredFormat(
                         fieldHolder.typeMirror,
                         fieldHolder.defaultValue
-                    )}",
-                    fieldHolder.accessorSuffix()
+                    )
                 )
             }
         }
+
+        builder.addCode(
+            CodeBlock.builder()
+                .beginControlFlow("result.forEach")
+                .beginControlFlow("if(it.value != null)").addStatement("map[it.key] = it.value!!").endControlFlow()
+                .endControlFlow()
+                .build()
+        )
         return builder.build()
     }
 
-    fun addAddCall(): CodeBlock {
-        return CodeBlock.builder().addStatement("addDefaults()").build()
+    fun addAddCall(nameOfMap: String): CodeBlock {
+        return CodeBlock.builder().addStatement("addDefaults(%N)", nameOfMap).build()
     }
 }
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EnsureTypesGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EnsureTypesGeneration.kt
index da3f2028..68cca4ff 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EnsureTypesGeneration.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EnsureTypesGeneration.kt
@@ -19,55 +19,17 @@ object EnsureTypesGeneration {
         val explicitType =
             if (useNullableMap) TypeUtil.hashMapStringAnyNullable() else TypeUtil.hashMapStringAny()
         val type = if (useNullableMap) TypeUtil.mapStringAnyNullable() else TypeUtil.mapStringAny()
-        val typeConversionReturnType =
-            if (useNullableMap) TypeUtil.anyNullable() else TypeUtil.any()
         val ensureTypes = FunSpec.builder("ensureTypes").addParameter("doc", type).returns(type)
         ensureTypes.addStatement("val %N = %T()", RESULT_VAL_NAME, explicitType)
         ensureTypes.addStatement("%N.putAll(doc)", RESULT_VAL_NAME)
 
         for (field in holder.fields.values) {
-            if (field.isNonConvertibleClass) {
-                if (field.isIterable) {
-                    ensureTypes.addStatement(
-                        "%T.getList<%T>(mutableMapOf(), %N, %N)",
-                        CrystalWrap::class,
-                        field.fieldType,
-                        RESULT_VAL_NAME,
-                        field.constantName
-                    )
-                } else {
-                    ensureTypes.addStatement(
-                        "%T.get<%T>(mutableMapOf(), %N, %N)",
-                        CrystalWrap::class,
-                        field.fieldType,
-                        RESULT_VAL_NAME,
-                        field.constantName
-                    )
-                }
-            } else if (field.isTypeOfSubEntity) {
-                if (field.isIterable) {
-                    ensureTypes.addStatement(
-                        "%T.getList(mutableMapOf(), %N, %N, {%T.fromMap(it) ?: emptyList()})",
-                        CrystalWrap::class,
-                        RESULT_VAL_NAME,
-                        field.constantName,
-                        field.subEntityTypeName
-                    )
-                } else {
-                    ensureTypes.addStatement(
-                        "%T.get(mutableMapOf(), %N, %N, {%T.fromMap(it)})",
-                        CrystalWrap::class,
-                        RESULT_VAL_NAME,
-                        field.constantName,
-                        field.subEntityTypeName
-                    )
-                }
-            } else {
+            if (!field.isNonConvertibleClass && !field.isTypeOfSubEntity) {
                 val typeConverterHolder =
                     typeConvertersByConvertedClass.get(field.fieldType)!!
                 if (field.isIterable) {
                     ensureTypes.addStatement(
-                        "%T.getList(mutableMapOf(), %N, %N, %T)",
+                        "%T.ensureListType(%N, %N, %T)",
                         CrystalWrap::class,
                         RESULT_VAL_NAME,
                         field.constantName,
@@ -75,7 +37,7 @@ object EnsureTypesGeneration {
                     )
                 } else {
                     ensureTypes.addStatement(
-                        "%T.get(mutableMapOf(), %N, %N, %T)",
+                        "%T.ensureType(%N, %N, %T)",
                         CrystalWrap::class,
                         RESULT_VAL_NAME,
                         field.constantName,
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EntityGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EntityGeneration.kt
index 551037bf..8c7286fc 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EntityGeneration.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/EntityGeneration.kt
@@ -69,7 +69,7 @@ class EntityGeneration {
             .addSuperinterface(MandatoryCheck::class)
             .addProperty(holder.dbNameProperty())
             .addFunction(EnsureTypesGeneration.ensureTypes(holder, false, typeConvertersByConvertedClass))
-            .addFunction(CblDefaultGeneration.addDefaults(holder, false))
+            .addFunction(CblDefaultGeneration.addDefaults(holder, false, typeConvertersByConvertedClass))
             .addFunction(CblConstantGeneration.addConstants(holder, false))
             .addFunction(ValidateMethodGeneration.generate(holder, true))
             .addProperty(
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/RebindMethodGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/RebindMethodGeneration.kt
index 120e6fbe..efe03046 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/RebindMethodGeneration.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/RebindMethodGeneration.kt
@@ -11,7 +11,7 @@ class RebindMethodGeneration {
         val type = if (clearMDocChanges) TypeUtil.mapStringAny() else TypeUtil.mapStringAnyNullable()
         val rebind = FunSpec.builder("rebind").addParameter("doc", type)
             .addStatement("mDoc = %T()", explicitType)
-            .addCode(CblDefaultGeneration.addAddCall())
+            .addCode(CblDefaultGeneration.addAddCall("mDoc"))
             .addCode(
                 CodeBlock.builder()
                     .beginControlFlow("if(doc != null)")
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/WrapperGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/WrapperGeneration.kt
index 07a05274..f45aae7f 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/WrapperGeneration.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/WrapperGeneration.kt
@@ -23,7 +23,7 @@ class WrapperGeneration {
             .addSuperinterface(holder.interfaceTypeName)
             .addSuperinterface(MandatoryCheck::class)
             .addFunction(EnsureTypesGeneration.ensureTypes(holder, true, typeConvertersByConvertedClass))
-            .addFunction(CblDefaultGeneration.addDefaults(holder, true))
+            .addFunction(CblDefaultGeneration.addDefaults(holder, true, typeConvertersByConvertedClass))
             .addFunction(CblConstantGeneration.addConstants(holder, true))
             .addFunction(SetAllMethodGeneration().generate(holder, false))
             .addFunction(MapSupportGeneration.toMap(holder))
diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblFieldHolder.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblFieldHolder.kt
index f569a280..946bee2c 100644
--- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblFieldHolder.kt
+++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblFieldHolder.kt
@@ -101,12 +101,6 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     fieldType,
                     constantName
                 )
-                setter.addStatement(
-                    "%T.setList(%N, %N, value)",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName
-                )
             } else {
                 getter.addStatement(
                     "return %T.get<%T>($mDocPhrase, %N)".forceCastIfMandatory(
@@ -116,17 +110,11 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     fieldType,
                     constantName
                 )
-                setter.addStatement(
-                    "%T.set(%N, %N, value)",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName
-                )
             }
         } else if (isTypeOfSubEntity) {
             if (isIterable) {
                 getter.addStatement(
-                    "return %T.getList<%T>($mDocPhrase, %N, {%T.fromMap(it) ?: emptyList()})".forceCastIfMandatory(
+                    "return %T.getList<%T>($mDocPhrase, %N, {%T.fromMap(it)})".forceCastIfMandatory(
                         mandatory
                     ),
                     CrystalWrap::class,
@@ -134,13 +122,6 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     constantName,
                     subEntityTypeName
                 )
-                setter.addStatement(
-                    "%T.setList(%N, %N, value, {%T.toMap(it)})",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName,
-                    subEntityTypeName
-                )
             } else {
                 getter.addStatement(
                     "return %T.get<%T>($mDocPhrase, %N, {%T.fromMap(it)})".forceCastIfMandatory(
@@ -151,13 +132,6 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     constantName,
                     subEntityTypeName
                 )
-                setter.addStatement(
-                    "%T.set(%N, %N, value, {%T.toMap(it)})",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName,
-                    subEntityTypeName
-                )
             }
         } else {
             val typeConverterHolder =
@@ -171,14 +145,6 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     constantName,
                     typeConverterHolder.instanceClassTypeName
                 )
-
-                setter.addStatement(
-                    "%T.setList(%N, %N, value, %T)",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName,
-                    typeConverterHolder.instanceClassTypeName
-                )
             } else {
                 getter.addStatement(
                     "return %T.get($mDocPhrase, %N, %T)".forceCastIfMandatory(
@@ -188,17 +154,11 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
                     constantName,
                     typeConverterHolder.instanceClassTypeName
                 )
-
-                setter.addStatement(
-                    "%T.set(%N, %N, value, %T)",
-                    CrystalWrap::class,
-                    if (useMDocChanges) "mDocChanges" else "mDoc",
-                    constantName,
-                    typeConverterHolder.instanceClassTypeName
-                )
             }
         }
 
+        crystalWrapSetStatement(setter, if (useMDocChanges) "mDocChanges" else "mDoc", typeConvertersByConvertedClass, "value")
+
         if (comment.isNotEmpty()) {
             propertyBuilder.addKdoc(KDocGeneration.generate(comment))
         }
@@ -206,6 +166,75 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
         return propertyBuilder.setter(setter.build()).getter(getter.build()).build()
     }
 
+    fun crystalWrapSetStatement(
+        setter: FunSpec.Builder,
+        mDocPhrase: String,
+        typeConvertersByConvertedClass: Map<TypeName, TypeConverterHolderForEntityGeneration>,
+        valueName: String
+    ) {
+        if (isNonConvertibleClass) {
+            if (isIterable) {
+                setter.addStatement(
+                    "%T.setList(%N, %N, %L)",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName
+                )
+            } else {
+                setter.addStatement(
+                    "%T.set(%N, %N, %L)",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName
+                )
+            }
+        } else if (isTypeOfSubEntity) {
+            if (isIterable) {
+                setter.addStatement(
+                    "%T.setList(%N, %N, %L, {%T.toMap(it)})",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName,
+                    subEntityTypeName
+                )
+            } else {
+                setter.addStatement(
+                    "%T.set(%N, %N, %L, {%T.toMap(it)})",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName,
+                    subEntityTypeName
+                )
+            }
+        } else {
+            val typeConverterHolder =
+                typeConvertersByConvertedClass.get(fieldType)!!
+            if (isIterable) {
+                setter.addStatement(
+                    "%T.setList(%N, %N, %L, %T)",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName,
+                    typeConverterHolder.instanceClassTypeName
+                )
+            } else {
+                setter.addStatement(
+                    "%T.set(%N, %N, %L, %T)",
+                    CrystalWrap::class,
+                    mDocPhrase,
+                    constantName,
+                    valueName,
+                    typeConverterHolder.instanceClassTypeName
+                )
+            }
+        }
+    }
+
     override fun builderSetter(
         dbName: String?,
         packageName: String,
@@ -244,18 +273,6 @@ class CblFieldHolder(field: Field, classPaths: List<String>, subEntityNameSuffix
         return listOf(fieldAccessorConstant)
     }
 
-    fun evaluateClazzForTypeConversion(): TypeName {
-        return if (isIterable) {
-            if (TypeUtil.isMap(fieldType)) {
-                TypeUtil.string()
-            } else {
-                fieldType
-            }
-        } else {
-            TypeUtil.parseMetaType(typeMirror, isIterable, false, subEntitySimpleName)
-        }
-    }
-
     private fun String.forceCastIfMandatory(mandatory: Boolean): String {
         if (mandatory) {
             return "$this!!"