diff --git a/compiler/container/container.iml b/compiler/container/container.iml index b530e6a1ccb..27d4bcddea0 100644 --- a/compiler/container/container.iml +++ b/compiler/container/container.iml @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt b/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt index a634e9070c6..978eb36c077 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt @@ -42,7 +42,8 @@ fun Class<*>.getInfo(): ClassInfo { data class ClassInfo( val constructorInfo: ConstructorInfo?, val setterInfos: List, - val registrations: List + val registrations: List, + val defaultImplementation: Class<*>? ) data class ConstructorInfo( @@ -56,7 +57,7 @@ data class SetterInfo( ) private fun traverseClass(c: Class<*>): ClassInfo { - return ClassInfo(getConstructorInfo(c), getSetterInfos(c), getRegistrations(c)) + return ClassInfo(getConstructorInfo(c), getSetterInfos(c), getRegistrations(c), getDefaultImplementation(c)) } private fun getSetterInfos(c: Class<*>): List { @@ -99,6 +100,10 @@ private fun collectInterfacesRecursive(type: Type, result: MutableSet) { } } +private fun getDefaultImplementation(klass: Class<*>): Class<*>? { + return klass.getAnnotation(DefaultImplementation::class.java)?.impl?.java +} + private fun getRegistrations(klass: Class<*>): List { val registrations = ArrayList() diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Components.kt b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt index 0f431005938..3013a80baf1 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Components.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt @@ -18,7 +18,7 @@ package org.jetbrains.kotlin.container import java.lang.reflect.* -class InstanceComponentDescriptor(val instance: Any) : ComponentDescriptor { +open class InstanceComponentDescriptor(val instance: Any) : ComponentDescriptor { override fun getValue(): Any = instance override fun getRegistrations(): Iterable = instance::class.java.getInfo().registrations @@ -28,4 +28,8 @@ class InstanceComponentDescriptor(val instance: Any) : ComponentDescriptor { override fun toString(): String { return "Instance: ${instance::class.java.simpleName}" } +} + +class DefaultInstanceComponentDescriptor(instance: Any): InstanceComponentDescriptor(instance) { + override fun toString() = "Default instance: ${instance.javaClass.simpleName}" } \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt index d5ec9b63190..0561875cd41 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt @@ -148,4 +148,10 @@ class ImplicitSingletonTypeComponentDescriptor(container: ComponentContainer, kl override fun toString(): String { return "Implicit: ${klass.simpleName}" } +} + +class DefaultSingletonTypeComponentDescriptor(container: ComponentContainer, klass: Class<*>) : SingletonTypeComponentDescriptor(container, klass) { + override fun toString(): String { + return "Default: ${klass.simpleName}" + } } \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt index 99d85c859a5..7f88e8bcfbd 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt @@ -161,18 +161,32 @@ class ComponentStorage(val myId: String, parent: ComponentStorage?) : ValueResol is ParameterizedType -> type.rawType as? Class<*> else -> null } - if (rawType == null) - continue - if (!Modifier.isAbstract(rawType.modifiers) && !rawType.isPrimitive) { - val implicitDescriptor = ImplicitSingletonTypeComponentDescriptor(context.container, rawType) - adhocDescriptors.add(implicitDescriptor) - collectAdhocComponents(context, implicitDescriptor, visitedTypes, adhocDescriptors) - } + val implicitDependency = rawType?.let { getImplicitlyDefinedDependency(context, it) } ?: continue + + adhocDescriptors.add(implicitDependency) + collectAdhocComponents(context, implicitDependency, visitedTypes, adhocDescriptors) } } } + private fun getImplicitlyDefinedDependency(context: ComponentResolveContext, rawType: Class<*>): ComponentDescriptor? { + if (!Modifier.isAbstract(rawType.modifiers) && !rawType.isPrimitive) { + return ImplicitSingletonTypeComponentDescriptor(context.container, rawType) + } + + val defaultImplementation = rawType.getInfo().defaultImplementation + if (defaultImplementation != null && defaultImplementation.getInfo().constructorInfo != null) { + return DefaultSingletonTypeComponentDescriptor(context.container, defaultImplementation) + } + + if (defaultImplementation != null) { + return defaultImplementation.getField("INSTANCE")?.get(null)?.let(::DefaultInstanceComponentDescriptor) + } + + return null + } + private fun injectProperties(instance: Any, context: ValueResolveContext) { val classInfo = instance::class.java.getInfo() diff --git a/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt b/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt index 67ba115a351..07c292da753 100644 --- a/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt +++ b/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt @@ -311,4 +311,51 @@ class ComponentContainerTest { assertTrue(a === bc1.get().a) assertTrue(a === bc1.get()) } + + @DefaultImplementation(impl = Impl::class) + abstract class I + + class Impl : I() + + class Impl2: I() + + class Use(val i: I) + + @Test + fun default_implementation() { + class Use(val i: I) + + val u = composeContainer("a") { + useImpl() + }.get() + + assertTrue(u.i is Impl) + } + + @Test + fun non_default_implementation() { + val ac = composeContainer("a") { + useImpl() + useImpl() + } + + val u = ac.get() + assertTrue(u.i is Impl2) + } + + @DefaultImplementation(impl = A::class) + interface S + + object A: S + + class UseS(val s: S) + + @Test + fun test_default_object() { + val useS = composeContainer("s") { + useImpl() + }.get() + + assertTrue(useS.s === A) + } } \ No newline at end of file diff --git a/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt b/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt new file mode 100644 index 00000000000..cab9fde5e5e --- /dev/null +++ b/core/util.runtime/src/org/jetbrains/kotlin/container/DefaultImplementation.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.container + +import kotlin.reflect.KClass + +/*Use to assist injection to provide a default implementation for a certain component and reduce boilerplate in injector code. +* Argument class must be a non-abstract component class or a kotlin object implementing target interface. +* Avoid using when there is no clear 'default' behaviour for a component. +* */ +annotation class DefaultImplementation(val impl: KClass<*>) \ No newline at end of file