-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #82 from cyrillhalter/typeconverters
Compile-time-safe type converters
- Loading branch information
Showing
71 changed files
with
1,581 additions
and
513 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
crystal-map-api/src/main/java/com/schwarz/crystalapi/ITypeConverter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.schwarz.crystalapi | ||
|
||
interface ITypeConverter<KotlinType, MapType> { | ||
fun write(value: KotlinType?): MapType? | ||
fun read(value: MapType?): KotlinType? | ||
} |
21 changes: 21 additions & 0 deletions
21
crystal-map-api/src/main/java/com/schwarz/crystalapi/ITypeConverterExporter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.schwarz.crystalapi | ||
|
||
import kotlin.reflect.KClass | ||
|
||
interface ITypeConverterExporter { | ||
|
||
val typeConverters: Map<KClass<*>, ITypeConverter<*, *>> | ||
|
||
val typeConverterImportables: List<TypeConverterImportable> | ||
} | ||
|
||
data class TypeConverterImportable( | ||
val typeConverterInstanceClassName: ClassNameDefinition, | ||
val domainClassName: ClassNameDefinition, | ||
val mapClassName: ClassNameDefinition | ||
) | ||
|
||
data class ClassNameDefinition( | ||
val packageName: String, | ||
val className: String | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 0 additions & 13 deletions
13
crystal-map-api/src/main/java/com/schwarz/crystalapi/TypeConversion.java
This file was deleted.
Oops, something went wrong.
5 changes: 5 additions & 0 deletions
5
crystal-map-api/src/main/java/com/schwarz/crystalapi/TypeConverter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.schwarz.crystalapi | ||
|
||
@Retention(AnnotationRetention.RUNTIME) | ||
@Target(AnnotationTarget.CLASS) | ||
annotation class TypeConverter |
5 changes: 5 additions & 0 deletions
5
crystal-map-api/src/main/java/com/schwarz/crystalapi/TypeConverterExporter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.schwarz.crystalapi | ||
|
||
@Retention(AnnotationRetention.RUNTIME) | ||
@Target(AnnotationTarget.CLASS) | ||
annotation class TypeConverterExporter |
7 changes: 7 additions & 0 deletions
7
crystal-map-api/src/main/java/com/schwarz/crystalapi/TypeConverterImporter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.schwarz.crystalapi | ||
|
||
import kotlin.reflect.KClass | ||
|
||
@Retention(AnnotationRetention.RUNTIME) | ||
@Target(AnnotationTarget.CLASS) | ||
annotation class TypeConverterImporter(val typeConverterExporter: KClass<out ITypeConverterExporter>) |
18 changes: 18 additions & 0 deletions
18
crystal-map-api/src/main/java/com/schwarz/crystalapi/typeconverters/EnumConverter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.schwarz.crystalapi.typeconverters | ||
|
||
import com.schwarz.crystalapi.ITypeConverter | ||
import kotlin.reflect.KClass | ||
|
||
class EnumConverter<T : Enum<T>> (private val enumClass: KClass<T>) : ITypeConverter<T, String> { | ||
override fun write(value: T?): String? = | ||
value?.toString() | ||
|
||
override fun read(value: String?): T? = | ||
value?.let { | ||
if (it.isBlank()) { | ||
null | ||
} else { | ||
java.lang.Enum.valueOf(enumClass.java, value) | ||
} | ||
} | ||
} |
207 changes: 109 additions & 98 deletions
207
crystal-map-api/src/main/java/com/schwarz/crystalapi/util/CrystalWrap.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,139 +1,150 @@ | ||
@file:Suppress("UNCHECKED_CAST") | ||
|
||
package com.schwarz.crystalapi.util | ||
|
||
import com.schwarz.crystalapi.ITypeConverter | ||
import com.schwarz.crystalapi.PersistenceConfig | ||
import java.lang.Exception | ||
import kotlin.reflect.KClass | ||
|
||
object CrystalWrap { | ||
|
||
inline fun <T> get( | ||
inline fun <reified T> get( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, Any>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String, | ||
clazz: KClass<*>, | ||
noinline mapper: ((MutableMap<String, Any?>?) -> T?)? = null | ||
): T? { | ||
return (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
mapper?.let { | ||
mapper.invoke(value as? MutableMap<String, Any?>) | ||
} ?: read(value, fieldName, clazz) | ||
} ?: null | ||
mapper: ((MutableMap<String, Any?>?) -> T?) | ||
): T? = (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
catchTypeConversionError(fieldName, value) { | ||
mapper.invoke(value as MutableMap<String, Any?>) | ||
} | ||
} | ||
|
||
inline fun validate( | ||
doc: MutableMap<String, Any>, | ||
mandatoryFields: Array<String> | ||
) { | ||
for (mandatoryField in mandatoryFields) { | ||
doc[mandatoryField]!! | ||
inline fun <reified T, reified U> get( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String, | ||
typeConverter: ITypeConverter<T, U> | ||
): T? = (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
catchTypeConversionError(fieldName, value) { | ||
typeConverter.read(value as U) | ||
} | ||
} | ||
|
||
inline fun <reified T> get( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String | ||
): T? = (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
catchTypeConversionError(fieldName, value) { | ||
value as T | ||
} | ||
} | ||
|
||
inline fun <T> getList( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, Any>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String, | ||
mapper: ((List<MutableMap<String, Any?>>?) -> List<T>) | ||
): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
catchTypeConversionError(fieldName, value) { | ||
mapper.invoke(value as List<MutableMap<String, Any?>>) | ||
} | ||
} | ||
|
||
inline fun <T, reified U> getList( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String, | ||
clazz: KClass<*>, | ||
noinline mapper: ((List<MutableMap<String, Any?>>?) -> List<T>)? = null | ||
): List<T>? { | ||
return (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
mapper?.let { | ||
mapper.invoke(value as? List<MutableMap<String, Any?>>) | ||
} ?: read(value, fieldName, clazz) | ||
} ?: null | ||
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) | ||
} | ||
} | ||
} | ||
|
||
inline fun <reified T> getList( | ||
changes: MutableMap<String, Any?>, | ||
doc: MutableMap<String, out Any?>, | ||
fieldName: String | ||
): List<T>? = (changes[fieldName] ?: doc[fieldName])?.let { value -> | ||
catchTypeConversionError(fieldName, value) { | ||
value as List<T> | ||
} | ||
} | ||
|
||
fun <T> set( | ||
inline fun <T> set( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: T, | ||
clazz: KClass<*>, | ||
mapper: ((T) -> MutableMap<String, Any>)? = null | ||
mapper: ((T) -> MutableMap<String, Any>) | ||
) { | ||
changes[fieldName] = mapper.invoke(value) | ||
} | ||
|
||
inline fun <T> set( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: T?, | ||
typeConverter: ITypeConverter<T, *> | ||
) { | ||
val valueToSet = mapper?.let { it.invoke(value) } ?: write<T>(value, fieldName, clazz) | ||
changes[fieldName] = valueToSet | ||
changes[fieldName] = typeConverter.write(value) | ||
} | ||
|
||
inline fun <T> set( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: T? | ||
) { | ||
changes[fieldName] = value | ||
} | ||
|
||
inline fun <T> setList( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: List<T>?, | ||
clazz: KClass<*>, | ||
noinline mapper: ((List<T>) -> List<MutableMap<String, Any>>)? = null | ||
mapper: ((List<T>) -> List<MutableMap<String, Any>>) | ||
) { | ||
val valueToSet = | ||
mapper?.let { if (value != null) it.invoke(value) else emptyList() } ?: write<T>( | ||
value, | ||
fieldName, | ||
clazz | ||
) | ||
changes[fieldName] = valueToSet | ||
changes[fieldName] = if (value != null) mapper.invoke(value) else emptyList() | ||
} | ||
|
||
fun <T> ensureTypes(map: Map<String, KClass<*>>, doc: Map<String, Any?>): Map<String, Any> { | ||
val result = mutableMapOf<String, Any>() | ||
for (entry in map) { | ||
write<T>(doc[entry.key], entry.key, entry.value)?.let { | ||
result[entry.key] = it | ||
} | ||
} | ||
return result | ||
inline fun <T> setList( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: List<T>?, | ||
typeConverter: ITypeConverter<T, *> | ||
) { | ||
changes[fieldName] = value?.map { typeConverter.write(it) } | ||
} | ||
|
||
fun <T, V> addDefaults(list: List<Array<Any>>, doc: MutableMap<String, V>) { | ||
for (entry in list) { | ||
val key = entry[0] as String | ||
val clazz = entry[1] as KClass<*> | ||
val value = entry[2] as Any | ||
if (doc[key] == null) { | ||
write<T>(value, key, clazz)?.let { | ||
doc[key] = it as V | ||
} | ||
} | ||
} | ||
inline fun <T> setList( | ||
changes: MutableMap<String, Any?>, | ||
fieldName: String, | ||
value: List<T>? | ||
) { | ||
changes[fieldName] = value | ||
} | ||
|
||
fun <T> read( | ||
value: Any?, | ||
fieldName: String, | ||
clazz: KClass<*> | ||
): T? { | ||
return try { | ||
val conversion = | ||
PersistenceConfig.getTypeConversion(clazz) ?: return value as T? | ||
return conversion.read(value) as T? | ||
} catch (ex: Exception) { | ||
PersistenceConfig.onTypeConversionError( | ||
com.schwarz.crystalapi.TypeConversionErrorWrapper( | ||
ex, | ||
fieldName, | ||
value, | ||
clazz | ||
) | ||
) | ||
null | ||
fun validate( | ||
doc: MutableMap<String, Any>, | ||
mandatoryFields: Array<String> | ||
) { | ||
for (mandatoryField in mandatoryFields) { | ||
doc[mandatoryField]!! | ||
} | ||
} | ||
|
||
fun <T> write( | ||
value: Any?, | ||
fieldName: String, | ||
clazz: KClass<*> | ||
): T? { | ||
return try { | ||
val conversion = | ||
PersistenceConfig.getTypeConversion(clazz) ?: return value as T? | ||
return conversion.write(value) as T? | ||
} catch (ex: Exception) { | ||
PersistenceConfig.onTypeConversionError( | ||
com.schwarz.crystalapi.TypeConversionErrorWrapper( | ||
ex, | ||
fieldName, | ||
value, | ||
clazz | ||
) | ||
inline fun <reified T> catchTypeConversionError(fieldName: String, value: Any, task: () -> T): T? = try { | ||
task() | ||
} catch (cce: ClassCastException) { | ||
PersistenceConfig.onTypeConversionError( | ||
com.schwarz.crystalapi.TypeConversionErrorWrapper( | ||
cce, | ||
fieldName, | ||
value, | ||
T::class | ||
) | ||
null | ||
} | ||
) | ||
null | ||
} | ||
} |
Oops, something went wrong.