diff --git a/samples/core/src/withStitch/kotlin/com/harrytmthy/stitch/core/Qualifiers.kt b/samples/core/src/withStitch/kotlin/com/harrytmthy/stitch/core/Qualifiers.kt new file mode 100644 index 0000000..6b0e391 --- /dev/null +++ b/samples/core/src/withStitch/kotlin/com/harrytmthy/stitch/core/Qualifiers.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2026 Harry Timothy Tumalewa + * + * 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 + * + * https://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 com.harrytmthy.stitch.core + +import com.harrytmthy.stitch.annotations.Qualifier + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class Production + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class Staging diff --git a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeActivity.kt b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeActivity.kt index 6b89400..66e5e98 100644 --- a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeActivity.kt +++ b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeActivity.kt @@ -18,8 +18,14 @@ package com.harrytmthy.stitch.feature.home import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.harrytmthy.stitch.annotations.Named import com.harrytmthy.stitch.api.StitchInjector +import com.harrytmthy.stitch.api.get +import com.harrytmthy.stitch.api.named +import com.harrytmthy.stitch.api.typed import com.harrytmthy.stitch.core.Logger +import com.harrytmthy.stitch.core.Production +import com.harrytmthy.stitch.core.Staging import javax.inject.Inject class HomeActivity : AppCompatActivity() { @@ -30,11 +36,47 @@ class HomeActivity : AppCompatActivity() { @Inject lateinit var viewModel: HomeViewModel + @Production + @Inject + lateinit var homeService: HomeService + + @Production + @Inject + lateinit var homeServiceImpl: HomeServiceImpl + + @Staging + @Inject + lateinit var homeServiceStaging: HomeService + + @Staging + @Inject + lateinit var homeServiceImplStaging: HomeServiceStaging + + @Named("dev") + @Inject + lateinit var homeServiceDev: HomeService + + @Named("dev") + @Inject + lateinit var homeServiceDevImpl: HomeServiceDev + override fun onCreate(savedInstanceState: Bundle?) { - StitchInjector.getSingletonGraph() + val activityInjector = StitchInjector.getSingletonGraph() .createInjectorForChildScope("activity") - .inject(this) + activityInjector.inject(this) super.onCreate(savedInstanceState) setContentView(android.R.layout.list_content) + assert(homeService === homeServiceImpl) + assert(viewModel.homeService === homeServiceImpl) + assert(homeServiceStaging === homeServiceImplStaging) + assert(homeService !== homeServiceStaging) + assert(homeServiceDev === homeServiceDevImpl) + assert(homeService !== homeServiceDev) + + val stagingQualifier = typed() + assert(activityInjector.get(stagingQualifier) === homeServiceStaging) + + val devQualifier = named("dev") + assert(activityInjector.get(devQualifier) === homeServiceDev) } } diff --git a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeService.kt b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeService.kt index 757837e..d86008f 100644 --- a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeService.kt +++ b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeService.kt @@ -17,7 +17,10 @@ package com.harrytmthy.stitch.feature.home import com.harrytmthy.stitch.annotations.Binds +import com.harrytmthy.stitch.annotations.Named import com.harrytmthy.stitch.core.Logger +import com.harrytmthy.stitch.core.Production +import com.harrytmthy.stitch.core.Staging import javax.inject.Inject import javax.inject.Singleton @@ -27,6 +30,7 @@ interface HomeService { @Singleton @Binds(aliases = [HomeService::class]) +@Production class HomeServiceImpl @Inject constructor(private val logger: Logger) : HomeService { override fun fetch(): Result { @@ -34,3 +38,25 @@ class HomeServiceImpl @Inject constructor(private val logger: Logger) : HomeServ return Result.success(Unit) } } + +@Singleton +@Binds(aliases = [HomeService::class]) +@Staging +class HomeServiceStaging @Inject constructor(private val logger: Logger) : HomeService { + + override fun fetch(): Result { + logger.log("Fetch on staging success!") + return Result.success(Unit) + } +} + +@Singleton +@Binds(aliases = [HomeService::class]) +@Named("dev") +class HomeServiceDev @Inject constructor(private val logger: Logger) : HomeService { + + override fun fetch(): Result { + logger.log("Fetch on dev success!") + return Result.success(Unit) + } +} diff --git a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeViewModel.kt b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeViewModel.kt index ed6f3bb..1b864d5 100644 --- a/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeViewModel.kt +++ b/samples/feature/home/src/withStitch/kotlin/com/harrytmthy/stitch/feature/home/HomeViewModel.kt @@ -18,13 +18,14 @@ package com.harrytmthy.stitch.feature.home import com.harrytmthy.stitch.core.Activity import com.harrytmthy.stitch.core.Logger +import com.harrytmthy.stitch.core.Production import javax.inject.Inject @Activity class HomeViewModel @Inject constructor( private val logger1: Logger, private val logger2: Logger, // logger1 === logger2 - private val homeService: HomeService, + @param:Production val homeService: HomeService, ) { fun fetchHomeData() { diff --git a/stitch-annotations/src/commonMain/kotlin/com/harrytmthy/stitch/annotations/Named.kt b/stitch-annotations/src/commonMain/kotlin/com/harrytmthy/stitch/annotations/Named.kt index 985ed77..3185f01 100644 --- a/stitch-annotations/src/commonMain/kotlin/com/harrytmthy/stitch/annotations/Named.kt +++ b/stitch-annotations/src/commonMain/kotlin/com/harrytmthy/stitch/annotations/Named.kt @@ -36,6 +36,7 @@ package com.harrytmthy.stitch.annotations */ @Qualifier @Target( + AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, diff --git a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/ScopedGraphGenerator.kt b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/ScopedGraphGenerator.kt index 3e988fb..06c63bf 100644 --- a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/ScopedGraphGenerator.kt +++ b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/ScopedGraphGenerator.kt @@ -408,6 +408,7 @@ object ScopedGraphGenerator { private fun TypeSpec.Builder.addUniversalGetterFunction(plan: InjectorPlan) { val qualifierClass = com.harrytmthy.stitch.api.Qualifier::class.asClassName() val namedClass = com.harrytmthy.stitch.api.Named::class.asClassName() + val typedClass = com.harrytmthy.stitch.api.Typed::class.asClassName() val kClassClass = kotlin.reflect.KClass::class.asClassName() addFunction( @@ -420,18 +421,21 @@ object ScopedGraphGenerator { .build(), ) .returns(TypeVariableName("T")) - .addCode(buildUniversalGetterBody(plan, namedClass)) + .addCode(buildUniversalGetterBody(plan, namedClass, typedClass)) .build(), ) } - private fun buildUniversalGetterBody(plan: InjectorPlan, namedClass: ClassName): CodeBlock { + private fun buildUniversalGetterBody( + plan: InjectorPlan, + namedClass: ClassName, + typedClass: ClassName, + ): CodeBlock { val bindingsByType = linkedMapOf>() for (binding in plan.ancestorBindings) { bindingsByType.getOrPut(binding.type) { ArrayList() } } for (binding in plan.ancestorBindings) { - @Suppress("UNCHECKED_CAST") (bindingsByType.getValue(binding.type) as MutableList).add(binding) } @@ -444,11 +448,15 @@ object ScopedGraphGenerator { val unqualified = bindings.firstOrNull { it.qualifier == null } val namedBindings = bindings.mapNotNull { binding -> - val qualifier = binding.qualifier - if (qualifier is Qualifier.Named) { - qualifier.value to binding - } else { - null + when (val qualifier = binding.qualifier) { + is Qualifier.Named -> qualifier.value to binding + else -> null + } + } + val typedBindings = bindings.mapNotNull { binding -> + when (val qualifier = binding.qualifier) { + is Qualifier.Custom -> qualifier.qualifiedName to binding + else -> null } } @@ -465,22 +473,60 @@ object ScopedGraphGenerator { endControlFlow() } - if (namedBindings.isNotEmpty()) { - beginControlFlow("if (qualifier is %T)", namedClass) - beginControlFlow("when (qualifier.value)") - for ((name, binding) in namedBindings) { - addStatement("%S -> return %L as T", name, dependencyAccess(plan.scope, binding)) + if (namedBindings.isNotEmpty() || typedBindings.isNotEmpty()) { + beginControlFlow("when (qualifier)") + if (namedBindings.isNotEmpty()) { + beginControlFlow("is %T ->", namedClass) + beginControlFlow("when (qualifier.value)") + for ((name, binding) in namedBindings) { + addStatement( + "%S -> return %L as T", + name, + dependencyAccess(plan.scope, binding), + ) + } + addStatement( + $$"else -> error(\"Binding with type '${type.simpleName}' has no qualifier with name '${qualifier.value}'\")", + ) + endControlFlow() + endControlFlow() } - addStatement($$"else -> error(\"Binding with type '${type.simpleName}' has no qualifier with name '${qualifier.value}'\")") - endControlFlow() + + if (typedBindings.isNotEmpty()) { + beginControlFlow("is %T ->", typedClass) + beginControlFlow("when (qualifier.value)") + for ((qualifiedName, binding) in typedBindings) { + addStatement( + "%T::class -> return %L as T", + ClassName.bestGuess(qualifiedName), + dependencyAccess(plan.scope, binding), + ) + } + addStatement( + $$"else -> error(\"Binding with type '${type.simpleName}' has no qualifier with type '${qualifier.value.qualifiedName}'\")", + ) + endControlFlow() + endControlFlow() + } + + addStatement( + $$"else -> error(\"Binding with type '${type.simpleName}' has no matching qualifier '$qualifier'\")", + ) endControlFlow() + } else { + addStatement( + $$"error(\"Binding with type '${type.simpleName}' has no matching qualifier '$qualifier'\")", + ) } - addStatement($$"error(\"Binding with type '${type.simpleName}' has no matching qualifier '$qualifier'\")") + endControlFlow() } } } - addStatement($$"else -> error(\"Binding with type '${type.simpleName}' is not found in $currentScope scope and its ancestors.\")") + + addStatement( + $$"else -> error(\"Binding with type '${type.simpleName}' is not found in $currentScope scope and its ancestors.\")", + ) endControlFlow() }.build() } @@ -494,6 +540,11 @@ object ScopedGraphGenerator { when (val qualifier = binding.qualifier) { null -> Unit + is Qualifier.Custom -> { + append("_custom_") + append(sanitizeName(qualifier.qualifiedName)) + } + is Qualifier.Named -> { append("_named_") append(sanitizeName(qualifier.value)) diff --git a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/model/CoreModels.kt b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/model/CoreModels.kt index 04716cb..968622a 100644 --- a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/model/CoreModels.kt +++ b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/model/CoreModels.kt @@ -103,8 +103,14 @@ sealed class Qualifier { abstract fun encode(): String + data class Custom(val qualifiedName: String) : Qualifier() { + override fun encode(): String = "Custom:$qualifiedName" + override fun toString(): String = qualifiedName + } + data class Named(val value: String) : Qualifier() { override fun encode(): String = "Named:$value" + override fun toString(): String = "Named '$value'" } companion object { @@ -118,6 +124,7 @@ sealed class Qualifier { error("Should not happen") } return when { + parts[0] == "Custom" -> Custom(parts[1]) parts[0] == "Named" -> Named(parts[1]) else -> error("Should not happen") } diff --git a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/scanner/LocalAnnotationScanner.kt b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/scanner/LocalAnnotationScanner.kt index 002a6d3..c3827c0 100644 --- a/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/scanner/LocalAnnotationScanner.kt +++ b/stitch-ksp/src/main/kotlin/com/harrytmthy/stitch/ksp/scanner/LocalAnnotationScanner.kt @@ -48,6 +48,7 @@ import com.harrytmthy.stitch.ksp.utils.filePathAndLineNumber import com.harrytmthy.stitch.ksp.utils.find import com.harrytmthy.stitch.ksp.utils.findArgument import com.harrytmthy.stitch.ksp.utils.qualifiedName +import com.harrytmthy.stitch.annotations.Qualifier as QualifierAnnotation import com.harrytmthy.stitch.annotations.Scope as ScopeAnnotation class LocalAnnotationScanner( @@ -59,7 +60,7 @@ class LocalAnnotationScanner( private val customScopeByQualifiedName = HashMap() - private val qualifierBySymbol = HashMap() + private val customQualifierByQualifiedName = HashMap() private val nullableBySymbol = HashSet() @@ -73,7 +74,6 @@ class LocalAnnotationScanner( scanRoot() scanScopes() scanDependsOn() - scanQualifiers() scanNullables() scanProvides() scanInjects() @@ -237,23 +237,6 @@ class LocalAnnotationScanner( } } - private fun scanQualifiers() { - scanNamedQualifiers() - // TODO: Add more qualifier types - } - - private fun scanNamedQualifiers() { - for (annotationName in namedAnnotations) { - for (symbol in resolver.getSymbolsWithAnnotation(annotationName)) { - if (symbol in qualifierBySymbol) { - fatalError("@Named cannot be used with other qualifiers", symbol) - } - val name = symbol.annotations.find(annotationName).arguments.first().value as String - qualifierBySymbol[symbol] = Qualifier.Named(name) - } - } - } - private fun scanNullables() { for (nullableAnnotation in nullableAnnotations) { for (symbol in resolver.getSymbolsWithAnnotation(nullableAnnotation)) { @@ -308,7 +291,7 @@ class LocalAnnotationScanner( val resolvedType = symbol.returnType?.resolve() val type = resolvedType?.declaration?.qualifiedName(symbol) ?: fatalError("@Provides has no return type", symbol) - val qualifier = qualifierBySymbol[symbol] + val qualifier = getQualifierFromSymbol(symbol) val scope = getScopeFromSymbol(symbol) val location = symbol.filePathAndLineNumber!! val parentDeclaration = symbol.parentDeclaration as? KSClassDeclaration @@ -393,7 +376,7 @@ class LocalAnnotationScanner( } val resolvedType = canonicalSymbol.asStarProjectedType() val type = resolvedType.declaration.qualifiedName(canonicalSymbol) - val qualifier = qualifierBySymbol[canonicalSymbol] + val qualifier = getQualifierFromSymbol(canonicalSymbol) val scope = getScopeFromSymbol(canonicalSymbol) val location = canonicalSymbol.filePathAndLineNumber!! val annotatedWithNullable = canonicalSymbol in nullableBySymbol @@ -432,7 +415,7 @@ class LocalAnnotationScanner( fatalError("@Inject field '${symbol.simpleName}' cannot be private", symbol) } val type = symbol.type.resolve().declaration.qualifiedName(symbol) - val qualifier = qualifierBySymbol[symbol] + val qualifier = getQualifierFromSymbol(symbol) val location = symbol.filePathAndLineNumber!! val fieldName = symbol.simpleName.asString() val binding = RequestedBinding(type, qualifier, location, fieldName) @@ -445,7 +428,7 @@ class LocalAnnotationScanner( for ((providedBinding, parameters) in parametersByBinding) { for (parameter in parameters) { val type = parameter.type.resolve().declaration.qualifiedName(parameter) - val qualifier = qualifierBySymbol[parameter] + val qualifier = getQualifierFromSymbol(parameter) val location = parameter.filePathAndLineNumber!! val binding = BindingDeclaration(type, qualifier, location) val dependencies = providedBinding.dependencies @@ -507,7 +490,7 @@ class LocalAnnotationScanner( } // TODO: Display a proper KSP warning to inform about the skipped @Binds val dependency = providedBindingBySymbol[symbol] ?: continue - val qualifier = qualifierBySymbol[symbol] + val qualifier = getQualifierFromSymbol(symbol) val location = symbol.filePathAndLineNumber.orEmpty() for (alias in aliases) { val type = (alias as KSType).declaration.qualifiedName(symbol) @@ -526,7 +509,7 @@ class LocalAnnotationScanner( "@Binds requires a return type when annotating functions", symbol, ) - val qualifier = qualifierBySymbol[symbol] + val qualifier = getQualifierFromSymbol(symbol) val location = symbol.filePathAndLineNumber.orEmpty() if (symbol.isAbstract) { val parameter = symbol.parameters.singleOrNull() @@ -596,6 +579,44 @@ class LocalAnnotationScanner( scanResult.providedBindings[alias] = alias } + private fun getQualifierFromSymbol(symbol: KSAnnotated): Qualifier? { + var qualifier: Qualifier? = null + for (annotation in symbol.annotations) { + val declaration = annotation.annotationType.resolve().declaration + val qualifiedName = declaration.qualifiedName(symbol) + if (qualifiedName in namedAnnotations) { + // @Named("...") path + if (qualifier != null) { + fatalError("Multiple qualifiers are not allowed", symbol) + } + val value = annotation.arguments[0].value as String + qualifier = Qualifier.Named(value) + continue + } + + // @CustomQualifier path + customQualifierByQualifiedName[qualifiedName]?.let { + if (qualifier != null) { + fatalError("Multiple qualifiers are not allowed", symbol) + } + qualifier = it + continue + } + for (metaAnnotation in declaration.annotations) { + val fqn = metaAnnotation.annotationType.resolve().declaration.qualifiedName(symbol) + if (fqn in qualifierAnnotations) { + if (qualifier != null) { + fatalError("Multiple qualifiers are not allowed", symbol) + } + qualifier = Qualifier.Custom(qualifiedName) + customQualifierByQualifiedName[qualifiedName] = qualifier + break + } + } + } + return qualifier + } + /** * Should be used only when scanning `@Provides` + constructor injections. */ @@ -647,7 +668,12 @@ class LocalAnnotationScanner( "javax.inject.Inject", ) - val namedAnnotations = listOf( + val qualifierAnnotations = setOf( + QualifierAnnotation::class.qualifiedName!!, + "javax.inject.Qualifier", + ) + + val namedAnnotations = setOf( Named::class.qualifiedName!!, "javax.inject.Named", ) diff --git a/stitch/api/stitch.api b/stitch/api/stitch.api index b3cc495..62f7243 100644 --- a/stitch/api/stitch.api +++ b/stitch/api/stitch.api @@ -45,22 +45,17 @@ public final class com/harrytmthy/stitch/api/ModuleKt { } public final class com/harrytmthy/stitch/api/Named : com/harrytmthy/stitch/api/Qualifier { - public static final field Companion Lcom/harrytmthy/stitch/api/Named$Companion; - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getValue ()Ljava/lang/String; public fun hashCode ()I } -public final class com/harrytmthy/stitch/api/Named$Companion { - public final fun of (Ljava/lang/String;)Lcom/harrytmthy/stitch/api/Named; -} - public abstract interface class com/harrytmthy/stitch/api/Qualifier { } public final class com/harrytmthy/stitch/api/QualifierKt { public static final fun named (Ljava/lang/String;)Lcom/harrytmthy/stitch/api/Named; + public static final fun typed (Lkotlin/reflect/KClass;)Lcom/harrytmthy/stitch/api/Typed; } public final class com/harrytmthy/stitch/api/ResolutionContext { @@ -123,6 +118,12 @@ public final class com/harrytmthy/stitch/api/StitchInjectorKt { public static final fun close (Lcom/harrytmthy/stitch/api/Injector;)V } +public final class com/harrytmthy/stitch/api/Typed : com/harrytmthy/stitch/api/Qualifier { + public fun equals (Ljava/lang/Object;)Z + public final fun getValue ()Lkotlin/reflect/KClass; + public fun hashCode ()I +} + public final class com/harrytmthy/stitch/exception/CycleException : com/harrytmthy/stitch/exception/GetFailedException { } diff --git a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Qualifier.kt b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Qualifier.kt index f0645c0..089d041 100644 --- a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Qualifier.kt +++ b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Qualifier.kt @@ -18,17 +18,16 @@ package com.harrytmthy.stitch.api import com.harrytmthy.stitch.internal.ConcurrentHashMap import kotlinx.atomicfu.atomic +import kotlin.reflect.KClass /** * Differentiates between multiple bindings of the same type. - * - * @see Named */ sealed interface Qualifier /** * A string-based [Qualifier] for the runtime path. Instances are pooled, where calling - * [named] or [of] with the same value always returns the same instance. + * [named] with the same value always returns the same instance. * * ``` * val prodModule = module { @@ -39,34 +38,79 @@ sealed interface Qualifier * val config: Config = Stitch.get(qualifier = named("prod")) * ``` */ -class Named private constructor(val value: String) : Qualifier { +class Named internal constructor(val value: String) : Qualifier { - private val id = nextId() + private val id = QualifierManager.nextId() override fun hashCode(): Int = id override fun equals(other: Any?): Boolean = other is Named && other.id == this.id +} + +/** + * A type-based [Qualifier] for the runtime path. Instances are pooled, where calling + * [typed] with the same type always returns the same instance. + * + * Useful when qualifier annotations are already defined and reusing them avoids string duplication: + * + * ``` + * val appModule = module { + * singleton(qualifier = typed()) { ProdConfig() }.bind() + * singleton(qualifier = typed()) { StagingConfig() }.bind() + * } + * + * val config: Config = Stitch.get(qualifier = typed()) + * ``` + */ +class Typed internal constructor(val value: KClass<*>) : Qualifier { - companion object { + private val id = QualifierManager.nextId() - private val pool = ConcurrentHashMap() + override fun hashCode(): Int = id + + override fun equals(other: Any?): Boolean = other is Typed && other.id == this.id +} - private val nextId = atomic(1) +internal object QualifierManager { - fun of(name: String): Named = pool.computeIfAbsent(name, ::Named) + private val namedPool = ConcurrentHashMap() - internal fun nextId(): Int = nextId.getAndIncrement() + private val typedPool = ConcurrentHashMap, Typed>() - internal fun clear() { - pool.clear() - nextId.value = 1 - } + private val nextId = atomic(1) + + fun getOrCreate(name: String): Named = namedPool.computeIfAbsent(name, ::Named) + + fun getOrCreate(type: KClass<*>): Typed = typedPool.computeIfAbsent(type, ::Typed) + + fun nextId(): Int = nextId.getAndIncrement() + + fun clear() { + namedPool.clear() + typedPool.clear() + nextId.value = 1 } } /** - * Returns a pooled [Named] qualifier for the given [value]. + * Returns a pooled [Named] qualifier for the given [value], creating one if it doesn't exist. + */ +fun named(value: String): Named = QualifierManager.getOrCreate(value) + +/** + * Returns a pooled [Typed] qualifier for the given type, creating one if it doesn't exist. + */ +inline fun typed(): Typed = typed(T::class) + +/** + * Returns a pooled [Typed] qualifier for the given type, creating one if it doesn't exist. + * Use this to ease migration from Koin. + */ +inline fun typeQualifier(): Typed = typed(T::class) + +/** + * Returns a pooled [Typed] qualifier for the given [type], creating one if it doesn't exist. */ -fun named(value: String): Named = Named.of(value) +fun typed(type: KClass<*>): Typed = QualifierManager.getOrCreate(type) internal object DefaultQualifier : Qualifier diff --git a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Stitch.kt b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Stitch.kt index d9a3b5b..7a15bc9 100644 --- a/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Stitch.kt +++ b/stitch/src/commonMain/kotlin/com/harrytmthy/stitch/api/Stitch.kt @@ -86,7 +86,7 @@ object Stitch { */ fun reset() { unregisterAll() - Named.clear() + QualifierManager.clear() ScopeManager.clear() }