Last active
May 8, 2023 12:42
-
-
Save hrules6872/3e1c6f605b96835954a05127110127b1 to your computer and use it in GitHub Desktop.
Yet another Koin killer :)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
private val data = submodule { | |
/* ... */ | |
} | |
private val usecases = submodule { | |
/* ... */ | |
} | |
private val example = submodule { | |
factory { Example(get(), get(), get()) } | |
singleOf(::Example) | |
} | |
val locator = locator { | |
single<Application> { App.get() } | |
single<Context> { get<Application>().applicationContext } | |
/* ... */ | |
submodule(data).overrideWith { flavorData } | |
submodule(usecases).overrideWith { flavorUseCase } | |
}.overrideWith { flavorLocator } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import kotlin.reflect.KClass | |
object ServiceLocator { | |
var locator: Module = Module() | |
private set | |
fun create(module: Module) { | |
if (locator.definitions.isNotEmpty()) error("Dependency graph already created") | |
this.locator = module | |
} | |
} | |
fun startServiceLocator(module: Module) = ServiceLocator.create(module) | |
fun startServiceLocator(module: Declaration) = ServiceLocator.create(locator { module() }) | |
data class Identifier(val instanceClass: KClass<*>, val named: String) { | |
override fun toString(): String = "$instanceClass" + if (named.isNotEmpty()) " named $named" else "" | |
} | |
abstract class Dependency<TYPE>(val value: () -> TYPE) { | |
abstract fun get(): TYPE | |
} | |
class Factory<TYPE>(value: () -> TYPE) : Dependency<TYPE>(value) { | |
override fun get(): TYPE = value() | |
} | |
class Singleton<TYPE>(value: () -> TYPE) : Dependency<TYPE>(value) { | |
private val instance by lazy { value() } | |
override fun get(): TYPE = instance | |
} | |
class Module internal constructor() { | |
private val _definitions = mutableMapOf<Identifier, Dependency<*>>() | |
val definitions: Map<Identifier, Dependency<*>> get() = _definitions.toMap() | |
inline fun <reified TYPE> single(named: String? = "", override: Boolean = false, noinline instance: () -> TYPE): Identifier { | |
val identifier = Identifier(TYPE::class, named.orEmpty()) | |
val singleton = Singleton(instance) | |
updateDefinition(identifier, singleton, override) | |
return identifier | |
} | |
inline fun <reified TYPE> factory(named: String? = "", override: Boolean = false, noinline instance: () -> TYPE): Identifier { | |
val identifier = Identifier(TYPE::class, named.orEmpty()) | |
val factory = Factory(instance) | |
updateDefinition(identifier, factory, override) | |
return identifier | |
} | |
inline fun <reified TYPE> resolve(noinline instance: () -> TYPE): TYPE = instance() | |
fun <TYPE> updateDefinition(identifier: Identifier, instance: Dependency<TYPE>, override: Boolean = false) { | |
if (!override) if (definitions.containsKey(identifier)) throw DependencyCreationException("Found duplicated definition for: $identifier") | |
_definitions[identifier] = instance | |
} | |
private val latestResolved: MutableList<Identifier> = mutableListOf() | |
fun checkLatestResolved(identifier: Identifier): Boolean = when { | |
latestResolved.contains(identifier) -> false | |
else -> { | |
latestResolved.add(identifier) | |
true | |
} | |
} | |
fun clearLatestResolved() = latestResolved.clear() | |
fun latestResolvedToString() = latestResolved.joinToString(separator = System.lineSeparator()) | |
@Suppress("TooGenericExceptionCaught", "ThrowsCount") | |
inline fun <reified RESULT> get(named: String? = ""): RESULT { | |
val identifier = Identifier(RESULT::class, named.orEmpty()) | |
try { | |
if (!checkLatestResolved(identifier)) throw CyclicDependencyCreationException() | |
val definition = definitions[identifier]?.get() as? RESULT ?: throw DependencyResolutionException("No definition found for: $identifier") | |
clearLatestResolved() | |
return definition | |
} catch (exception: RuntimeException) { | |
when (exception) { | |
is CyclicDependencyCreationException, | |
is StackOverflowError -> throw CyclicDependencyCreationException( | |
"Cyclic dependency detected while resolving:${System.lineSeparator()}${latestResolvedToString()}" | |
) | |
else -> throw exception | |
} | |
} | |
} | |
} | |
class DependencyCreationException(override val message: String) : RuntimeException() | |
class DependencyResolutionException(override val message: String) : RuntimeException() | |
class CyclicDependencyCreationException(msg: String = "") : RuntimeException(msg) | |
private typealias Declaration = Module.() -> Unit | |
fun locator(declaration: Declaration): Module = Module().apply(declaration) | |
fun module(declaration: Declaration): Module = Module().apply(declaration) | |
fun submodule(declaration: Declaration): Declaration = declaration | |
fun overrideWith(declaration: Declaration): Declaration = declaration | |
fun Module.submodule(alias: String = "", declaration: Declaration): Module = this.apply(declaration) | |
fun Module.submodule(submodule: Declaration): Module = this.apply(submodule) | |
fun Module.overrideWith(submodule: Declaration): Module = this.apply(submodule) | |
inline fun <reified T> inject(named: String? = ""): Lazy<T> = lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ServiceLocator.locator.get(named) } | |
inline fun <reified T> resolve(noinline instance: Module.() -> T): Lazy<T> = lazy(LazyThreadSafetyMode.SYNCHRONIZED) { instance(ServiceLocator.locator) } | |
inline fun <reified T> instantiate(named: String? = ""): T = ServiceLocator.locator.get(named) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
import org.amshove.kluent.* | |
import org.junit.Test | |
class ServiceLocatorTest { | |
@Test | |
fun `try to resolve a circular dependency should throw CyclicDependencyCreationException`() { | |
val module = module { | |
single { CycleA(get()) } | |
single { CycleB(get()) } | |
} | |
invoking { module.get<CycleA>() } `should throw` CyclicDependencyCreationException::class | |
} | |
@Test | |
fun `duplicate dependency definition should throw DependencyCreationException`() { | |
invoking { | |
module { | |
single { ClazzA() } | |
single { ClazzA() } | |
} | |
} `should throw` DependencyCreationException::class | |
} | |
@Test | |
fun `override duplicate dependency definition should pass ok`() { | |
invoking { | |
module { | |
single { ClazzA() } | |
single(override = true) { ClazzA() } | |
} | |
} `should not throw` DependencyCreationException::class | |
} | |
@Test | |
fun `try to resolve a not defined dependency should throw DependencyResolutionException`() { | |
val module = module { | |
single { ClazzA() } | |
} | |
invoking { module.get<CycleB>() } `should throw` DependencyResolutionException::class | |
} | |
} | |
private class Clazzes { | |
class ClazzA | |
class ClazzB(val a: ClazzA) | |
class CycleA(val b: CycleB) | |
class CycleB(val a: CycleA) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3> Module.instantiate(constructor: (TYPE1, TYPE2, TYPE3) -> RESULT): RESULT = | |
constructor(get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4> Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5> Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6> Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7> Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get(), get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8> | |
Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get(), get(), get(), get(), get()) | |
inline fun <reified RESULT, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8, | |
reified TYPE9> Module.instantiate( | |
constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8, TYPE9) -> RESULT | |
): RESULT = constructor(get(), get(), get(), get(), get(), get(), get(), get(), get()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
inline fun <reified TYPE> Module.factoryOf(crossinline constructor: () -> TYPE): Identifier = | |
factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1> Module.factoryOf(crossinline constructor: (TYPE1) -> TYPE): Identifier = | |
factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2> Module.factoryOf(crossinline constructor: (TYPE1, TYPE2) -> TYPE): Identifier = | |
factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3> Module.factoryOf(crossinline constructor: (TYPE1, TYPE2, TYPE3) -> TYPE): Identifier = | |
factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4> Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5> Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6> Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7> Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8> | |
Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8, reified TYPE9> | |
Module.factoryOf( | |
crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8, TYPE9) -> TYPE | |
): Identifier = factory(instance = { instantiate(constructor) }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2022. Héctor de Isidro - hrules6872 | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
inline fun <reified TYPE> Module.singleOf(crossinline constructor: () -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1> Module.singleOf(crossinline constructor: (TYPE1) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2> Module.singleOf(crossinline constructor: (TYPE1, TYPE2) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3> Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4) -> TYPE): Identifier = single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5) -> TYPE): Identifier = single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6) -> TYPE): Identifier = single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) | |
inline fun <reified TYPE, reified TYPE1, reified TYPE2, reified TYPE3, reified TYPE4, reified TYPE5, reified TYPE6, reified TYPE7, reified TYPE8, reified TYPE9> | |
Module.singleOf(crossinline constructor: (TYPE1, TYPE2, TYPE3, TYPE4, TYPE5, TYPE6, TYPE7, TYPE8, TYPE9) -> TYPE): Identifier = | |
single(instance = { instantiate(constructor) }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment