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 Show documentation
Show all versions of korinject 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