-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Tried in Worklog. Cannot set container as private, cannot properly handle subtypes, etc.
Snippet:
package hu.tothlp.worklog.core.di
import com.github.ajalt.mordant.terminal.Terminal
import kotlin.reflect.KClass
//TODO: Migrate to Beanject "lib"
/**
* Simple DI container. It handles registering and retrieving beans, with a simple DSL.
*/
object Beanject {
data class Bean(
val implementation: Any,
var name: String? = null,
var primary: Boolean = false
)
val t = Terminal()
// Internal container for beans. Two variables are needed, because the container is mutable,
// but the external view should be immutable. We also need a publicly accessible view of the container, so the reified functions can access it.
internal val internalBeanContainer = mutableMapOf<KClass<*>, MutableList<Bean>>()
val beanContainer: Map<KClass<*>, List<Bean>> get() = internalBeanContainer
fun getBeanByName(name: String): Any {
return beanContainer.values.flatten().firstOrNull { it.name == name } ?: throw IllegalArgumentException("No bean found with name $name")
}
inline fun <reified T> getBean(name: String? = null): T {
t.println(beanContainer)
if (beanContainer.values.flatten().none { (it.implementation as? T) != null }) throw IllegalArgumentException("No bean found for type ${T::class.simpleName}")
return if (!name.isNullOrBlank()) getBeanByName(name) as T
else beanContainer.values.first { it is T } as T
}
internal inline fun <reified T> registerBean(name: String, implementation: Any, primary: Boolean? = false) {
validateBean<T>(name, primary ?: false)
val bean = Bean(implementation, name, primary ?: false)
internalBeanContainer.getOrPut(T::class) { mutableListOf() }.add(bean)
}
/**
* Validates the bean registration. It should meet the following criteria:
*
* - Name and primary flag are needed only if more than one bean exist for a type.
* - Only one bean can be registered with the same name, no matter the type.
* - Only one primary bean can be registered for a type.
* - If more than one bean exist for a type, one of them should be marked as primary.
* - If more than one bean exist for a type, non-primary ones should have a name.
*
*/
internal inline fun <reified T> validateBean(name: String?, primary: Boolean) {
val nonPrimaryNameError = "If more than one bean exist for a type, non-primary ones should have a name."
val beansForClass = internalBeanContainer[T::class]
// Check if a primary bean already exists for the given type
val containsPrimary = beansForClass?.any { it.primary } ?: false
// Check if a non-primary bean already exists for the given type
val containsNonPrimary = beansForClass?.any { !it.primary } ?: false
// Check if a non-primary bean already exists for the given type without a name
val containsNonPrimaryWithoutName = containsNonPrimary && (beansForClass?.any { it.name.isNullOrBlank() } ?: false)
val validationError: String? = when {
!name.isNullOrEmpty() && internalBeanContainer.entries.any { it.value.any { it.name == name } } -> "Bean with name $name already exists"
// We want to register a primary bean, but a primary bean already exists for the given type
primary && containsPrimary -> "Primary bean already exists for type ${T::class.simpleName}"
// We want to register a primary bean, but a non-primary bean already exists for the given type without a name
primary && containsNonPrimaryWithoutName -> "A non-primary bean already exists for type ${T::class.simpleName}. $nonPrimaryNameError"
// We want to register a non-primary bean, and at least one non-primary bean exists, but no primary bean. We should also check the new beans and the existing ones for names.
!primary && containsNonPrimary && !containsPrimary ->
"A bean already exists for type ${T::class.simpleName}, one of them should be marked as primary.".appendIf(name.isNullOrEmpty() || containsNonPrimaryWithoutName, nonPrimaryNameError)
// We want to register a non-primary bean, and a primary bean exists, but either the new bean or one of the existing ones don't have a name.
!primary && containsPrimary && (name.isNullOrEmpty() || containsNonPrimaryWithoutName) -> nonPrimaryNameError
else -> null
}
validationError?.let { throw IllegalArgumentException(it) }
}
fun String.appendIf(condition: Boolean, appendix: String) = if (condition) this + appendix else this
/**
* DSL for defining beans.
*/
class BeanDefinitionDsl
/**
* Creates the bean registration environment by creating a [BeanDefinitionDsl] and calling the [init] function on it.
*/
fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl = BeanDefinitionDsl().apply(init)
/**
* Registers a bean with the given name and creator function.
*/
internal inline fun <reified T : Any> BeanDefinitionDsl.bean(name: String, primary: Boolean? = false, beanCreator: BeanDefinitionDsl.() -> T): T =
beanCreator().also { registerBean<T>(name, it, primary) }
}Metadata
Metadata
Assignees
Labels
No labels