Last active
April 23, 2025 12:23
-
-
Save hector6872/3e1c6f605b96835954a05127110127b1 to your computer and use it in GitHub Desktop.
Yet another Koin killer :)
This file contains hidden or 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 - hector6872 | |
* | |
* 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 hidden or 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) 2025. Héctor de Isidro - hector6872 | |
* | |
* 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") | |
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 | |
} | |
@Suppress("TooGenericExceptionCaught", "ThrowsCount", "SwallowedException") | |
inline fun <reified RESULT> get(named: String? = ""): RESULT { | |
val identifier = Identifier(RESULT::class, named.orEmpty()) | |
return try { | |
definitions[identifier]?.get() as? RESULT ?: throw DependencyResolutionException("No definition found for: $identifier") | |
} catch (error: StackOverflowError) { | |
throw CyclicDependencyCreationException() | |
} catch (exception: Exception) { | |
when (exception) { | |
is CyclicDependencyCreationException -> throw CyclicDependencyCreationException("Cyclic dependency detected while resolving: $identifier") | |
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(module: Declaration): Module = this.apply(module) | |
inline fun <reified TYPE> inject(named: String? = ""): Lazy<TYPE> = lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ServiceLocator.locator.get(named) } | |
inline fun <reified TYPE> resolve(noinline instance: Module.() -> TYPE): Lazy<TYPE> = lazy(LazyThreadSafetyMode.SYNCHRONIZED) { instance(ServiceLocator.locator) } | |
inline fun <reified TYPE> instantiate(named: String? = ""): TYPE = ServiceLocator.locator.get(named) |
This file contains hidden or 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 - hector6872 | |
* | |
* 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()) } | |
} | |
assertThatExceptionOfType(CyclicDependencyCreationException::class.java).isThrownBy { module.get<CycleA>() } | |
} | |
@Test | |
fun `duplicate dependency definition should throw DependencyCreationException`() { | |
assertThatExceptionOfType(DependencyCreationException::class.java).isThrownBy { | |
module { | |
single { ClazzA() } | |
single { ClazzA() } | |
} | |
} | |
} | |
@Test | |
fun `override duplicate dependency definition should pass ok`() { | |
assertThatCode { | |
module { | |
single { ClazzA() } | |
single(override = true) { ClazzA() } | |
} | |
}.doesNotThrowAnyException() | |
} | |
@Test | |
fun `try to resolve a not defined dependency should throw DependencyResolutionException`() { | |
val module = module { single { ClazzA() } } | |
assertThatExceptionOfType(DependencyResolutionException::class.java).isThrownBy { module.get<CycleB>() } | |
} | |
} | |
private class Clazzes { | |
class ClazzA | |
class ClazzB(val a: ClazzA) | |
class CycleA(val b: CycleB) | |
class CycleB(val a: CycleA) | |
} |
This file contains hidden or 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 - hector6872 | |
* | |
* 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 hidden or 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 - hector6872 | |
* | |
* 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 hidden or 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 - hector6872 | |
* | |
* 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