diff --git a/build.gradle b/build.gradle index 7e714638..120a4420 100755 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,8 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:8.0.1' + classpath("com.android.library:com.android.library.gradle.plugin:8.1.4") + classpath("com.android.application:com.android.application.gradle.plugin:8.1.4") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.21" classpath "org.jlleitschuh.gradle:ktlint-gradle:11.5.1" // NOTE: Do not place your application dependencies here; they belong diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/CrystalCreator.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/CrystalCreator.kt new file mode 100644 index 00000000..bb162e10 --- /dev/null +++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/CrystalCreator.kt @@ -0,0 +1,8 @@ +package com.schwarz.crystalapi + +abstract class CrystalCreator { + + abstract fun create(): T + + abstract fun create(map: MutableMap): T +} diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/Field.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/Field.kt index 9edf4b9e..5070b519 100644 --- a/crystal-map-api/src/main/java/com/schwarz/crystalapi/Field.kt +++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/Field.kt @@ -3,4 +3,4 @@ package com.schwarz.crystalapi import kotlin.reflect.KClass @Retention(AnnotationRetention.BINARY) -annotation class Field(val name: String = "", val type: KClass, val list: Boolean = false, val defaultValue: String = "", val readonly: Boolean = false, val comment: Array = []) +annotation class Field(val name: String = "", val type: KClass, val list: Boolean = false, val defaultValue: String = "", val readonly: Boolean = false, val comment: Array = [], val mandatory: Boolean = false) diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/MandatoryCheck.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/MandatoryCheck.kt new file mode 100644 index 00000000..7df2d725 --- /dev/null +++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/MandatoryCheck.kt @@ -0,0 +1,5 @@ +package com.schwarz.crystalapi + +interface MandatoryCheck { + fun validate() +} diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/WrapperCompanion.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/WrapperCompanion.kt new file mode 100644 index 00000000..6056455a --- /dev/null +++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/WrapperCompanion.kt @@ -0,0 +1,37 @@ +package com.schwarz.crystalapi + +abstract class WrapperCompanion : CrystalCreator() { + + fun fromMap(obj: MutableMap?): T? { + if (obj == null) { + return null + } + return create(obj) + } + + fun fromMap(obj: List>?): List? { + if (obj == null) { + return null + } + var result = ArrayList() + for (entry in obj) { + result.add(create(entry)) + } + return result + } + + abstract fun toMap(obj: T?): MutableMap + + fun toMap(obj: List?): List> { + if (obj == null) { + return listOf() + } + var result = ArrayList>() + for (entry in obj) { + var temp = mutableMapOf() + temp.putAll(toMap(entry)!!) + result.add(temp) + } + return result + } +} diff --git a/crystal-map-api/src/main/java/com/schwarz/crystalapi/schema/EntitySchema.kt b/crystal-map-api/src/main/java/com/schwarz/crystalapi/schema/EntitySchema.kt index 6619a43f..047222a5 100644 --- a/crystal-map-api/src/main/java/com/schwarz/crystalapi/schema/EntitySchema.kt +++ b/crystal-map-api/src/main/java/com/schwarz/crystalapi/schema/EntitySchema.kt @@ -2,7 +2,7 @@ package com.schwarz.crystalapi.schema data class EntitySchema(val name: String, val fields: List, val basedOn: List, val queries: List, val docId: DocId?, val deprecatedSchema: DeprecatedSchema?) -data class Fields(val dbField: String, val fieldType: String, val isIterable: Boolean, val isConstant: Boolean, val defaultValue: String) +data class Fields(val dbField: String, val fieldType: String, val isIterable: Boolean, val isConstant: Boolean, val defaultValue: String, val mandatory: Boolean?) data class DeprecatedSchema(val replacedBy: String?, val inUse: Boolean, val deprecatedFields: List) 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 05617ad4..cf6efd26 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 @@ -20,6 +20,15 @@ object CrystalWrap { } ?: null } + inline fun validate( + doc: MutableMap, + mandatoryFields: Array + ) { + for (mandatoryField in mandatoryFields) { + doc[mandatoryField]!! + } + } + inline fun getList( changes: MutableMap, doc: MutableMap, diff --git a/crystal-map-api/src/test/java/com/schwarz/crystalapi/util/CrystalWrapTest.kt b/crystal-map-api/src/test/java/com/schwarz/crystalapi/util/CrystalWrapTest.kt new file mode 100644 index 00000000..bb0b58e8 --- /dev/null +++ b/crystal-map-api/src/test/java/com/schwarz/crystalapi/util/CrystalWrapTest.kt @@ -0,0 +1,16 @@ +package com.schwarz.crystalapi.util + +import org.junit.Assert +import org.junit.Test + +class CrystalWrapTest { + @Test + fun `test validate throws exception on null value`() { + val values = mutableMapOf("foo" to "bar") + try { + CrystalWrap.validate(values, arrayOf("foo", "foobar")) + Assert.fail("there should be an exception") + } catch (e: NullPointerException) { + } + } +} diff --git a/crystal-map-api/src/test/java/kaufland/com/coachbasebinderapi/ExampleUnitTest.java b/crystal-map-api/src/test/java/kaufland/com/coachbasebinderapi/ExampleUnitTest.java deleted file mode 100644 index 8a1d152b..00000000 --- a/crystal-map-api/src/test/java/kaufland/com/coachbasebinderapi/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.schwarz.crystalapi; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/crystal-map-processor/build.gradle b/crystal-map-processor/build.gradle index dc59d0bf..43b2332e 100644 --- a/crystal-map-processor/build.gradle +++ b/crystal-map-processor/build.gradle @@ -19,8 +19,8 @@ dependencies { testImplementation 'org.mockito:mockito-core:1.10.19' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - kapt "com.google.auto.service:auto-service:1.0-rc7" - compileOnly "com.google.auto.service:auto-service:1.0-rc7" + kapt "com.google.auto.service:auto-service:1.1.1" + compileOnly "com.google.auto.service:auto-service:1.1.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/BuilderClassGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/BuilderClassGeneration.kt index 1a365c87..6868f668 100644 --- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/BuilderClassGeneration.kt +++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/BuilderClassGeneration.kt @@ -9,13 +9,23 @@ import com.squareup.kotlinpoet.TypeSpec object BuilderClassGeneration { fun generateBaseBuilder(holder: BaseEntityHolder): TypeSpec.Builder { - val builderBuilder = TypeSpec.classBuilder("Builder").primaryConstructor(FunSpec.constructorBuilder().addParameter("parent", holder.entityTypeName).build()) - builderBuilder.addProperty(PropertySpec.builder("obj", holder.entityTypeName).initializer("parent").build()) - builderBuilder.addFunction(FunSpec.builder("exit").addStatement("return obj").returns(holder.entityTypeName).build()) + val builderBuilder = TypeSpec.classBuilder("Builder").primaryConstructor( + FunSpec.constructorBuilder().addParameter("parent", holder.entityTypeName).build() + ) + builderBuilder.addProperty( + PropertySpec.builder("obj", holder.entityTypeName).initializer("parent").build() + ) + builderBuilder.addFunction( + FunSpec.builder("exit") + .addStatement("obj.validate()") + .addStatement("return obj") + .returns(holder.entityTypeName).build() + ) return builderBuilder } fun generateBuilderFun(holder: BaseEntityHolder): FunSpec { - return FunSpec.builder("builder").addStatement("return Builder(this)").returns(ClassName(holder.sourcePackage, "${holder.entitySimpleName}.Builder")).build() + return FunSpec.builder("builder").addStatement("return Builder(this)") + .returns(ClassName(holder.sourcePackage, "${holder.entitySimpleName}.Builder")).build() } } 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 34dbcf02..4f5e93ec 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 @@ -13,6 +13,7 @@ import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.jvm.throws import com.schwarz.crystalapi.Entity +import com.schwarz.crystalapi.MandatoryCheck import com.schwarz.crystalapi.PersistenceConfig import com.schwarz.crystalapi.PersistenceException @@ -31,6 +32,7 @@ class EntityGeneration { fun generateModel(holder: EntityHolder, useSuspend: Boolean): FileSpec { val companionSpec = TypeSpec.companionObjectBuilder() + companionSpec.superclass(TypeUtil.crystalCreator(TypeUtil.any(), holder.entityTypeName)) companionSpec.addProperty(idConstant()) companionSpec.addProperty(CblReduceGeneration.onlyIncludeProperty(holder)) companionSpec.addFunctions(create(holder, useSuspend)) @@ -58,10 +60,12 @@ class EntityGeneration { .addModifiers(KModifier.PUBLIC) .addSuperinterface(TypeUtil.iEntity()) .addSuperinterface(holder.interfaceTypeName) + .addSuperinterface(MandatoryCheck::class) .addProperty(holder.dbNameProperty()) .addFunction(EnsureTypesGeneration.ensureTypes(holder, false)) .addFunction(CblDefaultGeneration.addDefaults(holder, false)) .addFunction(CblConstantGeneration.addConstants(holder, false)) + .addFunction(ValidateMethodGeneration.generate(holder, true)) .addProperty( PropertySpec.builder( "mDoc", @@ -250,11 +254,16 @@ class EntityGeneration { .throws(PersistenceException::class) val idFields = holder.docId?.distinctFieldAccessors(holder) ?: emptyList() - if (holder.deprecated?.addDeprecatedFunctions(idFields.toTypedArray(), saveBuilder) == true) { + if (holder.deprecated?.addDeprecatedFunctions( + idFields.toTypedArray(), + saveBuilder + ) == true + ) { saveBuilder.addStatement("// workaround for kotlin poet to create brackets") saveBuilder.addStatement("throw %T()", UnsupportedOperationException::class) } else { saveBuilder.addStatement("val doc = toMap()") + saveBuilder.addStatement("validate()") var idResolve = "getId()" holder.docId?.let { @@ -299,13 +308,13 @@ class EntityGeneration { PersistenceConfig::class, holder.dbName ).returns(holder.entityTypeName).build(), - FunSpec.builder("create").addModifiers(evaluateModifiers(useSuspend)) + FunSpec.builder("create").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) .addAnnotation(JvmStatic::class).addStatement( "return %N(%T())", holder.entitySimpleName, TypeUtil.hashMapStringAny() ).returns(holder.entityTypeName).build(), - FunSpec.builder("create").addModifiers(KModifier.PUBLIC) + FunSpec.builder("create").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) .addParameter("map", TypeUtil.mutableMapStringAny()).addAnnotation(JvmStatic::class) .addStatement( "return %N(map)", diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/ValidateMethodGeneration.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/ValidateMethodGeneration.kt new file mode 100644 index 00000000..b84a8a6e --- /dev/null +++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/generation/model/ValidateMethodGeneration.kt @@ -0,0 +1,32 @@ +package com.schwarz.crystalprocessor.generation.model + +import com.schwarz.crystalapi.util.CrystalWrap +import com.schwarz.crystalprocessor.model.entity.BaseEntityHolder +import com.schwarz.crystalprocessor.util.TypeUtil +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier + +object ValidateMethodGeneration { + + fun generate(holder: BaseEntityHolder, useMDocChanges: Boolean): FunSpec { + val validateBuilder = + FunSpec.builder("validate").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) + val mandatoryFields = holder.fields.values.filter { it.mandatory }.map { it.constantName } + + if (mandatoryFields.isNotEmpty()) { + val statement = + "%T.validate(toMap(), %M(${mandatoryFields.map { "%N" }.joinToString()}))" + val arguments = mutableListOf(CrystalWrap::class, TypeUtil.arrayOf()) + for (mandatoryField in mandatoryFields) { + arguments.add(mandatoryField) + } + + validateBuilder.addStatement( + statement, + *arguments.toTypedArray() + ) + } + + return validateBuilder.build() + } +} 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 ba1f9255..135ea122 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 @@ -1,5 +1,6 @@ package com.schwarz.crystalprocessor.generation.model +import com.schwarz.crystalapi.MandatoryCheck import com.schwarz.crystalprocessor.generation.MapifyableImplGeneration import com.schwarz.crystalprocessor.model.entity.BaseEntityHolder import com.schwarz.crystalprocessor.model.entity.WrapperEntityHolder @@ -11,6 +12,7 @@ class WrapperGeneration { fun generateModel(holder: WrapperEntityHolder, useSuspend: Boolean): FileSpec { val companionSpec = TypeSpec.companionObjectBuilder() + companionSpec.superclass(TypeUtil.wrapperCompanion(holder.entityTypeName)) val builderBuilder = BuilderClassGeneration.generateBaseBuilder(holder) @@ -18,11 +20,13 @@ class WrapperGeneration { .addSuperinterface(TypeUtil.mapSupport()) .addModifiers(KModifier.PUBLIC) .addSuperinterface(holder.interfaceTypeName) + .addSuperinterface(MandatoryCheck::class) .addFunction(EnsureTypesGeneration.ensureTypes(holder, true)) .addFunction(CblDefaultGeneration.addDefaults(holder, true)) .addFunction(CblConstantGeneration.addConstants(holder, true)) .addFunction(SetAllMethodGeneration().generate(holder, false)) .addFunction(MapSupportGeneration.toMap(holder)) + .addFunction(ValidateMethodGeneration.generate(holder, false)) .addProperty(PropertySpec.builder("mDoc", TypeUtil.mutableMapStringAnyNullable()).addModifiers(KModifier.PRIVATE).mutable().initializer("%T()", TypeUtil.linkedHashMapStringAnyNullable()).build()) .addFunction(constructorMap()) .addFunction(constructorDefault()) @@ -52,7 +56,6 @@ class WrapperGeneration { } } - companionSpec.addFunctions(fromMap(holder)) companionSpec.addFunctions(toMap(holder)) companionSpec.addFunctions(create(holder)) typeBuilder.addType(companionSpec.build()) @@ -66,54 +69,19 @@ class WrapperGeneration { private fun toMap(holder: BaseEntityHolder): List { val nullCheck = CodeBlock.builder().beginControlFlow("if(obj == null)").addStatement("return mutableMapOf()").endControlFlow().build() - val nullCheckList = CodeBlock.builder().beginControlFlow("if(obj == null)").addStatement("return listOf()").endControlFlow().build() return Arrays.asList( - FunSpec.builder("toMap").addModifiers(KModifier.PUBLIC) + FunSpec.builder("toMap").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE) .addParameter("obj", holder.entityTypeName.copy(nullable = true)).returns(TypeUtil.mutableMapStringAny()) .addAnnotation(JvmStatic::class) .addCode(nullCheck).addStatement("var result = mutableMapOf<%T,%T>()", TypeUtil.string(), TypeUtil.any()) .beginControlFlow("obj.mDoc.forEach") .beginControlFlow("if(it.value != null)").addStatement("result[it.key] = it.value!!").endControlFlow() .endControlFlow() - .addStatement("return result").build(), - - FunSpec.builder("toMap").addModifiers(KModifier.PUBLIC) - .addParameter("obj", TypeUtil.list(holder.entityTypeName).copy(nullable = true)).addAnnotation(JvmStatic::class) - .returns(TypeUtil.listWithMutableMapStringAny()).addCode(nullCheckList) - .addStatement("var result = %T()", TypeUtil.arrayListWithMutableMapStringAny()) - .addCode( - CodeBlock.builder() - .beginControlFlow("for(entry in obj)") - .addStatement("var temp = mutableMapOf<%T,%T>()", TypeUtil.string(), TypeUtil.any()) - .addStatement("temp.putAll(%N.toMap(entry)!!)", holder.entitySimpleName) - .addStatement("result.add(temp)", holder.entitySimpleName).endControlFlow().build() - ) .addStatement("return result").build() ) } - private fun fromMap(holder: BaseEntityHolder): List { - val nullCheck = CodeBlock.builder().beginControlFlow("if(obj == null)").addStatement("return null").endControlFlow().build() - - return Arrays.asList( - FunSpec.builder("fromMap").addModifiers(KModifier.PUBLIC) - .addParameter("obj", TypeUtil.mutableMapStringAnyNullable().copy(nullable = true)).addAnnotation(JvmStatic::class) - .returns(holder.entityTypeName.copy(nullable = true)).addCode(nullCheck) - .addStatement("return %T(obj)", holder.entityTypeName).build(), - - FunSpec.builder("fromMap").addModifiers(KModifier.PUBLIC).addAnnotation(JvmStatic::class) - .addParameter("obj", TypeUtil.listWithMutableMapStringAnyNullable().copy(nullable = true)) - .returns(TypeUtil.list(holder.entityTypeName).copy(nullable = true)).addCode(nullCheck) - .addStatement("var result = %T()", TypeUtil.arrayList(holder.entityTypeName)) - .addCode( - CodeBlock.builder().beginControlFlow("for(entry in obj)") - .addStatement("result.add(%N(entry))", holder.entitySimpleName) - .endControlFlow().build() - ).addStatement("return result").build() - ) - } - private fun constructorMap(): FunSpec { return FunSpec.constructorBuilder().addModifiers(KModifier.PUBLIC).addParameter("doc", TypeUtil.mutableMapStringAnyNullable()).addStatement("rebind(ensureTypes(doc))").build() } @@ -125,11 +93,11 @@ class WrapperGeneration { private fun create(holder: WrapperEntityHolder): List { return Arrays.asList( - FunSpec.builder("create").addModifiers(KModifier.PUBLIC).addParameter("doc", TypeUtil.mutableMapStringAnyNullable()).addAnnotation(JvmStatic::class).addStatement( + FunSpec.builder("create").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).addParameter("doc", TypeUtil.mutableMapStringAnyNullable()).addAnnotation(JvmStatic::class).addStatement( "return %N(doc)", holder.entitySimpleName ).returns(holder.entityTypeName).build(), - FunSpec.builder("create").addModifiers(KModifier.PUBLIC).addAnnotation(JvmStatic::class).addStatement( + FunSpec.builder("create").addModifiers(KModifier.PUBLIC, KModifier.OVERRIDE).addAnnotation(JvmStatic::class).addStatement( "return %N(%T())", holder.entitySimpleName, TypeUtil.hashMapStringAnyNullable() diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/meta/SchemaGenerator.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/meta/SchemaGenerator.kt index bea2d9d0..39ae697f 100644 --- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/meta/SchemaGenerator.kt +++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/meta/SchemaGenerator.kt @@ -40,7 +40,7 @@ class SchemaGenerator(path: String, val fileName: String) { private fun DeprecatedModel.deprecatedToSchema(): DeprecatedSchema = DeprecatedSchema(replacedBy = this.replacedByTypeMirror.toString(), inUse = this.deprecationType == DeprecationType.FIELD_DEPRECATION || this.deprecationType == DeprecationType.ENTITY_DEPRECATION, deprecatedFields = this.deprecatedFields.values.map { DeprecatedFields(field = it.field, replacedBy = it.replacedBy, inUse = it.inUse) }) - private fun List.fieldsToSchemaList(): List = map { Fields(dbField = it.dbField, fieldType = it.fieldType.toString(), isIterable = it.isIterable, isConstant = it.isConstant, defaultValue = it.defaultValue) } + private fun List.fieldsToSchemaList(): List = map { Fields(dbField = it.dbField, fieldType = it.fieldType.toString(), isIterable = it.isIterable, isConstant = it.isConstant, defaultValue = it.defaultValue, mandatory = (if (it.mandatory) true else null)) } private fun List.queriesToSchemaList(): List = map { Queries(it.fields.asList()) } } diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblBaseFieldHolder.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblBaseFieldHolder.kt index 83b83dac..e911917a 100644 --- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblBaseFieldHolder.kt +++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/model/field/CblBaseFieldHolder.kt @@ -33,6 +33,9 @@ abstract class CblBaseFieldHolder(val dbField: String, private val mField: Field val defaultValue: String get() = mField.defaultValue + val mandatory: Boolean + get() = mField.mandatory + val comment: Array get() = mField.comment 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 9b17dbd4..de7a0540 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 @@ -46,12 +46,20 @@ class CblFieldHolder(field: Field, allWrappers: List) : } } - override fun interfaceProperty(isOverride: Boolean, deprecated: DeprecatedModel?): PropertySpec { - val returnType = TypeUtil.parseMetaType(typeMirror, isIterable, subEntitySimpleName) - .copy(nullable = true) + override fun interfaceProperty( + isOverride: Boolean, + deprecated: DeprecatedModel? + ): PropertySpec { + var returnType = TypeUtil.parseMetaType(typeMirror, isIterable, subEntitySimpleName) + + if (mandatory.not()) { + returnType = returnType.copy(nullable = true) + } + val modifiers = listOfNotNull(KModifier.PUBLIC, KModifier.OVERRIDE.takeIf { isOverride }) - val propertyBuilder = PropertySpec.builder(accessorSuffix(), returnType.copy(true), modifiers) - .mutable(true) + val propertyBuilder = + PropertySpec.builder(accessorSuffix(), returnType, modifiers) + .mutable(true) deprecated?.addDeprecated(dbField, propertyBuilder) return propertyBuilder.build() } @@ -62,12 +70,15 @@ class CblFieldHolder(field: Field, allWrappers: List) : useMDocChanges: Boolean, deprecated: DeprecatedModel? ): PropertySpec { - val returnType = TypeUtil.parseMetaType(typeMirror, isIterable, subEntitySimpleName) - .copy(nullable = true) + var returnType = TypeUtil.parseMetaType(typeMirror, isIterable, subEntitySimpleName) + + if (mandatory.not()) { + returnType = returnType.copy(nullable = true) + } val propertyBuilder = PropertySpec.builder( accessorSuffix(), - returnType.copy(true), + returnType, KModifier.PUBLIC, KModifier.OVERRIDE ).mutable(true) @@ -82,7 +93,7 @@ class CblFieldHolder(field: Field, allWrappers: List) : if (isTypeOfSubEntity) { if (isIterable) { getter.addStatement( - "return %T.getList<%T>($mDocPhrase, %N, %T::class, {%T.fromMap(it) ?: emptyList()})", + "return %T.getList<%T>($mDocPhrase, %N, %T::class, {%T.fromMap(it) ?: emptyList()})".forceCastIfMandatory(mandatory), CrystalWrap::class, subEntityTypeName, constantName, @@ -99,7 +110,7 @@ class CblFieldHolder(field: Field, allWrappers: List) : ) } else { getter.addStatement( - "return %T.get<%T>($mDocPhrase, %N, %T::class, {%T.fromMap(it)})", + "return %T.get<%T>($mDocPhrase, %N, %T::class, {%T.fromMap(it)})".forceCastIfMandatory(mandatory), CrystalWrap::class, subEntityTypeName, constantName, @@ -119,7 +130,7 @@ class CblFieldHolder(field: Field, allWrappers: List) : val forTypeConversion = evaluateClazzForTypeConversion() if (isIterable) { getter.addStatement( - "return %T.getList<%T>($mDocPhrase, %N, %T::class)", + "return %T.getList<%T>($mDocPhrase, %N, %T::class)".forceCastIfMandatory(mandatory), CrystalWrap::class, fieldType, constantName, @@ -127,7 +138,7 @@ class CblFieldHolder(field: Field, allWrappers: List) : ) } else { getter.addStatement( - "return %T.get<%T>($mDocPhrase, %N, %T::class)", + "return %T.get<%T>($mDocPhrase, %N, %T::class)".forceCastIfMandatory(mandatory), CrystalWrap::class, fieldType, constantName, @@ -200,4 +211,11 @@ class CblFieldHolder(field: Field, allWrappers: List) : TypeUtil.parseMetaType(typeMirror, isIterable, false, subEntitySimpleName) } } + + private fun String.forceCastIfMandatory(mandatory: Boolean): String { + if (mandatory) { + return "$this!!" + } + return this + } } diff --git a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/util/TypeUtil.kt b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/util/TypeUtil.kt index bd402e07..e3ff2f6a 100644 --- a/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/util/TypeUtil.kt +++ b/crystal-map-processor/src/main/java/com/schwarz/crystalprocessor/util/TypeUtil.kt @@ -1,5 +1,6 @@ package com.schwarz.crystalprocessor.util +import com.schwarz.crystalapi.CrystalCreator import com.schwarz.crystalprocessor.javaToKotlinType import com.squareup.kotlinpoet.* @@ -7,6 +8,7 @@ import javax.lang.model.type.TypeMirror import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.schwarz.crystalapi.IEntity import com.schwarz.crystalapi.MapSupport +import com.schwarz.crystalapi.WrapperCompanion import com.schwarz.crystalapi.mapify.IMapper import com.schwarz.crystalapi.mapify.IMapifyable import com.schwarz.crystalapi.mapify.Mapifyable @@ -78,6 +80,14 @@ object TypeUtil { return ClassName("kotlin.collections", "List").parameterizedBy(typeName) } + fun crystalCreator(valueType: TypeName, type: TypeName): ParameterizedTypeName { + return CrystalCreator::class.asTypeName().parameterizedBy(listOf(type, valueType)) + } + + fun wrapperCompanion(type: TypeName): ParameterizedTypeName { + return WrapperCompanion::class.asTypeName().parameterizedBy(type) + } + fun arrayList(typeName: TypeName): ParameterizedTypeName { return ClassName("kotlin.collections", "ArrayList").parameterizedBy(typeName) } @@ -169,4 +179,10 @@ object TypeUtil { fun isMap(fieldType: TypeName): Boolean { return fieldType.toString().startsWith("kotlin.collections.Map") } + + fun mapOf() = MemberName("kotlin.collections", "mapOf") + + fun mutableMapOf() = MemberName("kotlin.collections", "mutableMapOf") + + fun arrayOf() = MemberName("kotlin", "arrayOf") } diff --git a/demo/build.gradle b/demo/build.gradle index 280c39f4..b864e720 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -6,7 +6,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.0.1' + classpath("com.android.library:com.android.library.gradle.plugin:8.1.4") + classpath("com.android.application:com.android.application.gradle.plugin:8.1.4") classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" //TODO enable to locally test plugin @@ -103,7 +104,4 @@ dependencies { implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.1' testImplementation 'ch.qos.logback:logback-classic:1.4.4' kapt 'org.apache.logging.log4j:log4j-api:2.19.0' - androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { - exclude group: 'com.android.support', module: 'support-annotations' - }) } diff --git a/demo/src/main/java/com/schwarz/crystaldemo/entity/Product.kt b/demo/src/main/java/com/schwarz/crystaldemo/entity/Product.kt index 955c0f54..d792daff 100644 --- a/demo/src/main/java/com/schwarz/crystaldemo/entity/Product.kt +++ b/demo/src/main/java/com/schwarz/crystaldemo/entity/Product.kt @@ -27,7 +27,8 @@ import com.schwarz.crystalapi.query.Query Field( name = "name", type = String::class, - comment = ["contains the product name.", "and other infos"] + comment = ["contains the product name.", "and other infos"], + mandatory = true ), Field( name = "comments",