From f7507f384459b57460af899bf9ceae4f52f1ea21 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Fri, 6 Dec 2024 19:27:07 +0100 Subject: refactor: Add DI and packages --- src/main/kotlin/moe/nea/ledger/utils/DI.kt | 63 ++++++++++++++++++++++ src/main/kotlin/moe/nea/ledger/utils/DIProvider.kt | 53 ++++++++++++++++++ src/main/kotlin/moe/nea/ledger/utils/Inject.kt | 6 +++ 3 files changed, 122 insertions(+) create mode 100644 src/main/kotlin/moe/nea/ledger/utils/DI.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/DIProvider.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/Inject.kt (limited to 'src/main/kotlin/moe/nea/ledger/utils') diff --git a/src/main/kotlin/moe/nea/ledger/utils/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/DI.kt new file mode 100644 index 0000000..1114127 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/DI.kt @@ -0,0 +1,63 @@ +package moe.nea.ledger.utils + +import java.lang.reflect.AnnotatedElement +import java.util.Collections +import java.util.Stack + +@Suppress("UNCHECKED_CAST") +class DI { + 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: ${injectionStack.joinToString(" -> ")} -> $type") + } + injectionStack.push(type) + val value = try { + provider.provideWithContext(this, context) + } catch (ex: Exception) { + throw RuntimeException("Could not create instance for type $type", 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) + } + + 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<*, *>>() + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/DIProvider.kt b/src/main/kotlin/moe/nea/ledger/utils/DIProvider.kt new file mode 100644 index 0000000..3cedf40 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/DIProvider.kt @@ -0,0 +1,53 @@ +package moe.nea.ledger.utils + +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.Constructor + +fun interface DIProvider : BaseDIProvider { + override fun provideWithContext(di: DI, context: Unit): T { + return provide(di) + } + + override fun createContext(element: AnnotatedElement) { + } + + override fun createEmptyContext() { + } + + fun provide(di: DI): T + + companion object { + + fun fromInjectableClass(clazz: Class): DIProvider { + @Suppress("UNCHECKED_CAST") + val cons = (clazz.constructors.find { it.getAnnotation(Inject::class.java) != null } + ?: clazz.constructors.find { it.parameterCount == 0 } + ?: error("Could not find DI injection entrypoint for class $clazz")) + as Constructor + return DIProvider { di -> + val typArgs = cons.parameters.map { + di.provide(it.type, it) + }.toTypedArray() + val instance = cons.newInstance(*typArgs) + for (it in clazz.fields) { + if (it.getAnnotation(Inject::class.java) != null) { + continue + } + it.set(instance, di.provide(it.type, it)) + } + instance + } + } + + fun singeleton(value: T): DIProvider { + return DIProvider { _ -> value } + } + } + +} + +interface BaseDIProvider { + fun createContext(element: AnnotatedElement): C + fun provideWithContext(di: DI, context: C): T + fun createEmptyContext(): C +} diff --git a/src/main/kotlin/moe/nea/ledger/utils/Inject.kt b/src/main/kotlin/moe/nea/ledger/utils/Inject.kt new file mode 100644 index 0000000..654f77e --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/Inject.kt @@ -0,0 +1,6 @@ +package moe.nea.ledger.utils + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD) +annotation class Inject( +) -- cgit