diff options
author | Linnea Gräf <nea@nea.moe> | 2025-01-07 14:07:14 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2025-01-07 14:07:14 +0100 |
commit | 34334396178eec3f72f1228b400cb9ec81c4ce4c (patch) | |
tree | 416d05a1193aa787c3fcbe44431a87d9ec555f3b /dependency-injection | |
parent | f2783ceea23147d2702c47c9b47c06ad7fc707a8 (diff) | |
download | LocalTransactionLedger-34334396178eec3f72f1228b400cb9ec81c4ce4c.tar.gz LocalTransactionLedger-34334396178eec3f72f1228b400cb9ec81c4ce4c.tar.bz2 LocalTransactionLedger-34334396178eec3f72f1228b400cb9ec81c4ce4c.zip |
build: Split up dependency injection into its own package
Diffstat (limited to 'dependency-injection')
4 files changed, 153 insertions, 0 deletions
diff --git a/dependency-injection/build.gradle.kts b/dependency-injection/build.gradle.kts new file mode 100644 index 0000000..5a51941 --- /dev/null +++ b/dependency-injection/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `java-library` + kotlin("jvm") +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(8)) +} diff --git a/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt new file mode 100644 index 0000000..0683063 --- /dev/null +++ b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt @@ -0,0 +1,83 @@ +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(" -> ") + + fun <T : Any> getProvider(type: Class<T>): BaseDIProvider<T, *> { + val provider = providers[type] as BaseDIProvider<T, *>? + ?: error("Could not find provider for type $type") + return provider + } + + private fun <T : Any, C> internalProvide(type: Class<T>, element: AnnotatedElement? = null): T { + try { + val provider = getProvider(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 = + provider.provideWithContext(this, context) + val cycleCheckCookie = injectionStack.pop() + require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } + values[key] = value + return value + } catch (ex: Exception) { + throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex) + } + } + + 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 <I : Any, T : I> registerInjectableInterface(parent: Class<I>, type: Class<T>) { + internalRegisterInjectableClass(type) + register(parent, DIProvider.fromInheritance(type)) + } + + 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) + } +}
\ No newline at end of file diff --git a/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt new file mode 100644 index 0000000..8a54d5f --- /dev/null +++ b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt @@ -0,0 +1,56 @@ +package moe.nea.ledger.utils.di + +import java.lang.reflect.AnnotatedElement +import java.lang.reflect.Constructor + +fun interface DIProvider<T : Any> : BaseDIProvider<T, Unit> { + 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 <T : Any> fromInjectableClass(clazz: Class<out T>): DIProvider<T> { + @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<out T> + // TODO: consider using unsafe init to inject the parameters *before* calling the 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 <T : Any> singeleton(value: T): DIProvider<T> { + return DIProvider { _ -> value } + } + + fun <I : Any> fromInheritance(type: Class<out I>): DIProvider<I> { + return DIProvider { it.provide(type) } + } + } + +} + +interface BaseDIProvider<T : Any, C> { + fun createContext(element: AnnotatedElement): C + fun provideWithContext(di: DI, context: C): T + fun createEmptyContext(): C +} diff --git a/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/Inject.kt b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/Inject.kt new file mode 100644 index 0000000..a8fdd87 --- /dev/null +++ b/dependency-injection/src/main/kotlin/moe/nea/ledger/utils/di/Inject.kt @@ -0,0 +1,6 @@ +package moe.nea.ledger.utils.di + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD) +annotation class Inject( +) |