Implement injection of Iterable<T> and some improvements in generic handling in general

This commit is contained in:
Ilya Ryzhenkov
2015-07-14 20:32:18 +03:00
committed by Pavel V. Talanov
parent 0c41f4f736
commit 07139879cd
10 changed files with 252 additions and 105 deletions
@@ -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<SetterInfo>,
val registrations: List<Class<*>>
val registrations: List<Type>
)
data class ConstructorInfo(
val constructor: Constructor<*>,
val parameters: List<Class<*>>
val parameters: List<Type>
)
data class SetterInfo(
val method: Method,
val parameters: List<Class<*>>
val parameters: List<Type>
)
private fun traverseClass(c: Class<*>): ClassInfo {
@@ -66,7 +64,7 @@ private fun getSetterInfos(c: Class<*>): List<SetterInfo> {
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<Class<*>>) {
cl.getInterfaces().forEach {
private fun collectInterfacesRecursive(type: Type, result: MutableSet<Type>) {
// 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<Class<*>> {
val registrations = ArrayList<Class<*>>()
private fun getRegistrations(klass: Class<*>): List<Type> {
val registrations = ArrayList<Type>()
val superClasses = sequence(klass) { (it as Class<Any>).getSuperclass() }
val superClasses = sequence<Type>(klass) {
when (it) {
is Class<*> -> it.getGenericSuperclass()
is ParameterizedType -> (it.getRawType() as? Class<*>)?.getGenericSuperclass()
else -> null
}
}
registrations.addAll(superClasses)
val interfaces = LinkedHashSet<Class<*>>()
val interfaces = LinkedHashSet<Type>()
superClasses.forEach { collectInterfacesRecursive(it, interfaces) }
registrations.addAll(interfaces)
registrations.remove(javaClass<Any>())
@@ -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<Class<*>> = instance.javaClass.getInfo().registrations
override fun getRegistrations(): Iterable<Type> = instance.javaClass.getInfo().registrations
override fun getDependencies(context: ValueResolveContext): Collection<Class<*>> = emptyList()
}
@@ -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<Iterable<*>>()) 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<ValueDescriptor> {
@@ -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<Class<*>>
fun getDependencies(context: ValueResolveContext): Collection<Class<*>>
fun getRegistrations(): Iterable<Type>
fun getDependencies(context: ValueResolveContext): Collection<Type>
val shouldInjectProperties: Boolean
get() = false
}
public class IterableDescriptor(val descriptors: Iterable<ValueDescriptor>) : ValueDescriptor {
override fun getValue(): Any {
return descriptors.map { it.getValue() }
}
}
@@ -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<ComponentDescriptor>): MultiMap<Class<*>, ComponentDescriptor> {
val registrationMap = MultiMap<Class<*>, ComponentDescriptor>()
fun buildRegistrationMap(descriptors: Collection<ComponentDescriptor>): MultiMap<Type, ComponentDescriptor> {
val registrationMap = MultiMap<Type, ComponentDescriptor>()
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<Class<*>, ComponentDescriptor>()
private var registrationMap = MultiMap.createLinkedSet<Type, ComponentDescriptor>()
public fun addAll(descriptors: Collection<ComponentDescriptor>) {
registrationMap.putAllValues(buildRegistrationMap(descriptors))
}
public fun tryGetEntry(request: Class<*>): Collection<ComponentDescriptor> {
public fun tryGetEntry(request: Type): Collection<ComponentDescriptor> {
return registrationMap.get(request)
}
}
@@ -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<Class<*>>, context: ValueResolveContext): List<ValueDescriptor> {
private fun Member.bindArguments(parameters: List<Type>, context: ValueResolveContext): List<ValueDescriptor> {
val bound = ArrayList<ValueDescriptor>(parameters.size())
var unsatisfied: MutableList<Type>? = null
@@ -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<Class<*>> = klass.getInfo().registrations
public override fun getRegistrations(): Iterable<Type> = 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<Class<*>> {
override fun getDependencies(context: ValueResolveContext): Collection<Type> {
val classInfo = klass.getInfo()
return classInfo.constructorInfo?.parameters.orEmpty() + classInfo.setterInfos.flatMap { it.parameters }
}
@@ -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<ComponentDescriptor>) : Exception(message)
public class ComponentStorage(val myId: String) : ValueResolver {
var state = ComponentStorageState.Initial
val registry = ComponentRegistry()
val descriptors = LinkedHashSet<ComponentDescriptor>()
val dependencies = MultiMap.createLinkedSet<ComponentDescriptor, Class<*>>()
val dependencies = MultiMap.createLinkedSet<ComponentDescriptor, Type>()
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<ValueDescriptor> {
public fun resolveMultiple(request: Type, context: ValueResolveContext): Iterable<ValueDescriptor> {
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<ComponentDescriptor>): LinkedHashSet<ComponentDescriptor> {
val implicits = LinkedHashSet<ComponentDescriptor>()
val visitedTypes = HashSet<Class<*>>()
private fun inspectDependenciesAndRegisterAdhoc(context: ComponentResolveContext, descriptors: Collection<ComponentDescriptor>): LinkedHashSet<ComponentDescriptor> {
val adhoc = LinkedHashSet<ComponentDescriptor>()
val visitedTypes = HashSet<Type>()
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<Class<*>>, implicitDescriptors: LinkedHashSet<ComponentDescriptor>
private fun collectAdhocComponents(context: ComponentResolveContext, descriptor: ComponentDescriptor,
visitedTypes: HashSet<Type>, adhocDescriptors: LinkedHashSet<ComponentDescriptor>
) {
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)
}
}
}
@@ -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<TestAdhocComponent1>()
useImpl<TestAdhocComponent2>()
}.use {
val descriptor1 = it.resolve<TestAdhocComponent1>()
assertNotNull(descriptor1)
val descriptor2 = it.resolve<TestAdhocComponent2>()
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<TestComponent>()
useImpl<TestClientComponent>()
useImpl<TestClientComponent2>()
useImpl<TestIterableComponent>()
}.use {
val descriptor = it.resolve<TestIterableComponent>()
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<TestGenericClient>()
useImpl<TestStringComponent>()
useImpl<TestIntComponent>()
}.use {
val descriptor = it.resolve<TestGenericClient>()
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<TestImplicitGenericClient>()
}.use {
val descriptor = it.resolve<TestImplicitGenericClient>()
assertNotNull(descriptor)
val genericClient = descriptor!!.getValue() as TestImplicitGenericClient
assertTrue(genericClient.component is TestImplicitGeneric)
}
}
Test fun should_fail_with_invalid_cardinality() {
createContainer("test") {
useImpl<TestComponent>()
useInstance(TestComponent())
}.use {
assertTrue {
fails {
it.resolve<TestComponent>()
} is InvalidCardinalityException
}
}
}
class WithSetters {
var isSetterCalled = false
@@ -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<TestClientComponentInterface>)
interface TestGenericComponent<T>
class TestGenericClient(val component1 : TestGenericComponent<String>, val component2: TestGenericComponent<Int>)
class TestStringComponent : TestGenericComponent<String>
class TestIntComponent : TestGenericComponent<Int>
class TestImplicitGeneric<T>()
class TestImplicitGenericClient(val component: TestImplicitGeneric<String>)