ConvertersManager.kt

  1. package com.hexagontk.converters

  2. import kotlin.reflect.KClass

  3. /**
  4.  * Registry that holds functions to convert from one type to another.
  5.  *
  6.  * @sample com.hexagontk.converters.ConvertersManagerTest.usageExample
  7.  */
  8. object ConvertersManager {

  9.     private var converters: Map<Pair<*, *>, (Any) -> Any> = emptyMap()

  10.     /**
  11.      * Register a mapping function from one type to another.
  12.      *
  13.      * @param key Pair which key is the source type and the value is the target type.
  14.      * @param block Block that converts an instance of the source type to the target one.
  15.      */
  16.     @Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
  17.     fun <S : Any, T : Any> register(key: Pair<KClass<S>, KClass<T>>, block: (S) -> T) {
  18.         converters = converters + (key as Pair<*, *> to block as (Any) -> Any)
  19.     }

  20.     /**
  21.      * Delete an existing mapping by its key.
  22.      *
  23.      * @param key Key of the mapping to be removed. No error is triggered if key doesn't exist.
  24.      */
  25.     fun remove(key: Pair<KClass<*>, KClass<*>>) {
  26.         converters = converters - key
  27.     }

  28.     /**
  29.      * Convert one type to another using the registered mapper function among both types. On sources
  30.      * of the same type as [target], the source object is returned without conversion.
  31.      *
  32.      * Converter search *DOES NOT CONSIDER SOURCE'S INTERFACES OR PARENT CLASSES*. If no exact type
  33.      * is registered for the converter, it won't be found. There is an exception with maps: if no
  34.      * converter is found and source implements Map, `Map::class to KClass<Target>` will be
  35.      * searched in the converters' registry.
  36.      *
  37.      * If no mapper function is defined for the specified types, an exception is thrown.
  38.      *
  39.      * @param source Value to convert to another type.
  40.      * @param target Target type for the source instance.
  41.      * @return Source converted to the target type, or source itself if its type is the same as
  42.      *   target.
  43.      */
  44.     @Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
  45.     fun <S : Any, T : Any> convert(source: S, target: KClass<T>): T =
  46.         if (source::class == target)
  47.             source as T
  48.         else
  49.             searchConverter(source, target)
  50.                 ?.invoke(source)
  51.                 ?: error("No converter for ${source::class.simpleName} -> ${target.simpleName}")

  52.     /**
  53.      * Convert a group of instances of one type to another type using the registered mapper function
  54.      * among both types. If no mapper function is defined for the specified types, an exception is
  55.      * thrown.
  56.      *
  57.      * @param source Values to convert to another type.
  58.      * @param target Target type for the source instances in the group.
  59.      * @return List of converted instances.
  60.      *
  61.      * @see ConvertersManager.convert
  62.      */
  63.     fun <S : Any, T : Any> convertObjects(source: Iterable<S>, target: KClass<T>): List<T> =
  64.         source.map { convert(it, target) }

  65.     @Suppress("UNCHECKED_CAST") // Type consistency is checked at runtime
  66.     private fun <S : Any, T : Any> searchConverter(source: S, target: KClass<T>): ((S) -> T)? {
  67.         val sourceType = source::class

  68.         val converter = converters[sourceType to target]
  69.         if (converter != null)
  70.             return converter as? (S) -> T

  71.         if (source is Map<*, *>) {
  72.             val superTypeConverter = converters[Map::class to target]
  73.             if (superTypeConverter != null)
  74.                 return superTypeConverter as? (S) -> T
  75.         }

  76.         return null
  77.     }
  78. }