All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.kodein.di.bindings.scopes.kt Maven / Gradle / Ivy

There is a newer version: 7.22.0
Show newest version
package org.kodein.di.bindings

import org.kodein.di.DI
import org.kodein.di.DIContext
import org.kodein.di.DirectDI
import org.kodein.di.Volatile
import org.kodein.di.internal.maySynchronized
import org.kodein.di.internal.newConcurrentMap
import org.kodein.di.internal.synchronizedIfNotNull
import org.kodein.di.internal.synchronizedIfNull
import org.kodein.type.TypeToken

public interface ScopeCloseable {
    public fun close()
}

private typealias RegKey = Any

/**
 * A registry is responsible managing references inside a scope.
 */
public sealed class ScopeRegistry : ScopeCloseable {
    /**
     * Get or create a value that correspond for the given key.
     *
     * This function should operate as follow:
     *
     * - If there is a function associated to the [key], call it, and if the result is not empty, return it.
     * - If there is no function associated, or if the function returned null:
     *   * Call the creator function
     *   * Store the [Reference.next] function and associate it to the key for future requests.
     *   * Return the [Reference.current] value.
     *
     * @param key An object representing a "key" to associate.
     * @param creator A function that creates a reference that will be stored in the registry.
     * @return A value associated to the [key], whether created by [creator] or retrieved by [Reference.next].
     */
    public abstract fun getOrCreate(key: RegKey, sync: Boolean = true, creator: () -> Reference): Any

    public abstract fun getOrNull(key: RegKey): (() -> Any?)?

    public abstract fun values(): Iterable Any?>>

    public abstract fun remove(key: RegKey)

    public abstract fun clear()

    final override fun close(): Unit = clear()
}

/**
 * Standard [ScopeRegistry] implementation.
 */
public class StandardScopeRegistry : ScopeRegistry() {

    private val _cache = newConcurrentMap Any?>()

    private val _lock = Any()

    override fun getOrCreate(key: RegKey, sync: Boolean, creator: () -> Reference): Any {
        return synchronizedIfNull(
                lock = if (sync) _lock else null,
                predicate = { _cache[key]?.invoke() },
                ifNotNull = { it },
                ifNull = {
                    val (current, next) = creator()
                    _cache[key] = next
                    current
                }
        )
    }

    override fun getOrNull(key: RegKey): (() -> Any?)? = _cache[key]

    override fun values(): List Any?>> = _cache.map { it.toPair() }

    override fun remove(key: RegKey) {
        (_cache.remove(key)?.invoke() as? ScopeCloseable)?.close()
    }

    /**
     * Remove all objects from the scope.
     */
    override fun clear() {
        val refs = maySynchronized(_lock) {
            val refs = _cache.values.toList()
            _cache.clear()
            refs
        }
        refs.forEach {
            (it.invoke() as? ScopeCloseable)?.close()
        }
    }

    /**
     * The number of singleton objects currently created in this scope.
     */
    public val size: Int get() = _cache.size

    /**
     * @return Whether or not this scope is empty (contains no singleton objects).
     */
    public fun isEmpty(): Boolean = _cache.isEmpty()
}

/**
 * [ScopeRegistry] that is specialized to hold only one item.
 *
 * If the key changes, the held item will be replaced.
 */
public class SingleItemScopeRegistry : ScopeRegistry() {
    private val _lock = Any()
    @Volatile private var _pair: Pair Any?>? = null

    override fun getOrCreate(key: RegKey, sync: Boolean, creator: () -> Reference): Any {
        val (oldRef, value) = synchronizedIfNull(
                lock = if (sync) _lock else null,
                predicate = { _pair?.let { (pKey, pRef) -> if (key == pKey) pRef() else null } },
                ifNotNull = { null to it },
                ifNull = {
                    val oldRef = _pair?.second
                    val (value, ref) = creator()
                    _pair = key to ref
                    oldRef to value
                }
        )
        (oldRef?.invoke() as? ScopeCloseable)?.close()
        return value
    }

    override fun getOrNull(key: RegKey): (() -> Any?)? = _pair?.let { (pKey, pRef) -> if (key == pKey) pRef else null }

