commonMain.org.kodein.di.internal.DIContainerImpl.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.internal
import org.kodein.di.*
import org.kodein.di.bindings.*
internal class DIContainerImpl private constructor(
override val tree: DITree,
private val node: Node?,
private val fullDescriptionOnError: Boolean,
private val fullContainerTreeOnError: Boolean,
) : DIContainer {
@Volatile var initCallbacks: (() -> Unit)? = null
private set
/**
* "Main" constructor that uses the bindings map configured by a [DIContainer.Builder].
*/
internal constructor(
builder: DIContainerBuilderImpl,
externalSources: List,
fullDescriptionOnError: Boolean,
fullContainerTreeOnError: Boolean,
runCallbacks: Boolean
) : this(DITreeImpl(builder.bindingsMap, externalSources, builder.translators), null, fullDescriptionOnError, fullContainerTreeOnError) {
val init: () -> Unit = {
val direct = DirectDIImpl(this, AnyDIContext)
builder.callbacks.forEach { @Suppress("UNUSED_EXPRESSION") it(direct) }
}
if (runCallbacks)
init()
else {
val lock = Any()
initCallbacks = {
synchronizedIfNotNull(
lock = lock,
predicate = this::initCallbacks,
ifNull = {},
ifNotNull = {
initCallbacks = null
init()
}
)
}
}
}
/**
* Class used to check for recursive dependencies, represents a node in the dependency tree.
*
* Each factory, in their Binding@getInstance methods receives a DI instance to enable transient dependency.
* However, it is not the same kodein instance as the one used to get the main dependency.
* Each time a transient dependency is needed, a new DI instance is constructed, with a new Node that has
* the current Node as it's parent.
* This allows, at each step, to walk up the node tree and check if the requested key has not yet been requested.
* If the same key exists twice in the tree, it means that it has, and that there's a dependency recursion.
*
* @property key The key of this node, meaning that this key has been looked for once.
* @property parent The parent node, meaning the parent lookup that needed this key.
*/
private class Node(private val key: DI.Key<*, *, *>, private val overrideLevel: Int, private val parent: Node?, private val fullDescriptionOnError: Boolean) {
/**
* Check that given key does **not** exist in the node tree or throws an exception if it does.
*
* @throws DI.DependencyLoopException if the key exists in the dependency tree.
*/
internal fun check(searchedKey: DI.Key<*, *, *>, searchedOverrideLevel: Int) {
if (!recursiveCheck(this, searchedKey, searchedOverrideLevel)) {
val list = recursiveLoop(this, searchedKey, searchedOverrideLevel, emptyList()) + displayString(searchedKey, overrideLevel)
val sb = StringBuilder()
list.forEachIndexed { index, string ->
sb.append(" ")
when (index) {
0 -> sb.append(" ")
1 -> sb.append(" ╔╩>")
else -> {
sb.append(" ║")
sb.append(" ".repeat(index - 1))
sb.append("╚>")
}
}
sb.append(string)
sb.append("\n") // appendln does not exist in JS
}
sb.append(" ╚")
sb.append("══".repeat(list.size - 1))
sb.append("╝")
throw DI.DependencyLoopException("Dependency recursion:\n$sb")
}
}
private fun displayString(key: DI.Key<*, *, *>, overrideLevel: Int): String {
val descProp = if (fullDescriptionOnError) key::bindFullDescription else key::bindDescription
return if (overrideLevel != 0) "overridden ${descProp.get()}" else descProp.get()
}
/**
* @return The current transitive dependency tree as a list of string.
*/
private tailrec fun recursiveLoop(node: Node, firstKey: DI.Key<*, *, *>, firstOverrideLevel: Int, tail: List): List {
return if (node.parent == null || (firstKey == node.key && firstOverrideLevel == node.overrideLevel))
listOf(displayString(node.key, node.overrideLevel)) + tail
else
return recursiveLoop(node.parent, firstKey, firstOverrideLevel, listOf(displayString(node.key, node.overrideLevel)) + tail)
}
/**
* Recursive function that walks up the node tree to check if a specific key can be found.
*
* @return whether the given key exists in the tree.
*/
private tailrec fun recursiveCheck(node: Node, searchedKey: DI.Key<*, *, *>, searchedOverrideLevel: Int): Boolean {
return if (node.key == searchedKey && node.overrideLevel == searchedOverrideLevel)
false
else if (node.parent == null)
true
else
recursiveCheck(node.parent, searchedKey, searchedOverrideLevel)
}
}
private fun bindingDI(key: DI.Key, context: DIContext, tree: DITree, overrideLevel: Int) : BindingDI {
val container = DIContainerImpl(tree, Node(key, overrideLevel, node, fullDescriptionOnError), fullDescriptionOnError, fullContainerTreeOnError)
return BindingDIImpl(DirectDIImpl(container, context), key, overrideLevel)
}
@Suppress("UNCHECKED_CAST")
override fun factoryOrNull(key: DI.Key, context: C, overrideLevel: Int): ((A) -> T)? {
tree.find(key, 0).let {
if (it.size == 1) {
val (_, definition, translator) = it[0]
node?.check(key, 0)
val originalContext = DIContext(key.contextType, context) as DIContext
val kContext = translator?.toKContext(DirectDIImpl(this, originalContext), context) ?: originalContext
key as DI.Key
val bindingDI = bindingDI(key, kContext, definition.tree, overrideLevel)
return definition.binding.getFactory(key, bindingDI)
}
}
val bindingDI = bindingDI(key, DIContext(key.contextType, context), tree, overrideLevel)
tree.externalSources.forEach { source ->
source.getFactory(bindingDI, key)?.let {
node?.check(key, 0)
@Suppress("UNCHECKED_CAST")
return it as (A) -> T
}
}
return null
}
@Suppress("UNCHECKED_CAST")
override fun factory(key: DI.Key, context: C, overrideLevel: Int): (A) -> T {
val result = tree.find(key, overrideLevel)
if (result.size == 1) {
val (_, definition, translator) = result[0]
node?.check(key, overrideLevel)
val originalContext = DIContext(key.contextType, context) as DIContext
val kContext = translator?.toKContext(DirectDIImpl(this, originalContext), context) ?: originalContext
key as DI.Key
val bindingDI = bindingDI(key, kContext, definition.tree, overrideLevel)
return definition.binding.getFactory(key, bindingDI)
}
val bindingDI = bindingDI(key, DIContext(key.contextType, context), tree, overrideLevel)
tree.externalSources.forEach { source ->
source.getFactory(bindingDI, key)?.let {
node?.check(key, overrideLevel)
@Suppress("UNCHECKED_CAST")
return it as (A) -> T
}
}
val withOverrides = overrideLevel != 0
val descProp = if (fullDescriptionOnError) key::fullDescription else key::description
val descFun: BindingsMap.(Boolean) -> String = if (fullDescriptionOnError) ({ fullDescription(it) }) else ({ description(it) })
if (result.isEmpty()) {
val description = buildString {
append("No binding found for ${descProp.get()}")
if (fullContainerTreeOnError) {
appendLine()
val forType = tree.find(SearchSpecs(type = key.type))
if (forType.isNotEmpty()) {
append("Available bindings for this type:\n${forType.associate { it.first to it.second }.descFun(withOverrides)}")
}
append("Registered in this DI container:\n${tree.bindings.descFun(withOverrides)}")
}
}
throw DI.NotFoundException(key, description)
}
val potentials: BindingsMap = result.associate {
it.first to tree[it.first]!!.second
}
val others: BindingsMap = tree.bindings.filter { (key, _) -> key !in potentials.keys } // Map.minus does not yet exist in Konan
throw DI.NotFoundException(key, "${potentials.size} bindings found that match $key:\n${potentials.descFun(withOverrides)}Other bindings registered in DI:\n${others.descFun(withOverrides)}")
}
@Suppress("UNCHECKED_CAST")
override fun allFactories(key: DI.Key, context: C, overrideLevel: Int): List<(A) -> T> {
val result = tree.find(key, overrideLevel, all = true)
return result.map { (_, definition, translator) ->
node?.check(key, overrideLevel)
val originalContext = DIContext(key.contextType, context) as DIContext
val kContext = translator?.toKContext(DirectDIImpl(this, originalContext), context) ?: originalContext
key as DI.Key
val bindingDI = bindingDI(key, kContext, definition.tree, overrideLevel)
definition.binding.getFactory(key, bindingDI)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy