aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt
blob: 133637a906be65a1b99cb121574d567e08bf7ef5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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 <T : Any, C> internalProvide(type: Class<T>, element: AnnotatedElement? = null): T {
		val provider = providers[type] as BaseDIProvider<T, C>
		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 <T : Any> provide(type: Class<T>, element: AnnotatedElement? = null): T {
		return internalProvide<T, Any>(type, element)
	}

	inline fun <reified T : Any> provide(): T = provide(T::class.java)

	fun <T : Any> register(type: Class<T>, provider: BaseDIProvider<T, *>) {
		providers[type] = provider
	}

	fun registerInjectableClasses(vararg type: Class<*>) {
		type.forEach { internalRegisterInjectableClass(it) }
	}

	private fun <T : Any> internalRegisterInjectableClass(type: Class<T>) {
		register(type, DIProvider.fromInjectableClass(type))
	}

	fun instantiateAll() {
		providers.keys.forEach {
			provide(it, null)
		}
	}

	fun getAllInstances(): Collection<Any> =
		Collections.unmodifiableCollection(values.values)

	fun <T : Any> registerSingleton(value: T) {
		register(value.javaClass, DIProvider.singeleton(value))
	}

	private val injectionStack: Stack<Class<*>> = Stack()
	private val values = mutableMapOf<Pair<Class<*>, *>, Any>()
	private val providers = mutableMapOf<Class<*>, BaseDIProvider<*, *>>()

	init {
		registerSingleton(this)
	}
}