Injector.kt

package com.hexagontk.injection

import kotlin.reflect.KClass

/**
 * Inject type instances based on the suppliers defined in the [Module].
 */
class Injector(private val module: Module = Module()) {

    /**
     * Inject an instance of the type class and the supplied tag. If the injector's module doesn't
     * have a matching binding, then `null` is returned.
     *
     * @param T Generic type of the instance that will be created.
     * @param type Class for the instance to create (class of T).
     * @param tag Tag used to search the binding in the [Module].
     * @return An instance of T or `null` if no binding for that type with the passed tag is found.
     */
    @Suppress("UNCHECKED_CAST") // bind operation takes care of type matching
    fun <T : Any> injectOrNull(type: KClass<T>, tag: Any): T? =
        module.bindings[Target(type, tag)]?.provide() as? T

    @Suppress("UNCHECKED_CAST") // bind operation takes care of type matching
    fun <T : Any> injectList(type: KClass<T>): List<T> =
        module.bindings
            .filter { it.key.type == type }
            .map { it.value.provide() as T }

    @Suppress("UNCHECKED_CAST") // bind operation takes care of type matching
    fun <T : Any> injectMap(type: KClass<T>): Map<Any, T> =
        module.bindings
            .filter { it.key.type == type }
            .map { it.key.tag to it.value.provide() as T }
            .associate { it.first to it.second }

    fun <T : Any> inject(type: KClass<T>, tag: Any): T =
        injectOrNull(type, tag) ?: error("${type.java.name} generator missing")

    fun <T : Any> inject(type: KClass<T>): T =
        inject(type, Unit)

    fun <T : Any> injectOrNull(type: KClass<T>): T? =
        injectOrNull(type, Unit)

    inline fun <reified T : Any> inject(tag: Any): T =
        inject(T::class, tag)

    inline fun <reified T : Any> inject(): T =
        inject(T::class)

    inline fun <reified T : Any> injectOrNull(tag: Any): T? =
        injectOrNull(T::class, tag)

    inline fun <reified T : Any> injectOrNull(): T? =
        injectOrNull(T::class)

    inline fun <reified T : Any> injectList(): List<T> =
        injectList(T::class)

    inline fun <reified T : Any> injectMap(): Map<Any, T> =
        injectMap(T::class)
}