From 07139879cd78857969237946367920ae71345ef5 Mon Sep 17 00:00:00 2001 From: Ilya Ryzhenkov Date: Tue, 14 Jul 2015 20:32:18 +0300 Subject: [PATCH] Implement injection of Iterable and some improvements in generic handling in general --- .../org/jetbrains/kotlin/container/Cache.kt | 38 +++--- .../jetbrains/kotlin/container/Components.kt | 4 +- .../jetbrains/kotlin/container/Container.kt | 32 ++--- .../jetbrains/kotlin/container/Descriptors.kt | 11 +- .../jetbrains/kotlin/container/Registry.kt | 9 +- .../org/jetbrains/kotlin/container/Resolve.kt | 10 +- .../jetbrains/kotlin/container/Singletons.kt | 5 +- .../org/jetbrains/kotlin/container/Storage.kt | 52 +++++--- .../container/tests/ComponentContainerTest.kt | 114 +++++++++++------- .../kotlin/container/tests/TestComponents.kt | 82 +++++++++++++ 10 files changed, 252 insertions(+), 105 deletions(-) create mode 100644 compiler/container/tests/org/jetbrains/kotlin/container/tests/TestComponents.kt diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt b/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt index 73e78d10f93..bd8d2422fd4 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt @@ -17,9 +17,7 @@ package org.jetbrains.kotlin.container import com.intellij.util.containers.ContainerUtil -import java.lang.reflect.Constructor -import java.lang.reflect.Method -import java.lang.reflect.Modifier +import java.lang.reflect.* import java.util.ArrayList import java.util.LinkedHashSet @@ -44,17 +42,17 @@ fun Class<*>.getInfo(): ClassInfo { data class ClassInfo( val constructorInfo: ConstructorInfo?, val setterInfos: List, - val registrations: List> + val registrations: List ) data class ConstructorInfo( val constructor: Constructor<*>, - val parameters: List> + val parameters: List ) data class SetterInfo( val method: Method, - val parameters: List> + val parameters: List ) private fun traverseClass(c: Class<*>): ClassInfo { @@ -66,7 +64,7 @@ private fun getSetterInfos(c: Class<*>): List { for (method in c.getMethods()) { for (annotation in method.getDeclaredAnnotations()) { if (annotation.annotationType().getName().endsWith(".Inject")) { - setterInfos.add(SetterInfo(method, method.getParameterTypes().toList())) + setterInfos.add(SetterInfo(method, method.getGenericParameterTypes().toList())) } } } @@ -83,25 +81,37 @@ private fun getConstructorInfo(c: Class<*>): ConstructorInfo? { return null val constructor = constructors.single() - return ConstructorInfo(constructor, constructor.getParameterTypes().toList()) + return ConstructorInfo(constructor, constructor.getGenericParameterTypes().toList()) } -private fun collectInterfacesRecursive(cl: Class<*>, result: MutableSet>) { - cl.getInterfaces().forEach { +private fun collectInterfacesRecursive(type: Type, result: MutableSet) { + // TODO: should apply generic substitution through hierarchy + val klass : Class<*>? = when(type) { + is Class<*> -> type + is ParameterizedType -> type.getRawType() as? Class<*> + else -> null + } + klass?.getGenericInterfaces()?.forEach { if (result.add(it)) { collectInterfacesRecursive(it, result) } } } -private fun getRegistrations(klass: Class<*>): List> { - val registrations = ArrayList>() +private fun getRegistrations(klass: Class<*>): List { + val registrations = ArrayList() - val superClasses = sequence(klass) { (it as Class).getSuperclass() } + val superClasses = sequence(klass) { + when (it) { + is Class<*> -> it.getGenericSuperclass() + is ParameterizedType -> (it.getRawType() as? Class<*>)?.getGenericSuperclass() + else -> null + } + } registrations.addAll(superClasses) - val interfaces = LinkedHashSet>() + val interfaces = LinkedHashSet() superClasses.forEach { collectInterfacesRecursive(it, interfaces) } registrations.addAll(interfaces) registrations.remove(javaClass()) diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Components.kt b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt index 13cb5037d1f..67ac04e9e7e 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Components.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt @@ -16,12 +16,12 @@ package org.jetbrains.kotlin.container -import java.lang.reflect.Method +import java.lang.reflect.* public class InstanceComponentDescriptor(val instance: Any) : ComponentDescriptor { override fun getValue(): Any = instance - override fun getRegistrations(): Iterable> = instance.javaClass.getInfo().registrations + override fun getRegistrations(): Iterable = instance.javaClass.getInfo().registrations override fun getDependencies(context: ValueResolveContext): Collection> = emptyList() } diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Container.kt b/compiler/container/src/org/jetbrains/kotlin/container/Container.kt index 80dc41f2060..26942661084 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Container.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Container.kt @@ -17,7 +17,9 @@ package org.jetbrains.kotlin.container import java.io.Closeable -import java.lang.reflect.Modifier +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.lang.reflect.WildcardType import kotlin.properties.Delegates class ContainerConsistencyException(message: String) : Exception(message) @@ -49,20 +51,22 @@ public class StorageComponentContainer(id: String) : ComponentContainer, Closeab override fun close() = componentStorage.dispose() - jvmOverloads public fun resolve(request: Class<*>, context: ValueResolveContext = unknownContext): ValueDescriptor? { - val storageResolve = componentStorage.resolve(request, context) - if (storageResolve != null) - return storageResolve + jvmOverloads public fun resolve(request: Type, context: ValueResolveContext = unknownContext): ValueDescriptor? { + return componentStorage.resolve(request, context) ?: resolveIterable(request, context) + } - val hasSinglePublicConstructor = request.getConstructors().singleOrNull()?.let { Modifier.isPublic(it.getModifiers()) } ?: false - if (!hasSinglePublicConstructor) - return null - - val modifiers = request.getModifiers() - if (Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers) || request.isPrimitive()) - return null - - return SingletonTypeComponentDescriptor(this, request) + private fun resolveIterable(request: Type, context: ValueResolveContext): ValueDescriptor? { + if (request !is ParameterizedType) return null + val rawType = request.getRawType() + if (rawType != javaClass>()) return null + val typeArguments = request.getActualTypeArguments() + if (typeArguments.size() != 1) return null + val iterableTypeArgument = typeArguments[0] + if (!(iterableTypeArgument is WildcardType)) return null + val upperBounds = iterableTypeArgument.getUpperBounds() + if (upperBounds.size() != 1) return null + val iterableType = upperBounds[0] + return IterableDescriptor(componentStorage.resolveMultiple(iterableType, context)) } public fun resolveMultiple(request: Class<*>, context: ValueResolveContext = unknownContext): Iterable { diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt b/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt index def0d70e14a..5bf625f999f 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt @@ -16,14 +16,21 @@ package org.jetbrains.kotlin.container +import java.lang.reflect.* public interface ValueDescriptor { public fun getValue(): Any } internal interface ComponentDescriptor : ValueDescriptor { - fun getRegistrations(): Iterable> - fun getDependencies(context: ValueResolveContext): Collection> + fun getRegistrations(): Iterable + fun getDependencies(context: ValueResolveContext): Collection val shouldInjectProperties: Boolean get() = false +} + +public class IterableDescriptor(val descriptors: Iterable) : ValueDescriptor { + override fun getValue(): Any { + return descriptors.map { it.getValue() } + } } \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt index 576285c0aeb..bb94c2517ae 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt @@ -18,10 +18,11 @@ package org.jetbrains.kotlin.container import com.intellij.util.containers.MultiMap import java.util.ArrayList +import java.lang.reflect.* internal class ComponentRegistry { - fun buildRegistrationMap(descriptors: Collection): MultiMap, ComponentDescriptor> { - val registrationMap = MultiMap, ComponentDescriptor>() + fun buildRegistrationMap(descriptors: Collection): MultiMap { + val registrationMap = MultiMap() for (descriptor in descriptors) { for (registration in descriptor.getRegistrations()) { registrationMap.putValue(registration, descriptor) @@ -30,13 +31,13 @@ internal class ComponentRegistry { return registrationMap } - private var registrationMap = MultiMap.createLinkedSet, ComponentDescriptor>() + private var registrationMap = MultiMap.createLinkedSet() public fun addAll(descriptors: Collection) { registrationMap.putAllValues(buildRegistrationMap(descriptors)) } - public fun tryGetEntry(request: Class<*>): Collection { + public fun tryGetEntry(request: Type): Collection { return registrationMap.get(request) } } \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Resolve.kt b/compiler/container/src/org/jetbrains/kotlin/container/Resolve.kt index 96ffd0ea785..62dfe8bd926 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Resolve.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Resolve.kt @@ -23,15 +23,15 @@ import java.lang.reflect.Type import java.util.ArrayList public interface ValueResolver { - fun resolve(request: Class<*>, context: ValueResolveContext): ValueDescriptor? + fun resolve(request: Type, context: ValueResolveContext): ValueDescriptor? } public interface ValueResolveContext { - fun resolve(registration: Class<*>): ValueDescriptor? + fun resolve(registration: Type): ValueDescriptor? } internal class ComponentResolveContext(val container: StorageComponentContainer, val requestingDescriptor: ValueDescriptor) : ValueResolveContext { - override fun resolve(registration: Class<*>): ValueDescriptor? = container.resolve(registration, this) + override fun resolve(registration: Type): ValueDescriptor? = container.resolve(registration, this) override fun toString(): String = "for $requestingDescriptor in $container" } @@ -54,10 +54,10 @@ fun Class<*>.bindToConstructor(context: ValueResolveContext): ConstructorBinding } fun Method.bindToMethod(context: ValueResolveContext): MethodBinding { - return MethodBinding(this, bindArguments(getParameterTypes().toList(), context)) + return MethodBinding(this, bindArguments(getGenericParameterTypes().toList(), context)) } -private fun Member.bindArguments(parameters: List>, context: ValueResolveContext): List { +private fun Member.bindArguments(parameters: List, context: ValueResolveContext): List { val bound = ArrayList(parameters.size()) var unsatisfied: MutableList? = null diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt index 724f1aa8b85..8593baeea75 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.container import java.io.Closeable import java.util.ArrayList import kotlin.properties.Delegates +import java.lang.reflect.* enum class ComponentState { Null, @@ -116,7 +117,7 @@ public abstract class SingletonDescriptor(val container: ComponentContainer) : C } public abstract class SingletonComponentDescriptor(container: ComponentContainer, val klass: Class<*>) : SingletonDescriptor(container) { - public override fun getRegistrations(): Iterable> = klass.getInfo().registrations + public override fun getRegistrations(): Iterable = klass.getInfo().registrations } public class SingletonTypeComponentDescriptor(container: ComponentContainer, klass: Class<*>) : SingletonComponentDescriptor(container, klass) { @@ -139,7 +140,7 @@ public class SingletonTypeComponentDescriptor(container: ComponentContainer, kla return instance } - override fun getDependencies(context: ValueResolveContext): Collection> { + override fun getDependencies(context: ValueResolveContext): Collection { val classInfo = klass.getInfo() return classInfo.constructorInfo?.parameters.orEmpty() + classInfo.setterInfos.flatMap { it.parameters } } diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt index 94cdc266321..8c39aa62a4d 100644 --- a/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt +++ b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt @@ -19,6 +19,8 @@ package org.jetbrains.kotlin.container import com.intellij.util.containers.MultiMap import java.io.Closeable import java.lang.reflect.Modifier +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type import java.util.ArrayList import java.util.HashSet import java.util.LinkedHashSet @@ -30,13 +32,15 @@ public enum class ComponentStorageState { Disposed } +class InvalidCardinalityException(message: String, val descriptors: Collection) : Exception(message) + public class ComponentStorage(val myId: String) : ValueResolver { var state = ComponentStorageState.Initial val registry = ComponentRegistry() val descriptors = LinkedHashSet() - val dependencies = MultiMap.createLinkedSet>() + val dependencies = MultiMap.createLinkedSet() - override fun resolve(request: Class<*>, context: ValueResolveContext): ValueDescriptor? { + override fun resolve(request: Type, context: ValueResolveContext): ValueDescriptor? { if (state == ComponentStorageState.Initial) throw ContainerConsistencyException("Container was not composed before resolving") @@ -44,12 +48,14 @@ public class ComponentStorage(val myId: String) : ValueResolver { if (entry.isNotEmpty()) { registerDependency(request, context) + if (entry.size() > 1) + throw InvalidCardinalityException("Request $request cannot be satisfied because there is more than one type registered", entry) return entry.singleOrNull() } return null } - private fun registerDependency(request: Class<*>, context: ValueResolveContext) { + private fun registerDependency(request: Type, context: ValueResolveContext) { if (context is ComponentResolveContext) { val descriptor = context.requestingDescriptor if (descriptor is ComponentDescriptor) { @@ -58,7 +64,7 @@ public class ComponentStorage(val myId: String) : ValueResolver { } } - public fun resolveMultiple(request: Class<*>, context: ValueResolveContext): Iterable { + public fun resolveMultiple(request: Type, context: ValueResolveContext): Iterable { registerDependency(request, context) return registry.tryGetEntry(request) } @@ -89,7 +95,7 @@ public class ComponentStorage(val myId: String) : ValueResolver { registry.addAll(descriptors) - val implicits = inspectDependenciesAndRegisterImplicits(context, descriptors) + val implicits = inspectDependenciesAndRegisterAdhoc(context, descriptors) injectProperties(context, descriptors + implicits) } @@ -102,30 +108,38 @@ public class ComponentStorage(val myId: String) : ValueResolver { } } - private fun inspectDependenciesAndRegisterImplicits(context: ComponentResolveContext, descriptors: Collection): LinkedHashSet { - val implicits = LinkedHashSet() - val visitedTypes = HashSet>() + private fun inspectDependenciesAndRegisterAdhoc(context: ComponentResolveContext, descriptors: Collection): LinkedHashSet { + val adhoc = LinkedHashSet() + val visitedTypes = HashSet() for (descriptor in descriptors) { - registerImplicits(context, descriptor, visitedTypes, implicits) + collectAdhocComponents(context, descriptor, visitedTypes, adhoc) } - registry.addAll(implicits) - return implicits + registry.addAll(adhoc) + return adhoc } - private fun registerImplicits( - context: ComponentResolveContext, descriptor: ComponentDescriptor, - visitedTypes: HashSet>, implicitDescriptors: LinkedHashSet + private fun collectAdhocComponents(context: ComponentResolveContext, descriptor: ComponentDescriptor, + visitedTypes: HashSet, adhocDescriptors: LinkedHashSet ) { val dependencies = descriptor.getDependencies(context) for (type in dependencies) { - if (!visitedTypes.add(type)) continue + if (!visitedTypes.add(type)) + continue val entry = registry.tryGetEntry(type) if (entry.isEmpty()) { - if (!Modifier.isAbstract(type.getModifiers()) && !type.isPrimitive()) { - val implicitDescriptor = SingletonTypeComponentDescriptor(context.container, type) - implicitDescriptors.add(implicitDescriptor) - registerImplicits(context, implicitDescriptor, visitedTypes, implicitDescriptors) + val rawType : Class<*>? = when(type){ + is Class<*> -> type + is ParameterizedType -> type.getRawType() as? Class<*> + else -> null + } + if (rawType == null) + continue + + if (!Modifier.isAbstract(rawType.getModifiers()) && !rawType.isPrimitive()) { + val implicitDescriptor = SingletonTypeComponentDescriptor(context.container, rawType) + adhocDescriptors.add(implicitDescriptor) + collectAdhocComponents(context, implicitDescriptor, visitedTypes, adhocDescriptors) } } } 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 2aead72b9f5..cb617b7da4f 100644 --- a/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt +++ b/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt @@ -17,53 +17,11 @@ package org.jetbrains.kotlin.container.tests import org.jetbrains.kotlin.container.* +import org.junit.Ignore import org.junit.Test -import java.io.Closeable import javax.inject.Inject import kotlin.test.* -interface TestComponentInterface { - public val disposed: Boolean - fun foo() -} - -interface TestClientComponentInterface - -class TestComponent : TestComponentInterface, Closeable { - public override var disposed: Boolean = false - override fun close() { - disposed = true - } - - override fun foo() { - throw UnsupportedOperationException() - } -} - -class ManualTestComponent(val name: String) : TestComponentInterface, Closeable { - public override var disposed: Boolean = false - override fun close() { - disposed = true - } - - override fun foo() { - throw UnsupportedOperationException() - } -} - -class TestClientComponent(val dep: TestComponentInterface) : TestClientComponentInterface, Closeable { - override fun close() { - if (dep.disposed) - throw Exception("Dependency shouldn't be disposed before dependee") - disposed = true - } - - var disposed: Boolean = false -} - -class TestClientComponent2() : TestClientComponentInterface { -} - class ComponentContainerTest { Test fun should_throw_when_not_composed() { val container = StorageComponentContainer("test") @@ -155,6 +113,76 @@ class ComponentContainerTest { } } + Test fun should_resolve_adhoc_types_to_same_instances() { + createContainer("test") { + useImpl() + useImpl() + }.use { + val descriptor1 = it.resolve() + assertNotNull(descriptor1) + val descriptor2 = it.resolve() + assertNotNull(descriptor2) + val component1 = descriptor1!!.getValue() as TestAdhocComponent1 + val component2 = descriptor2!!.getValue() as TestAdhocComponent2 + assertTrue(component1.service === component2.service) + } + } + + Test fun should_resolve_iterable() { + createContainer("test") { + useImpl() + useImpl() + useImpl() + useImpl() + }.use { + val descriptor = it.resolve() + assertNotNull(descriptor) + val iterableComponent = descriptor!!.getValue() as TestIterableComponent + assertEquals(2, iterableComponent.components.count()) + assertTrue(iterableComponent.components.any { it is TestClientComponent }) + assertTrue(iterableComponent.components.any { it is TestClientComponent2 }) + } + } + + Test fun should_distinguish_generic() { + createContainer("test") { + useImpl() + useImpl() + useImpl() + }.use { + val descriptor = it.resolve() + assertNotNull(descriptor) + val genericClient = descriptor!!.getValue() as TestGenericClient + assertTrue(genericClient.component1 is TestStringComponent) + assertTrue(genericClient.component2 is TestIntComponent) + } + } + + Ignore("Need generic type substitution") + Test fun should_resolve_generic_adhoc() { + createContainer("test") { + useImpl() + }.use { + val descriptor = it.resolve() + assertNotNull(descriptor) + val genericClient = descriptor!!.getValue() as TestImplicitGenericClient + assertTrue(genericClient.component is TestImplicitGeneric) + } + } + + Test fun should_fail_with_invalid_cardinality() { + createContainer("test") { + useImpl() + useInstance(TestComponent()) + }.use { + assertTrue { + fails { + it.resolve() + } is InvalidCardinalityException + } + } + } + class WithSetters { var isSetterCalled = false diff --git a/compiler/container/tests/org/jetbrains/kotlin/container/tests/TestComponents.kt b/compiler/container/tests/org/jetbrains/kotlin/container/tests/TestComponents.kt new file mode 100644 index 00000000000..bbeb3b452f8 --- /dev/null +++ b/compiler/container/tests/org/jetbrains/kotlin/container/tests/TestComponents.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2015 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.tests + +import java.io.* + +interface TestComponentInterface { + public val disposed: Boolean + fun foo() +} + +interface TestClientComponentInterface { +} + +class TestComponent : TestComponentInterface, Closeable { + public override var disposed: Boolean = false + override fun close() { + disposed = true + } + + override fun foo() { + throw UnsupportedOperationException() + } +} + +class ManualTestComponent(val name: String) : TestComponentInterface, Closeable { + public override var disposed: Boolean = false + override fun close() { + disposed = true + } + + override fun foo() { + throw UnsupportedOperationException() + } +} + +class TestClientComponent(val dep: TestComponentInterface) : TestClientComponentInterface, Closeable { + override fun close() { + if (dep.disposed) + throw Exception("Dependency shouldn't be disposed before dependee") + disposed = true + } + + var disposed: Boolean = false +} + +class TestClientComponent2() : TestClientComponentInterface { +} + +class TestAdhocComponentService +class TestAdhocComponent1(val service: TestAdhocComponentService) { + +} + +class TestAdhocComponent2(val service: TestAdhocComponentService) { + +} + +class TestIterableComponent(val components: Iterable) + +interface TestGenericComponent + +class TestGenericClient(val component1 : TestGenericComponent, val component2: TestGenericComponent) +class TestStringComponent : TestGenericComponent +class TestIntComponent : TestGenericComponent + +class TestImplicitGeneric() +class TestImplicitGenericClient(val component: TestImplicitGeneric)