Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@hrules6872
Last active May 8, 2023 12:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hrules6872/3e1c6f605b96835954a05127110127b1 to your computer and use it in GitHub Desktop.
Save hrules6872/3e1c6f605b96835954a05127110127b1 to your computer and use it in GitHub Desktop.
Yet another Koin killer :)
/*
* 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 }
/*
* 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)
/*
* 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)
}
/*
* 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())
/*
* 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) })
/*
* 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