class Injector {
// List of pre-constructed dependencies to inject
private val dependencies = IdentityHashMap<KClass<*>, Either<Any, MutableList<Any>>>()
private fun addDependency(type: KClass<*>, classOrInstance: Either<Any, MutableList<Any>>) {
if (dependencies.containsKey(type))
throw IllegalArgumentException("Dependency of class '${type.simpleName}' already exists")
dependencies[type] = classOrInstance
}
/**
* Add an instance as a dependency, this will not be injected into but it can be depended by others
*/
fun addInstance(instance: Any): Injector {
addDependency(instance::class, Either.Left(instance))
return this
}
fun add(type: KClass<*>, vararg arguments: Any): Injector {
addDependency(type, Either.Right(mutableListOf(*arguments)))
return this
}
inline fun <reified T> add(vararg arguments: Any): Injector {
add(T::class, *arguments)
return this
}
fun build() {
// Use a foreach instead of a filter because things will be moved
dependencies.forEach {
if (it.value.isRight())
construct(it.key)
}
}
private fun construct(type: KClass<*>): Any {
checkCyclic(type, type) // Check cyclic constructors recursively
val constructValues = mutableListOf<Any>()
type.primaryConstructor!!.parameters
.forEach { it ->
val paramType = it.type.classifier as KClass<*>
if (paramType.hasAnnotation<Dependency>()) {
dependencies[paramType]!!.fold(
{ constructValues.add(it) },
{ constructValues.add(construct(paramType)) } // Construct the argument
)
} else {
val passedValue = dependencies[paramType]!!.orNull()!!.remove(0)
if (passedValue::class != paramType)
throw IllegalArgumentException("Construct error on '${type::class.simpleName}', " +
"argument type '${paramType.simpleName}' did not match '${passedValue::class.simpleName}'")
}
}
val newValue = type.primaryConstructor!!.call(*constructValues.toTypedArray())
dependencies[type] = Either.Left(newValue)
return newValue
}
/**
* Check for cyclic dependency
*/
private fun checkCyclic(dontFind: KClass<*>, type: KClass<*>) {
type.primaryConstructor!!.parameters
.filter { it.hasAnnotation<Dependency>() }
.forEach {
val paramType = it.type.classifier as KClass<*>
if (paramType == dontFind)
throw IllegalStateException("Cyclic dependency detected between '${dontFind.simpleName}' and '${type.simpleName}'")
checkCyclic(dontFind, it.type.classifier as KClass<*>)
}
}
}