tech.codingzen.kdi.data_structure.Kdi.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kdi Show documentation
Show all versions of kdi Show documentation
0 dependency kotlin dependency injection
package tech.codingzen.kdi.data_structure
import tech.codingzen.kdi.InternalKdi
import tech.codingzen.kdi.KdiException
import tech.codingzen.kdi.ScopeId
import tech.codingzen.kdi.Tag
import tech.codingzen.kdi.data_structure.ScopeExtenderType.POST
import tech.codingzen.kdi.data_structure.ScopeExtenderType.PRE
import tech.codingzen.kdi.dsl.ScopeDsl
import tech.codingzen.kdi.dsl.builder.kdi_app.KdiAppSpecBuilders
import tech.codingzen.kdi.dsl.builder.kdi_app.ScopeSpecBuilders
import tech.codingzen.kdi.dsl.module.ModuleDsl
import tech.codingzen.kdi.dsl.module.MultipleModulesDsl
import tech.codingzen.kdi.logging.Logger
import tech.codingzen.kdi.spec.*
import java.util.*
class Kdi(
private val extensions: KdiExtensions,
val logger: Logger,
private val scopes: ScopesList
) {
companion object {
@InternalKdi
val fqcn = Kdi::class.qualifiedName
/**
* default tag for components that do not have a specific tag
*/
val defaultTag: Tag = "___default-tag___"
fun appSpec(name: String = "app") = KdiAppSpecBuilders.LoggerStep(name)
fun scopeSpec(id: ScopeId) = ScopeSpecBuilders.BootstrapperStep(id)
fun moduleSpec(id: ScopeId, block: ModuleDsl.() -> Unit): BaseSpec =
scopeSpec(id)
.noBootstrapper()
.module(block)
.build()
fun modulesSpec(id: ScopeId, block: MultipleModulesDsl.() -> Unit): BaseSpec =
scopeSpec(id)
.noBootstrapper()
.modules(block)
.build()
/**
* Create a component descriptor
*
* @param T component type
* @param tag uniquely identifies components with the same fqcn. Optional parameter that defaults to [Kdi.defaultTag]
* @return descriptor for type [T]
*/
inline fun descriptor(tag: Tag = defaultTag): Descriptor {
val fqcn = T::class.qualifiedName
?: throw IllegalStateException("Cannot create a component without a qualified name")
return Descriptor(fqcn, tag)
}
}
/**
* Using the current 'scope' (held by an instance of Kdi) to create a child scope defined by [spec]. The created
* scope is then accessed via ScopeDsl and [executor].
*
* @param T result type
* @param spec scope specification to be created in the context of this Kdi instance
* @param executor uses the created scope to compute a result of type T
* @returns the result of applying [executor] to the built scope defined by [spec]
*/
suspend fun execute(spec: ScopeSpec, executor: ScopeExecutor): T {
val builtScopes = when (spec) {
is BaseSpec -> createScope(scopes, spec)
is MultipleSpec -> {
var acc: ScopesList = scopes
val stack = Stack().apply { spec.specs.asReversed().forEach { push(it) } }
while (stack.isNotEmpty()) {
when (val current = stack.pop()) {
is BaseSpec -> acc = createScope(acc, current)
is MultipleSpec -> current.specs.asReversed().forEach { stack.push(it) }
}
}
acc
}
}
return executor.execute(ScopeDsl(Kdi(extensions, logger, builtScopes), builtScopes))
}
private fun copy(scopes: ScopesList) = Kdi(extensions, logger, scopes)
private suspend fun createScope(parent: ScopesList, base: BaseSpec): ScopesList {
val preScope = parent + Scope("${base.id}-pre-scope", base.preModules)
val preExtenders = (extensions.scopeExtensions[base.id]?.get(PRE) ?: listOf())
val extendedPreScope =
if (preExtenders.isEmpty()) preScope
else run {
val scopeDsl = ScopeDsl(copy(preScope), preScope)
val modules = try {
preExtenders.flatMap { it(scopeDsl).create() }
} catch (exc: Exception) {
throw KdiException("pre ScopeExtender failed", exc)
}
preScope + Scope("${base.id}-pre-scope-extended", modules)
}
val runtimeModules = try {
base.bootstrapper(ScopeDsl(copy(extendedPreScope), extendedPreScope)).create()
} catch (exc: Exception) {
throw KdiException("bootstrapper for scope: ${base.id} failed", exc)
}
val bootstrappedScope = extendedPreScope + Scope("${base.id}-bootstrapped-scope", runtimeModules)
val postScope = bootstrappedScope + Scope("${base.id}-post-scope", base.postModules)
val postExtenders = (extensions.scopeExtensions[base.id]?.get(POST) ?: listOf())
val extendedPostScope =
if (postExtenders.isEmpty()) postScope
else run {
val scopeDsl = ScopeDsl(copy(postScope), postScope)
val modules = try {
postExtenders.flatMap { it(scopeDsl).create() }
} catch (exc: Exception) {
throw KdiException("pre ScopeExtender failed", exc)
}
postScope + Scope("${base.id}-post-scope-extended", modules)
}
return extendedPostScope
}
}