ConvertersManager.kt
package com.hexagontk.converters
import kotlin.reflect.KClass
/**
* Registry that holds functions to convert from one type to another.
*
* @sample com.hexagontk.converters.ConvertersManagerTest.usageExample
*/
object ConvertersManager {
private var converters: Map<Pair<*, *>, (Any) -> Any> = emptyMap()
/**
* Register a mapping function from one type to another.
*
* @param key Pair which key is the source type and the value is the target type.
* @param block Block that converts an instance of the source type to the target one.
*/
@Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
fun <S : Any, T : Any> register(key: Pair<KClass<S>, KClass<T>>, block: (S) -> T) {
converters = converters + (key as Pair<*, *> to block as (Any) -> Any)
}
/**
* Delete an existing mapping by its key.
*
* @param key Key of the mapping to be removed. No error is triggered if key doesn't exist.
*/
fun remove(key: Pair<KClass<*>, KClass<*>>) {
converters = converters - key
}
/**
* Convert one type to another using the registered mapper function among both types. On sources
* of the same type as [target], the source object is returned without conversion.
*
* Converter search *DOES NOT CONSIDER SOURCE'S INTERFACES OR PARENT CLASSES*. If no exact type
* is registered for the converter, it won't be found. There is an exception with maps: if no
* converter is found and source implements Map, `Map::class to KClass<Target>` will be
* searched in the converters' registry.
*
* If no mapper function is defined for the specified types, an exception is thrown.
*
* @param source Value to convert to another type.
* @param target Target type for the source instance.
* @return Source converted to the target type, or source itself if its type is the same as
* target.
*/
@Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
fun <S : Any, T : Any> convert(source: S, target: KClass<T>): T =
if (source::class == target)
source as T
else
searchConverter(source, target)
?.invoke(source)
?: error("No converter for ${source::class.simpleName} -> ${target.simpleName}")
/**
* Convert a group of instances of one type to another type using the registered mapper function
* among both types. If no mapper function is defined for the specified types, an exception is
* thrown.
*
* @param source Values to convert to another type.
* @param target Target type for the source instances in the group.
* @return List of converted instances.
*
* @see ConvertersManager.convert
*/
fun <S : Any, T : Any> convertObjects(source: Iterable<S>, target: KClass<T>): List<T> =
source.map { convert(it, target) }
@Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
private fun <S : Any, T : Any> searchConverter(source: S, target: KClass<T>): ((S) -> T)? {
val sourceType = source::class
val converter = converters[sourceType to target]
if (converter != null)
return converter as? (S) -> T
if (source is Map<*, *>) {
val superTypeConverter = converters[Map::class to target]
if (superTypeConverter != null)
return superTypeConverter as? (S) -> T
}
return null
}
}