    /**
     * @return Whether or not this scope is empty (contains no item).
     */
    public fun isEmpty(): Boolean = _pair == null

    override fun values(): List Any?>> = _pair?.let { listOf(it) } ?: emptyList()

    override fun remove(key: RegKey) {
        val ref = synchronizedIfNotNull(
                lock = _lock,
                predicate = { _pair },
                ifNull = { null },
                ifNotNull = { (pKey, pRef) ->
                    if (pKey != key)
                        throw IllegalStateException("SingleItemScopeRegistry currently holds a different key\n$key != $pKey")
                    _pair = null
                    pRef
                }
        )

        (ref?.invoke() as? ScopeCloseable)?.close()
    }

    /**
     * Remove the item & reset the scope.
     */
    override fun clear() {
        val ref = synchronizedIfNotNull(
                lock = _lock,
                predicate = { _pair },
                ifNull = { null },
                ifNotNull = { (_, pRef) ->
                    _pair = null
                    pRef
                }
        )

        (ref?.invoke() as? ScopeCloseable)?.close()
    }
}

public interface ContextTranslator {
    public val contextType: TypeToken
    public val scopeType: TypeToken
    public fun translate(di: DirectDI, ctx: C): S?
}

public class SimpleContextTranslator(override val contextType: TypeToken, override val scopeType: TypeToken, private val t: DirectDI.(ctx: C) -> S?) : ContextTranslator {
    override fun translate(di: DirectDI, ctx: C): S? = di.t(ctx)
    override fun toString(): String = "()"
}

public class SimpleAutoContextTranslator(override val scopeType: TypeToken, private val t: DirectDI.() -> S) : ContextTranslator {
    override val contextType: TypeToken get() = TypeToken.Any
    override fun translate(di: DirectDI, ctx: Any): S = di.t()
    override fun toString(): String = "(${scopeType.simpleDispString()} -> ${contextType.simpleDispString()})"
}

public fun  ContextTranslator.toKContext(di: DirectDI, ctx: C): DIContext? = translate(di, ctx)?.let { DIContext(scopeType, it) }

internal class CompositeContextTranslator(val src: ContextTranslator, val dst: ContextTranslator) : ContextTranslator {
    override val contextType get() = src.contextType
    override val scopeType get() = dst.scopeType
    override fun translate(di: DirectDI, ctx: C): S? = src.translate(di, ctx)?.let { dst.translate(di, it) }
    override fun toString() = "($src -> $dst)"
}


/**
 * A scope is an object that can return (or create) a [ScopeRegistry] according to a context.
 *
 * @param C The Context.
 */
public interface Scope {

    /**
     * Get a registry for a given context.
     * Should always return the same registry for the same context.
     *
     * @param context The context associated with the returned registry.
     * @return The registry associated with the given context.
     */
    public fun getRegistry(context: C): ScopeRegistry
}

/**
 * [Scope] that is not bound to a context (always lives).
 *
 * This is kind of equivalent to having no scope at all, except that you can call [clear].
 */
public open class UnboundedScope(public val registry: ScopeRegistry = StandardScopeRegistry()) : Scope, ScopeCloseable {
    override fun getRegistry(context: Any?): ScopeRegistry = registry

    override fun close(): Unit = registry.clear()
}

public abstract class SubScope(private val parentScope: Scope) : Scope {

    private data class Key(val context: C)

    protected abstract fun getParentContext(context: C): PC

    override fun getRegistry(context: C): ScopeRegistry {
        val parentRegistry = parentScope.getRegistry(getParentContext(context))
        @Suppress("UNCHECKED_CAST")
        return parentRegistry.getOrCreate(Key(context), false) { SingletonReference.make { newRegistry() } } as ScopeRegistry
    }

    public open fun newRegistry(): ScopeRegistry = StandardScopeRegistry()
}

/**
 * Default [Scope]: will always return the same registry, no matter the context.
 */
public class NoScope: Scope {

    private val _registry = StandardScopeRegistry()

    override fun getRegistry(context: Any?): StandardScopeRegistry = _registry
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy