commonMain.org.kodein.di.DI.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kodein-di-js Show documentation
Show all versions of kodein-di-js Show documentation
KODEIN Dependency Injection Core
package org.kodein.di
import org.kodein.di.bindings.*
import org.kodein.di.internal.DIImpl
import org.kodein.type.TypeToken
import kotlin.native.concurrent.ThreadLocal
/**
* KOtlin DEpendency INjection.
*
* To construct a DI instance, simply use [it's block constructor][DI.invoke] and define your bindings in it :
*
* ```kotlin
* val di = DI {
* bind() with factory { sides: Int -> RandomDice(sides) }
* bind() with singleton { SqliteDS.open("path/to/file") }
* bind() with provider { SecureRandom() }
* constant("answer") with "forty-two"
* }
* ```
*/
public interface DI : DIAware {
/**
* Exception thrown when there is a dependency loop.
*
* @param message The message of the exception.
*/
public class DependencyLoopException internal constructor(message: String) : RuntimeException(message)
/**
* Exception thrown when asked for a dependency that cannot be found.
*
* @property key The key that was not found.
* @param message The message of the exception.
*/
public class NotFoundException(public val key: Key<*, *, *>, message: String)
: RuntimeException(message)
/**
* Exception thrown when searching for bindings and none could be found.
*
* @property search The specs that lead to no result.
* @param message The message of the exception.
*/
public class NoResultException(public val search: SearchSpecs, message: String)
: RuntimeException(message)
/**
* Exception thrown when there is an overriding error.
*
* @param message The message of the exception.
*/
public class OverridingException(message: String) : RuntimeException(message)
override val di: DI get() = this
/**
* In DI, each [DIBinding] is bound to a Key. A Key holds all information necessary to retrieve a factory (and therefore an instance).
*
* A key contains many types & a tag.
* It defines the types of the values that will be passed to and retrieved from DI, not the values themselves.
*
* @param C The in context type.
* @param A The in argument type (`Unit` for a provider).
* @param T The out type.
* @property contextType The in context type.
* @property argType The in argument type (`Unit` for a provider).
* @property type The out type.
* @property tag The associated tag.
*/
@Suppress("EqualsOrHashCode")
public data class Key(
val contextType: TypeToken,
val argType: TypeToken,
val type: TypeToken,
val tag: Any?
) {
/**
* Because this type is immutable, we can cache its hash to make it faster inside a HashMap.
*/
private var _hashCode: Int = 0
/** @suppress */
override fun hashCode(): Int {
if (_hashCode == 0) {
_hashCode = contextType.hashCode()
_hashCode = 31 * _hashCode + argType.hashCode()
_hashCode = 29 * type.hashCode()
_hashCode = 23 * _hashCode + (tag?.hashCode() ?: 0)
}
return _hashCode
}
/**
* @return The [description]
*/
override fun toString(): String = description
/**
* Right part of the description string.
*
* @param displayString a function that gets the display string for a type.
*/
private fun StringBuilder.appendDescription(displayString: TypeToken<*>.() -> String) {
if (tag != null) {
append(""" tagged "$tag"""")
}
if (contextType != TypeToken.Any) {
append(" on context ${contextType.displayString()}")
}
if (argType != TypeToken.Unit) {
append(", with argument ${argType.displayString()}")
}
}
/**
* Description using simple type names. The description is as close as possible to the code used to create this bind.
*/
val bindDescription: String get() = buildString {
append("bind<${type.simpleDispString()}>")
if (tag != null) {
append("""(tag = "$tag")""")
}
}
/**
* Description using full type names. The description is as close as possible to the code used to create this bind.
*/
val bindFullDescription: String get() = buildString {
append("bind<${type.qualifiedDispString()}>")
if (tag != null) {
append("""(tag = "$tag")""")
}
}
/**
* Description using simple type names. The description is as close as possible to the code used to create this key.
*/
val description: String get() = buildString {
append(type.simpleDispString())
appendDescription(TypeToken<*>::simpleDispString)
}
val internalDescription: String get() = "(context: ${contextType.simpleDispString()}, arg: ${argType.simpleDispString()}, type: ${type.simpleDispString()}, tag: $tag)"
/**
* Description using full type names. The description is as close as possible to the code used to create this key.
*/
val fullDescription: String get() = buildString {
append(type.qualifiedDispString())
appendDescription(TypeToken<*>::qualifiedDispString)
}
}
/**
* Defines a di DSL function
*/
@DslMarker
public annotation class DIDsl
/**
* Base builder DSL interface that allows to define scoped and context bindings.
*
* @param C The context type.
*/
public interface BindBuilder {
/**
* The context type that will be used by all bindings that are defined in this DSL context.
*/
public val contextType: TypeToken
public val explicitContext: Boolean
/** @suppress */
public class ImplWithContext(override val contextType: TypeToken) : BindBuilder {
override val explicitContext: Boolean get() = true
}
/**
* Used to define bindings with a scope.
*
* @param C The scope's Context.
*/
public interface WithScope : BindBuilder {
/**
* The scope that will be used by all bindings that are defined in this DSL context.
*/
public val scope: Scope
}
/** @suppress */
public class ImplWithScope(override val contextType: TypeToken, override val scope: Scope) : WithScope {
override val explicitContext: Boolean get() = true
}
}
/**
* Allows for the DSL inside the block argument of the constructor of `DI` and `DI.Module`.
*
* Methods of this classes are really just proxies to the [DIContainer.Builder] methods.
*
* @property containerBuilder Every methods eventually ends up to a call to this builder.
*/
@DIDsl
public interface Builder : BindBuilder, BindBuilder.WithScope {
public val containerBuilder: DIContainer.Builder
/**
* Left part of the type-binding syntax (`bind(type, tag)`).
*
* @param T The type to bind.
*/
public interface TypeBinder {
/**
* Binds the previously given type and tag to the given binding.
*
* @param binding The binding to bind.
* @throws OverridingException If this bindings overrides an existing binding and is not allowed to.
*/
public infix fun with(binding: DIBinding)
}
/**
* Left part of the direct-binding syntax (`bind(tag)`).
*/
public interface DirectBinder {
/**
* Binds the previously given tag to the given binding.
*
* The bound type will be the [DIBinding.createdType].
*
* @param binding The binding to bind.
* @throws OverridingException If this bindings overrides an existing binding and is not allowed to.
*/
@Deprecated(
message = "'bind() fron [BINDING]' might be replace by 'bind { [BINDING] }' (This will be remove in Kodein-DI 8.0)",
replaceWith = ReplaceWith("bind { binding }"),
level = DeprecationLevel.ERROR
)
public infix fun from(binding: DIBinding)
}
/**
* Left part of the constant-binding syntax (`constant(tag)`).
*
*/
public interface ConstantBinder {
/**
* Binds the previously given tag to the given instance.
*
* @param T The type of value to bind.
* @param value The instance to bind.
* @param valueType The type to bind the instance to.
* @throws OverridingException If this bindings overrides an existing binding and is not allowed to.
*/
@Suppress("FunctionName")
public fun With(valueType: TypeToken, value: T)
}
/**
* Attaches the binding of a given type with a given tag.
*
* @param T The type of value to bind.
* @param tag The tag to bind.
* @param overrides Whether this bind **must** or **must not** override an existing binding.
*/
public fun Bind(tag: Any? = null, overrides: Boolean? = null, binding: DIBinding<*, *, T>)
/**
* Attaches the binding of a given type with a given tag.
*
* @param T The type of value to bind.
* @param tag The tag to bind.
* @param overrides Whether this bind **must** or **must not** override an existing binding.
*/
public fun BindSet(tag: Any? = null, overrides: Boolean? = null, binding: DIBinding<*, *, T>)
/**
* Starts the binding of a given type with a given tag.
*
* @param T The type of value to bind.
* @param type The type to bind.
* @param tag The tag to bind.
* @param overrides Whether this bind **must** or **must not** override an existing binding.
* @return The binder: call [TypeBinder.with]) on it to finish the binding syntax and register the binding.
*/
@Suppress("FunctionName")
public fun Bind(type: TypeToken, tag: Any? = null, overrides: Boolean? = null): TypeBinder
/**
* Starts a direct binding with a given tag. A direct bind does not define the type to be bound, the type will be defined according to the bound factory.
*
* @param tag The tag to bind.
* @param overrides Whether this bind **must** or **must not** override an existing binding.
* @return The binder: call [DirectBinder.from]) on it to finish the binding syntax and register the binding.
*/
@Suppress("FunctionName")
public fun Bind(tag: Any? = null, overrides: Boolean? = null): DirectBinder
/**
* Starts a constant binding.
*
* @param tag The tag to bind.
* @param overrides Whether this bind **must** or **must not** override an existing binding.
* @return The binder: call `with` on it to finish the binding syntax and register the binding.
*/
public fun constant(tag: Any, overrides: Boolean? = null): ConstantBinder
/**
* Imports all bindings defined in the given [DI.Module] into this builder's definition.
*
* Note that modules are *definitions*, they will re-declare their bindings in each di instance you use.
*
* @param module The module object to import.
* @param allowOverride Whether this module is allowed to override existing bindings.
* If it is not, overrides (even explicit) will throw an [OverridingException].
* @throws OverridingException If this module overrides an existing binding and is not allowed to
* OR [allowOverride] is true while YOU don't have the permission to override.
*/
public fun import(module: Module, allowOverride: Boolean = false)
/**
* Imports all bindings defined in the given [DI.Module]s into this builder's definition.
*
* Note that modules are *definitions*, they will re-declare their bindings in each di instance you use.
*
* @param modules The module objects to import.
* @param allowOverride Whether this module is allowed to override existing bindings.
* If it is not, overrides (even explicit) will throw an [OverridingException].
* @throws OverridingException If this module overrides an existing binding and is not allowed to
* OR [allowOverride] is true while YOU don't have the permission to override.
*/
public fun importAll(vararg modules: Module, allowOverride: Boolean = false)
/**
* Imports all bindings defined in the given [DI.Module]s into this builder's definition.
*
* Note that modules are *definitions*, they will re-declare their bindings in each di instance you use.
*
* @param modules The module objects to import.
* @param allowOverride Whether this module is allowed to override existing bindings.
* If it is not, overrides (even explicit) will throw an [OverridingException].
* @throws OverridingException If this module overrides an existing binding and is not allowed to
* OR [allowOverride] is true while YOU don't have the permission to override.
*/
public fun importAll(modules: Iterable, allowOverride: Boolean = false)
/**
* Like [import] but checks that will only import each module once.
*
* If the module has already been imported, nothing happens.
*
* Careful: this is checked by name. If two modules share the same name, only one will be imported!
*/
public fun importOnce(module: Module, allowOverride: Boolean = false)
/**
* Adds a callback that will be called once the DI object is configured and instantiated.
*
* @param cb The callback.
*/
public fun onReady(cb: DirectDI.() -> Unit)
public fun RegisterContextTranslator(translator: ContextTranslator<*, *>)
}
/**
* Builder to create a [DI] object.
*/
public interface MainBuilder : Builder {
/**
* If true, exceptions thrown will contain qualified names.
*/
public var fullDescriptionOnError: Boolean
/**
* If true, NotFoundException thrown will contain the list of all the bindings available.
*/
public var fullContainerTreeOnError: Boolean
/**
* The external source is repsonsible for fetching / creating a value when DI cannot find a matching binding.
*/
public val externalSources: MutableList
/**
* Imports all bindings defined in the given [DI] into this builder.
*
* Note that this preserves scopes, meaning that a singleton-bound in the di argument will continue to exist only once.
* Both di objects will share the same instance.
*
* Note that externalSource **will be overeridden** if defined in the extended DI.
*
* @param di The di object to import.
* @param allowOverride Whether this module is allowed to override existing bindings.
* If it is not, overrides (even explicit) will throw an [OverridingException].
* @param copy The copy specifications, that defines which bindings will be copied to the new container.
* All bindings from the extended container will be accessible in the new container, but only the copied bindings are able to access overridden bindings in this new container.
* By default, all bindings that do not hold references (e.g. not singleton or multiton) are copied.
* @throws OverridingException If this di overrides an existing binding and is not allowed to
* OR [allowOverride] is true while YOU don't have the permission to override.
*/
public fun extend(di: DI, allowOverride: Boolean = false, copy: Copy = Copy.NonCached)
/**
* Imports all bindings defined in the given [DI] into this builder.
*
* Note that this preserves scopes, meaning that a singleton-bound in the di argument will continue to exist only once.
* Both di objects will share the same instance.
*
* Note that externalSource **will be overeridden** if defined in the extended DI.
*
* @param directDI The direct di object to import.
* @param allowOverride Whether this module is allowed to override existing bindings.
* If it is not, overrides (even explicit) will throw an [OverridingException].
* @param copy The copy specifications, that defines which bindings will be copied to the new container.
* All bindings from the extended container will be accessible in the new container, but only the copied bindings are able to access overridden bindings in this new container.
* By default, all bindings that do not hold references (e.g. not singleton or multiton) are copied.
* @throws OverridingException If this di overrides an existing binding and is not allowed to
* OR [allowOverride] is true while YOU don't have the permission to override.
*/
public fun extend(directDI: DirectDI, allowOverride: Boolean = false, copy: Copy = Copy.NonCached)
}
/**
* A module is constructed the same way as in [DI] is:
*
* ```kotlinprivate
* val module = DI.Module {
* bind() with singleton { SqliteDS.open("path/to/file") }
* }
* ```
*
* @property name The name of this module (for debug).
* @property allowSilentOverride Whether this module is allowed to non-explicit overrides.
* @property init The block of configuration for this module.
*/
public data class Module(val name: String, val allowSilentOverride: Boolean = false, val prefix: String = "", val init: Builder.() -> Unit) {
@Deprecated(
message = "You should name your modules, for debug purposes.",
replaceWith = ReplaceWith("Module(\"module name\", allowSilentOverride, init)"),
level = DeprecationLevel.ERROR
)
public constructor(allowSilentOverride: Boolean = false, init: Builder.() -> Unit) : this("", allowSilentOverride, "", init)
}
/**
* Every methods eventually ends up to a call to this container.
*/
public val container: DIContainer
@ThreadLocal
public companion object {
/**
* Creates a [DI] instance.
*
* @param allowSilentOverride Whether the configuration block is allowed to non-explicit overrides.
* @param init The block of configuration.
* @return The new DI object, freshly created, and ready for hard work!
*/
public operator fun invoke(allowSilentOverride: Boolean = false, init: MainBuilder.() -> Unit): DI = DIImpl(allowSilentOverride, init)
/**
* Creates a [DI] instance that will be lazily created upon first access.
*
* @param allowSilentOverride Whether the configuration block is allowed to non-explicit overrides.
* @param init The block of configuration.
* @return A lazy property that will yield, when accessed, the new DI object, freshly created, and ready for hard work!
*/
public fun lazy(allowSilentOverride: Boolean = false, init: MainBuilder.() -> Unit): LazyDI = LazyDI { DIImpl(allowSilentOverride, init) }
/**
* Creates a direct [DirectDI] instance that will be lazily created upon first access.
*
* @param allowSilentOverride Whether the configuration block is allowed to non-explicit overrides.
* @param init The block of configuration.
* @return The new DirectDI object, freshly created, and ready for hard work!
*/
public fun direct(allowSilentOverride: Boolean = false, init: MainBuilder.() -> Unit): DirectDI = DIImpl(allowSilentOverride, init).direct
/**
* Creates a DI object but without directly calling onReady callbacks.
*
* Instead, returns both the di instance and the callbacks.
* Note that the returned di object should not be used before calling the callbacks.
*
* This is an **internal** function that exists primarily to prevent DI.global recursion.
*
* @param allowSilentOverride Whether the configuration block is allowed to non-explicit overrides.
* @param init The block of configuration.
* @return a Pair with the DI object, and the callbacks function to call.
* Note that you *should not* use the DI object before calling the callbacks function.
*/
public fun withDelayedCallbacks(allowSilentOverride: Boolean = false, init: MainBuilder.() -> Unit): Pair Unit> = DIImpl.withDelayedCallbacks(allowSilentOverride, init)
public fun from(modules: List): DI = DI {
modules.forEach { import(it) }
}
public var defaultFullDescriptionOnError: Boolean = false
public var defaultFullContainerTreeOnError: Boolean = false
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy