package klite import java.lang.System.Logger.Level.DEBUG import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter import kotlin.reflect.full.createInstance import kotlin.reflect.full.primaryConstructor import kotlin.reflect.jvm.jvmErasure interface Registry { fun require(type: KClass): T fun requireAll(type: KClass): List fun contains(type: KClass<*>): Boolean fun optional(type: KClass) = if (contains(type)) require(type) else null } interface MutableRegistry: Registry { fun register(type: KClass, implementation: KClass) fun register(type: KClass, instance: T) } inline fun Registry.require() = require(T::class) inline fun Registry.optional() = optional(T::class) inline fun Registry.requireAll() = requireAll(T::class) inline fun MutableRegistry.register(instance: T) = register(T::class, instance) inline fun MutableRegistry.register() = register(T::class, T::class) inline fun MutableRegistry.register(implementation: KClass) = register(T::class, implementation) class RegistryException(message: String, cause: Throwable? = null): Exception(message, cause) @Suppress("UNCHECKED_CAST") open class SimpleRegistry: MutableRegistry { private val instances = mutableMapOf, Any>(Registry::class to this) override fun register(type: KClass, implementation: KClass) = register(type, create(implementation)) override fun register(type: KClass, instance: T) { instances[type] = instance if (instance::class != type) instances[instance::class] = instance } override fun contains(type: KClass<*>) = instances.contains(type) override fun optional(type: KClass) = instances[type] as T? override fun require(type: KClass) = optional(type) ?: create(type).also { register(type, it) } override fun requireAll(type: KClass): List = instances.values.filter { type.java.isAssignableFrom(it.javaClass) }.distinct() as List open fun create(type: KClass): T = type.createInstance() } /** * Implements simple constructor injection that can easily replace your more complex dependency injection framework. * You may extend this class to override how exactly constructor parameters are created. * * Parameters with default values are populated from the registry falling back to the default value if not found. * Parameters without default values are required to be present in the registry. */ open class DependencyInjectingRegistry: SimpleRegistry() { private val log = logger() override fun create(type: KClass): T { val constructor = chooseConstructor(type) ?: throw RegistryException("$type has no usable constructor") try { val args = createArgs(constructor) return constructor.callBy(args).also { log.log(DEBUG) { "Auto-created ${type.simpleName}${args.values.map { it::class.simpleName }}" } } } catch (e: Throwable) { throw RegistryException("Failed to auto-create ${type.simpleName} with dependencies on ${constructor.parameters.map {it.type}}", e.cause ?: e) } } protected open fun chooseConstructor(type: KClass): KFunction? = type.primaryConstructor ?: type.constructors.minByOrNull { it.parameters.size } protected open fun createArgs(constructor: KFunction): Map = constructor.parameters.filter { !it.isOptional || contains(it.type.jvmErasure) }.associateWith { require(it.type.jvmErasure) } }