Skip to content

Commit

Permalink
map & set multibinding
Browse files Browse the repository at this point in the history
Fixes gh-772
  • Loading branch information
luozejiaqun committed Aug 26, 2024
1 parent 926a7b6 commit abd0d70
Show file tree
Hide file tree
Showing 6 changed files with 840 additions and 29 deletions.
106 changes: 104 additions & 2 deletions projects/core/koin-core/src/commonMain/kotlin/org/koin/core/Koin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import org.koin.core.logger.EmptyLogger
import org.koin.core.logger.Logger
import org.koin.core.module.Module
import org.koin.core.module.flatten
import org.koin.core.module.mapMultibindingQualifier
import org.koin.core.module.setMultibindingQualifier
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.TypeQualifier
Expand Down Expand Up @@ -151,6 +153,106 @@ class Koin {
parameters: ParametersDefinition? = null,
): T? = scopeRegistry.rootScope.getOrNull(clazz, qualifier, parameters)

/**
* Lazy inject a Koin Map Multibinding instance
* @param qualifier
* @param parameters
*
* @return Lazy instance of type Map<K, V>
*/
inline fun <reified K, reified V> injectMapMultibinding(
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<Map<K, V>> = scopeRegistry.rootScope.inject(qualifier, mode, parameters)

/**
* Lazy inject a Koin Set Multibinding instance
* @param qualifier
* @param parameters
*
* @return Lazy instance of type Set<E>
*/
inline fun <reified E> injectSetMultibinding(
qualifier: Qualifier = setMultibindingQualifier<E>(),
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<Set<E>> = scopeRegistry.rootScope.inject(qualifier, mode, parameters)

/**
* Lazy inject a Koin Map Multibinding instance if available
* @param qualifier
* @param parameters
*
* @return Lazy instance of type Map<K, V> or null
*/
inline fun <reified K, reified V> injectMapMultibindingOrNull(
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<Map<K, V>?> = scopeRegistry.rootScope.injectOrNull(qualifier, mode, parameters)

/**
* Lazy inject a Koin Set Multibinding instance if available
* @param qualifier
* @param parameters
*
* @return Lazy instance of type Set<E> or null
*/
inline fun <reified E> injectSetMultibindingOrNull(
qualifier: Qualifier = setMultibindingQualifier<E>(),
mode: LazyThreadSafetyMode = KoinPlatformTools.defaultLazyMode(),
noinline parameters: ParametersDefinition? = null,
): Lazy<Set<E>?> = scopeRegistry.rootScope.injectOrNull(qualifier, mode, parameters)

/**
* Get a Koin Map Multibinding instance
* @param qualifier
* @param parameters
*
* @return instance of type Map<K, V>
*/
inline fun <reified K, reified V> getMapMultibinding(
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
noinline parameters: ParametersDefinition? = null,
): Map<K, V> = scopeRegistry.rootScope.get(qualifier, parameters)

/**
* Get a Koin Set Multibinding instance
* @param qualifier
* @param parameters
*
* @return instance of type Set<E>
*/
inline fun <reified E> getSetMultibinding(
qualifier: Qualifier = setMultibindingQualifier<E>(),
noinline parameters: ParametersDefinition? = null,
): Set<E> = scopeRegistry.rootScope.get(qualifier, parameters)

/**
* Get a Koin Map Multibinding instance if available
* @param qualifier
* @param parameters
*
* @return instance of type Map<K, V> or null
*/
inline fun <reified K, reified V> getMapMultibindingOrNull(
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
noinline parameters: ParametersDefinition? = null,
): Map<K, V>? = scopeRegistry.rootScope.getOrNull(qualifier, parameters)

/**
* Get a Koin Set Multibinding instance if available
* @param qualifier
* @param parameters
*
* @return instance of type Set<E> or null
*/
inline fun <reified E> getSetMultibindingOrNull(
qualifier: Qualifier = setMultibindingQualifier<E>(),
noinline parameters: ParametersDefinition? = null,
): Set<E>? = scopeRegistry.rootScope.getOrNull(qualifier, parameters)

/**
* Declare a component definition from the given instance
* This result of declaring a single definition of type T, returning the given instance
Expand Down Expand Up @@ -307,12 +409,12 @@ class Koin {
* @param allowOverride - allow to override definitions
* @param createEagerInstances - run instance creation for eager single definitions
*/
fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances : Boolean = false) {
fun loadModules(modules: List<Module>, allowOverride: Boolean = true, createEagerInstances: Boolean = false) {
val flattedModules = flatten(modules)
instanceRegistry.loadModules(flattedModules, allowOverride)
scopeRegistry.loadScopes(flattedModules)

if (createEagerInstances){
if (createEagerInstances) {
createEagerInstances()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
package org.koin.core.module

import org.koin.core.annotation.KoinInternalApi
import org.koin.core.definition.*
import org.koin.core.definition.Definition
import org.koin.core.definition.IndexKey
import org.koin.core.definition.Kind
import org.koin.core.definition.KoinDefinition
import org.koin.core.definition._createDefinition
import org.koin.core.definition.indexKey
import org.koin.core.error.DefinitionOverrideException
import org.koin.core.instance.FactoryInstanceFactory
import org.koin.core.instance.InstanceFactory
Expand Down Expand Up @@ -114,6 +119,69 @@ class Module(
return KoinDefinition(this, factory)
}

/**
* Declare a Single Map<K, V> and inject an element to it with a given key
* @param key can't be null
* @param definition - the element definition function
*/
inline fun <reified K : Any, reified V> intoMap(
key: K,
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
noinline definition: Definition<V>,
): KoinDefinition<Map<K, V>> {
single(multibindingValueQualifier(qualifier, key), definition = definition)
single(multibindingIterateKeyQualifier(qualifier, key)) {
MultibindingIterateKey(key, multibindingValueQualifier(qualifier, key))
}
return declareMapMultibinding(qualifier)
}

/**
* Declare a Single Map<K, V> definition, the key type [K] can't be null
* @param qualifier can't be null
* @param createdAtStart
*/
inline fun <reified K : Any, reified V> declareMapMultibinding(
qualifier: Qualifier = mapMultibindingQualifier<K, V>(),
createdAtStart: Boolean = false,
): KoinDefinition<Map<K, V>> {
val isCreatedAtStart = createdAtStart || this._createdAtStart
return single<Map<K, V>>(qualifier) { parametersHolder ->
MapMultibinding(isCreatedAtStart, this, qualifier, V::class, parametersHolder)
}
}

/**
* Declare a Single Set<E> and inject an element to it
* @param definition - the element definition function
*/
inline fun <reified E> intoSet(
qualifier: Qualifier = setMultibindingQualifier<E>(),
noinline definition: Definition<E>,
): KoinDefinition<Set<E>> {
val key = SetMultibinding.getDistinctKey()
single(multibindingValueQualifier(qualifier, key), definition = definition)
single(multibindingIterateKeyQualifier(qualifier, key)) {
MultibindingIterateKey(key, multibindingValueQualifier(qualifier, key))
}
return declareSetMultibinding(qualifier)
}

/**
* Declare a Single Set<E> definition
* @param qualifier can't be null
* @param createdAtStart
*/
inline fun <reified E> declareSetMultibinding(
qualifier: Qualifier = setMultibindingQualifier<E>(),
createdAtStart: Boolean = false,
): KoinDefinition<Set<E>> {
val isCreatedAtStart = createdAtStart || this._createdAtStart
return single(qualifier) { parametersHolder ->
SetMultibinding(isCreatedAtStart, this, qualifier, E::class, parametersHolder)
}
}

@KoinInternalApi
fun indexPrimaryType(instanceFactory: InstanceFactory<*>) {
val def = instanceFactory.beanDefinition
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2017-Present the original author or authors.
*
* 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.
*/
package org.koin.core.module

import co.touchlab.stately.concurrency.AtomicInt
import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.Qualifier
import org.koin.core.qualifier.StringQualifier
import org.koin.core.scope.Scope
import org.koin.ext.getFullName
import kotlin.reflect.KClass

/**
* Multibinding of Map & Set
*
* @author - luozejiaqun
*/
inline fun <reified K, reified V> mapMultibindingQualifier(): Qualifier =
StringQualifier("map_multibinding_${K::class.getFullName()}_${V::class.getFullName()}")

inline fun <reified E> setMultibindingQualifier(): Qualifier =
StringQualifier("set_multibinding_${E::class.getFullName()}")

@PublishedApi
internal fun <K> multibindingValueQualifier(multibindingQualifier: Qualifier, key: K): Qualifier =
StringQualifier("${multibindingQualifier.value}_of_$key")

@PublishedApi
internal fun <K> multibindingIterateKeyQualifier(multibindingQualifier: Qualifier, key: K): Qualifier =
StringQualifier("${multibindingQualifier.value}_iterate_$key")

@PublishedApi
internal class MultibindingIterateKey<T>(val key: T, val valueQualifier: Qualifier)

@PublishedApi
internal class MapMultibinding<K : Any, V>(
createdAtStart: Boolean,
private val scope: Scope,
private val qualifier: Qualifier,
private val valueClass: KClass<*>,
private val parametersHolder: ParametersHolder,
) : Map<K, V> {

init {
if (createdAtStart) {
values
}
}

override val keys: Set<K>
get() {
val multibindingKeys = mutableSetOf<K>()
scope.getAll<MultibindingIterateKey<*>>()
.mapNotNullTo(multibindingKeys) { multibindingIterateKey ->
if (multibindingIterateKey.valueQualifier.value.startsWith(qualifier.value)) {
multibindingIterateKey.key as? K
} else {
null
}
}
return multibindingKeys
}

override val size: Int
get() = keys.size

override val values: Collection<V>
get() = keys.map { get(it) }

override val entries: Set<Map.Entry<K, V>>
get() {
val filed = LinkedHashSet<Map.Entry<K, V>>()
keys.mapTo(filed) { Entry(it, get(it)) }
return filed
}

override fun containsKey(key: K): Boolean =
keys.contains(key)

override fun containsValue(value: V): Boolean =
values.contains(value)

override fun get(key: K): V {
return scope.get(valueClass, multibindingValueQualifier(qualifier, key)) { parametersHolder }
}

override fun isEmpty(): Boolean = keys.isEmpty()

private class Entry<K, V>(override val key: K, override val value: V) : Map.Entry<K, V>
}

@PublishedApi
internal class SetMultibinding<E>(
createdAtStart: Boolean,
private val scope: Scope,
private val qualifier: Qualifier,
private val valueClass: KClass<*>,
private val parametersHolder: ParametersHolder,
) : Set<E> {
init {
if (createdAtStart) {
getAll()
}
}

override val size: Int
get() = getAllKeys().size

override fun contains(element: E): Boolean {
return getAll().contains(element)
}

override fun containsAll(elements: Collection<E>): Boolean {
return getAll().containsAll(elements)
}

override fun isEmpty(): Boolean = size == 0

override fun iterator(): Iterator<E> {
return getAll().iterator()
}

private fun getAllKeys(): Set<Key> {
val multibindingKeys = mutableSetOf<Key>()
scope.getAll<MultibindingIterateKey<*>>()
.mapNotNullTo(multibindingKeys) { multibindingIterateKey ->
if (multibindingIterateKey.valueQualifier.value.startsWith(qualifier.value)) {
multibindingIterateKey.key as? Key
} else {
null
}
}
return multibindingKeys
}

private fun getAll(): Set<E> {
val result = LinkedHashSet<E>()
getAllKeys().mapTo(result) { get(it) }
return result
}

private fun get(key: Key): E {
return scope.get(valueClass, multibindingValueQualifier(qualifier, key)) { parametersHolder }
}

class Key(private val placeholder: Int) {
override fun toString(): String = "placeholder_$placeholder"
}

companion object {
private val accumulatingKey = AtomicInt(0)

fun getDistinctKey() = Key(accumulatingKey.incrementAndGet())
}
}
Loading

0 comments on commit abd0d70

Please sign in to comment.