package moe.nea.ledger.utils.di import java.lang.reflect.AnnotatedElement import java.util.Collections import java.util.Stack @Suppress("UNCHECKED_CAST") class DI { private fun formatInjectionStack() = injectionStack.joinToString(" -> ") private fun internalProvide(type: Class, element: AnnotatedElement? = null): T { val provider = providers[type] as BaseDIProvider val context = if (element == null) provider.createEmptyContext() else provider.createContext(element) val key = Pair(type, context) val existingValue = values[key] if (existingValue != null) return existingValue as T if (type in injectionStack) { error("Found injection cycle: ${formatInjectionStack()} -> $type") } injectionStack.push(type) val value = try { provider.provideWithContext(this, context) } catch (ex: Exception) { throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex) } val cycleCheckCookie = injectionStack.pop() require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } values[key] = value return value } fun provide(type: Class, element: AnnotatedElement? = null): T { return internalProvide(type, element) } inline fun provide(): T = provide(T::class.java) fun register(type: Class, provider: BaseDIProvider) { providers[type] = provider } fun registerInjectableClasses(vararg type: Class<*>) { type.forEach { internalRegisterInjectableClass(it) } } private fun internalRegisterInjectableClass(type: Class) { register(type, DIProvider.fromInjectableClass(type)) } fun instantiateAll() { providers.keys.forEach { provide(it, null) } } fun getAllInstances(): Collection = Collections.unmodifiableCollection(values.values) fun registerSingleton(value: T) { register(value.javaClass, DIProvider.singeleton(value)) } private val injectionStack: Stack> = Stack() private val values = mutableMapOf, *>, Any>() private val providers = mutableMapOf, BaseDIProvider<*, *>>() init { registerSingleton(this) } }