All Downloads are FREE. Search and download functionalities are using the official Maven repository.

main.KontainerBlueprint.kt Maven / Gradle / Ivy

There is a newer version: 0.77.0
Show newest version
package de.peekandpoke.ultra.kontainer

import kotlin.reflect.KClass

/**
 * The Kontainer blueprint is built by the [KontainerBuilder]
 */
class KontainerBlueprint internal constructor(
    val config: Config,
    internal val configValues: Map,
    internal val definitions: Map, ServiceDefinition>,
) {
    data class Config(
        val trackKontainers: Boolean = false
    ) {
        companion object {
            val default = Config()
        }
    }

    val tracker = when (config.trackKontainers) {
        true -> KontainerTracker.live()
        else -> KontainerTracker.dummy()
    }

    /**
     * Counts how often times the blueprint was used
     */
    private var usages = 0

    /**
     * Collect dynamic service definitions
     */
    private val dynamicDefinitions: Map, ServiceDefinition> = definitions
        .filterValues { it.injectionType == InjectionType.Dynamic }

    /**
     * Collect Prototype service definitions
     */
    private val prototypeDefinitions: Map, ServiceDefinition> = definitions
        .filterValues { it.injectionType == InjectionType.Prototype }

    /**
     * A set of all dynamic service classes
     */
    private val dynamicsClasses = dynamicDefinitions.keys

    /**
     * A lookup for finding the base types of dynamic services from given super types
     */
    internal val dynamicsBaseTypeLookUp = TypeLookup.ForBaseTypes(dynamicsClasses)

    /**
     * Used to check whether unexpected instances are passed to [create]
     */
    private val dynamicsChecker = DynamicsChecker(dynamicsClasses)

    /**
     * Base type lookup for finding all candidate services by a given super type
     */
    @PublishedApi
    internal val superTypeLookup = TypeLookup.ForSuperTypes(definitions.keys)

    /**
     * Dependency lookup for figuring out which service depends on which other services
     */
    private val dependencyLookUp = DependencyLookup(superTypeLookup, definitions)

    /**
     * Semi dynamic services
     *
     * These are services that where initially defined as singletons, but which inject dynamic services.
     * Or which inject services that themselves inject dynamic services etc...
     */
    private val semiDynamicDefinitions: Map, ServiceDefinition> =
        // prototype cannot be "downgraded" to be semi dynamic singletons
        dependencyLookUp.getAllDependents(dynamicsClasses)
            // prototype cannot be "downgraded" to be semi dynamic singletons
            .filter { !prototypeDefinitions.contains(it) }
            .associateWith { definitions.getValue(it) }

    /**
     * Collect Singletons services/
     *
     * These are the services that
     * - have no transitive dependency to any of the dynamic services
     * - are no prototype services
     */
    private val singletonDefinitions: Map, ServiceDefinition> = definitions
        .filterKeys { !dynamicDefinitions.contains(it) }
        .filterKeys { !semiDynamicDefinitions.contains(it) }
        .filterKeys { !prototypeDefinitions.contains(it) }

    /**
     * All precomputed [ServiceProvider.Provider]s
     */
    private val serviceProviderProviders = mapOf, ServiceProvider.Provider>()
        .plus(
            singletonDefinitions.mapValues { (_, v) ->
                ServiceProvider.Provider.forGlobalSingleton(ServiceProvider.Type.Singleton, v)
            }
        ).plus(
            semiDynamicDefinitions.mapValues { (_, v) ->
                ServiceProvider.Provider.forDynamicSingleton(ServiceProvider.Type.SemiDynamic, v)
            }
        ).plus(
            dynamicDefinitions.mapValues { (_, v) ->
                ServiceProvider.Provider.forDynamicSingleton(ServiceProvider.Type.Dynamic, v)
            }
        ).plus(
            prototypeDefinitions.mapValues { (_, v) ->
                ServiceProvider.ForPrototype.Provider(v)
            }
        )

    /**
     * Extends the current blueprint with everything in [builder] and returns a new [KontainerBlueprint]
     */
    fun extend(builder: KontainerBuilder.() -> Unit): KontainerBlueprint {

        val result = KontainerBuilder(builder).build()

        return KontainerBlueprint(
            config = config,
            configValues = configValues.plus(result.configValues),
            definitions = definitions.plus(result.definitions),
        )
    }

    /**
     * Creates a kontainer instance and overrides the given dynamic services.
     *
     * The parameter [dynamics] can set dynamic overrides
     *
     * @throws KontainerInconsistent when there is a problem with the kontainer configuration
     */
    fun create(dynamics: DynamicOverrides.Builder.() -> Unit = {}): Kontainer {

        // On the first usage we validate the consistency of the container
        if (usages++ == 0) {
            // Validate the overall consistency
            validate()
        }

        val dynamicServices = DynamicOverrides.Builder().apply(dynamics).build()

        val unexpectedDynamics = dynamicsChecker.getUnexpected(dynamicServices.overrides.keys)

        if (unexpectedDynamics.isNotEmpty()) {
            throw KontainerInconsistent(
                "Unexpected dynamics were provided: " +
                        unexpectedDynamics.map { it.qualifiedName }.joinToString(", ")
            )
        }

        return instantiate(dynamicServices)
    }

    /**
     * Creates a new kontainer
     */
    internal fun instantiate(dynamics: DynamicOverrides): Kontainer {

        val factory = ServiceProviderFactory(
            blueprint = this,
            providerProviders = serviceProviderProviders,
            dynamics = dynamics,
        )

        return Kontainer(factory).apply {
            // Track the Kontainer
            tracker.track(this)
        }
    }

    private fun validate() {

        // Create a container with NO overwritten dynamic services to check to overall consistency
        val container = instantiate(DynamicOverrides(emptyMap()))

        // Validate all service providers are consistent
        val errors = container.getServiceProviderFactory().getAllProviders()
            .mapValues { (_, provider) -> provider to provider.validate(container) }
            .filterValues { (_, errors) -> errors.isNotEmpty() }
            .toList()
            .mapIndexed { serviceIdx, (cls, entry) ->

                val (provider, errors) = entry
                val codeLocation = provider.definition.codeLocation.first()

                "${serviceIdx + 1}. Service '${cls.qualifiedName}'\n" +
                        "    defined at ${codeLocation ?: "n/a"})\n" +
                        errors.joinToString("\n") { "    -> $it" }
            }

        if (errors.isNotEmpty()) {
            val err = "Kontainer is inconsistent!\n\n" +
                    "Problems:\n\n" +
                    errors.joinToString("\n") + "\n\n" +
                    "Config values:\n\n" +
                    configValues.map { (k, v) ->
                        "${k.padEnd(10)} => '$v' (${v::class.qualifiedName})"
                    }.joinToString("\n") + "\n"

            throw KontainerInconsistent(err)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy