-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathRegistry.kt
74 lines (61 loc) · 3.41 KB
/
Registry.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package klite
import java.lang.System.Logger.Level.DEBUG
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure
interface Registry {
fun <T: Any> require(type: KClass<T>): T
fun <T: Any> requireAll(type: KClass<T>): List<T>
fun contains(type: KClass<*>): Boolean
fun <T: Any> optional(type: KClass<T>) = if (contains(type)) require(type) else null
}
interface MutableRegistry: Registry {
fun <T: Any, I: T> register(type: KClass<T>, implementation: KClass<I>)
fun <T: Any> register(type: KClass<T>, instance: T)
}
inline fun <reified T: Any> Registry.require() = require(T::class)
inline fun <reified T: Any> Registry.optional() = optional(T::class)
inline fun <reified T: Any> Registry.requireAll() = requireAll(T::class)
inline fun <reified T: Any> MutableRegistry.register(instance: T) = register(T::class, instance)
inline fun <reified T: Any> MutableRegistry.register() = register(T::class, T::class)
inline fun <reified T: Any> MutableRegistry.register(implementation: KClass<out T>) = register(T::class, implementation)
class RegistryException(message: String, cause: Throwable? = null): Exception(message, cause)
@Suppress("UNCHECKED_CAST")
open class SimpleRegistry: MutableRegistry {
private val instances = mutableMapOf<KClass<*>, Any>(Registry::class to this)
override fun <T: Any, I: T> register(type: KClass<T>, implementation: KClass<I>) = register(type, create(implementation))
override fun <T: Any> register(type: KClass<T>, instance: T) {
instances[type] = instance
if (instance::class != type) instances[instance::class] = instance
}
override fun contains(type: KClass<*>) = instances.contains(type)
override fun <T: Any> optional(type: KClass<T>) = instances[type] as T?
override fun <T: Any> require(type: KClass<T>) = optional(type) ?: create(type).also { register(type, it) }
override fun <T: Any> requireAll(type: KClass<T>): List<T> = instances.values.filter { type.java.isAssignableFrom(it.javaClass) }.distinct() as List<T>
open fun <T: Any> create(type: KClass<T>): T = type.createInstance()
}
/**
* Implements simple constructor injection that can easily replace your more complex dependency injection framework.
* You may extend this class to override how exactly constructor parameters are created.
*/
open class DependencyInjectingRegistry: SimpleRegistry() {
private val log = logger()
override fun <T: Any> create(type: KClass<T>): T {
val constructor = chooseConstructor(type) ?: throw RegistryException("$type has no usable constructor")
try {
val args = createArgs(constructor)
return constructor.callBy(args).also {
log.log(DEBUG) { "Auto-created ${type.simpleName}${args.values.map { it::class.simpleName }}" }
}
} catch (e: Throwable) {
throw RegistryException("Failed to auto-create ${type.simpleName} with dependencies on ${constructor.parameters.map {it.type}}", e.cause ?: e)
}
}
protected open fun <T : Any> chooseConstructor(type: KClass<T>): KFunction<T>? =
type.primaryConstructor ?: type.constructors.minByOrNull { it.parameters.size }
protected open fun <T : Any> createArgs(constructor: KFunction<T>): Map<KParameter, Any> =
constructor.parameters.filter { !it.isOptional || contains(it.type.jvmErasure) }.associateWith { require(it.type.jvmErasure) }
}