
commonMain.korlibs.inject.AsyncInjector.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of korinject-jvm Show documentation
Show all versions of korinject-jvm Show documentation
Asynchronous Injector for Kotlin
package korlibs.inject
import kotlinx.coroutines.*
import kotlin.coroutines.*
import kotlin.reflect.*
interface AsyncObjectProvider {
suspend fun get(injector: AsyncInjector): T
suspend fun deinit()
}
class PrototypeAsyncObjectProvider(val generator: suspend AsyncInjector.() -> T) : AsyncObjectProvider {
override suspend fun get(injector: AsyncInjector): T = injector.created(generator(injector))
override suspend fun deinit() = Unit
override fun toString(): String = "PrototypeAsyncObjectProvider()"
}
class FactoryAsyncObjectProvider(val generator: suspend AsyncInjector.() -> AsyncFactory) :
AsyncObjectProvider {
override suspend fun get(injector: AsyncInjector): T = injector.created(generator(injector).create())
override suspend fun deinit() = Unit
override fun toString(): String = "FactoryAsyncObjectProvider()"
}
class SingletonAsyncObjectProvider(val generator: suspend AsyncInjector.() -> T) : AsyncObjectProvider {
var value: T? = null
override suspend fun get(injector: AsyncInjector): T {
if (value == null) value = injector.created(generator(injector))
return value!!
}
override suspend fun deinit() {
(value as? AsyncDestructor?)?.deinit()
}
override fun toString(): String = "SingletonAsyncObjectProvider($value)"
}
class InstanceAsyncObjectProvider(val instance: T) : AsyncObjectProvider {
override suspend fun get(injector: AsyncInjector): T = instance
override suspend fun deinit() {
(instance as? AsyncDestructor?)?.deinit()
}
override fun toString(): String = "InstanceAsyncObjectProvider($instance)"
}
class AsyncInjector(val parent: AsyncInjector? = null, val level: Int = 0) {
companion object {}
suspend inline fun getWith(vararg instances: Any): T = getWith(T::class, *instances)
suspend inline fun get(): T = get(T::class)
suspend inline fun getOrNull(): T? = getOrNull(T::class)
inline fun mapInstance(instance: T): AsyncInjector = mapInstance(T::class, instance)
inline fun mapFactory(noinline gen: suspend AsyncInjector.() -> AsyncFactory) =
mapFactory(T::class, gen)
inline fun mapSingleton(noinline gen: suspend AsyncInjector.() -> T) = mapSingleton(T::class, gen)
inline fun mapPrototype(noinline gen: suspend AsyncInjector.() -> T) = mapPrototype(T::class, gen)
fun removeMapping(clazz: KClass<*>) {
providersByClass.remove(clazz)
parent?.removeMapping(clazz)
}
var fallbackProvider: (suspend (clazz: kotlin.reflect.KClass<*>, ctx: RequestContext) -> AsyncObjectProvider<*>)? = null
val providersByClass = LinkedHashMap, AsyncObjectProvider<*>>()
val root: AsyncInjector = parent?.root ?: this
val nearestFallbackProvider get() = fallbackProvider ?: parent?.fallbackProvider
fun child() = AsyncInjector(this, level + 1)
suspend fun getWith(clazz: KClass, vararg instances: Any): T {
val c = child()
for (i in instances) {
@Suppress("UNCHECKED_CAST")
c.mapInstance(i::class as KClass, i)
}
return c.get(clazz)
}
fun dump() {
println("$this")
for ((k, v) in providersByClass) {
println("- $k: $v")
}
parent?.dump()
}
fun mapInstance(clazz: KClass, instance: T): AsyncInjector = this.apply {
providersByClass[clazz] = InstanceAsyncObjectProvider(instance)
}
fun mapFactory(clazz: KClass, gen: suspend AsyncInjector.() -> AsyncFactory): AsyncInjector =
this.apply {
providersByClass[clazz] = FactoryAsyncObjectProvider(gen)
}
fun mapSingleton(clazz: KClass, gen: suspend AsyncInjector.() -> T): AsyncInjector = this.apply {
providersByClass[clazz] = SingletonAsyncObjectProvider(gen)
}
fun mapPrototype(clazz: KClass, gen: suspend AsyncInjector.() -> T): AsyncInjector = this.apply {
providersByClass[clazz] = PrototypeAsyncObjectProvider(gen)
}
init {
mapInstance(AsyncInjector::class, this)
}
data class RequestContext(val initialClazz: KClass<*>)
fun getClassDefiner(clazz: KClass<*>): AsyncInjector? {
if (clazz in providersByClass) return this
return parent?.getClassDefiner(clazz)
}
suspend fun getProviderOrNull(
clazz: KClass,
ctx: RequestContext = RequestContext(clazz)
): AsyncObjectProvider? {
return (providersByClass[clazz]
?: parent?.getProviderOrNull(clazz, ctx)
?: nearestFallbackProvider?.invoke(clazz, ctx)?.also { providersByClass[clazz] = it }
) as? AsyncObjectProvider?
}
suspend fun getProvider(
clazz: KClass,
ctx: RequestContext = RequestContext(clazz)
): AsyncObjectProvider =
getProviderOrNull(clazz, ctx) ?: throw AsyncInjector.NotMappedException(
clazz, ctx.initialClazz, ctx, "Class '$clazz' doesn't have constructors $ctx"
)
@Suppress("UNCHECKED_CAST")
suspend fun getOrNull(clazz: KClass, ctx: RequestContext = RequestContext(clazz)): T? {
return getProviderOrNull(clazz, ctx)?.get(this)
}
inline fun getSync(ctx: RequestContext = RequestContext(T::class)): T = getSync(T::class, ctx)
inline fun getSyncOrNull(ctx: RequestContext = RequestContext(T::class)): T? = getSyncOrNull(T::class, ctx)
fun getSync(clazz: KClass, ctx: RequestContext = RequestContext(clazz)): T {
return getSyncOrNull(clazz, ctx) ?: throw RuntimeException("Couldn't get instance of type $clazz synchronously")
}
fun getSyncOrNull(clazz: KClass, ctx: RequestContext = RequestContext(clazz)): T? {
var rresult: T? = null
var rexception: Throwable? = null
suspend {
getOrNull(clazz, ctx)
}.startCoroutine(object : Continuation {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result) {
val exception = result.exceptionOrNull()
if (exception != null) {
rexception = exception
} else {
rresult = result.getOrNull()
}
}
})
if (rexception != null) throw rexception!!
try {
return rresult
} catch (e: Throwable) {
if (e is CancellationException) throw e
throw RuntimeException("Couldn't get instance of type $clazz synchronously", e)
}
}
@Suppress("UNCHECKED_CAST")
suspend fun get(clazz: KClass, ctx: RequestContext = RequestContext(clazz)): T {
return getProvider(clazz, ctx).get(this)
}
suspend fun has(clazz: KClass): Boolean = getProviderOrNull(clazz) != null
class NotMappedException(
val clazz: KClass<*>,
val requestedByClass: KClass<*>,
val ctx: RequestContext,
val msg: String = "Not mapped $clazz requested by $requestedByClass in $ctx"
) : RuntimeException(msg)
override fun toString(): String = "AsyncInjector(level=$level)"
suspend internal fun created(instance: T): T {
if (instance is AsyncDependency) instance.init()
if (instance is InjectorAsyncDependency) instance.init(this)
if (instance is AsyncDestructor) deinitList.add(instance)
return instance
}
private val deinitList = arrayListOf()
fun addDeinit(value: AsyncDestructor) {
deinitList.add(value)
}
suspend fun deinit() {
for (pair in providersByClass) pair.value.deinit()
for (deinit in deinitList) deinit.deinit()
deinitList.clear()
}
}
interface AsyncFactory {
suspend fun create(): T
}
interface InjectedHandler {
suspend fun injectedInto(instance: Any): Unit
}
annotation class AsyncFactoryClass(val clazz: KClass>)
//annotation class AsyncFactoryClass(val clazz: KClass>)
//annotation class AsyncFactoryClass(val clazz: KClass>)
//annotation class AsyncFactoryClass(val clazz: kotlin.reflect.KClass>)
interface AsyncDependency {
suspend fun init(): Unit
}
interface AsyncDestructor {
suspend fun deinit(): Unit
}
interface InjectorAsyncDependency {
suspend fun init(injector: AsyncInjector): Unit
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy