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
- }
- }