diff --git a/.idea/artifacts/KotlinPlugin.xml b/.idea/artifacts/KotlinPlugin.xml index e381b0ad678..3476cd93123 100644 --- a/.idea/artifacts/KotlinPlugin.xml +++ b/.idea/artifacts/KotlinPlugin.xml @@ -43,6 +43,7 @@ + diff --git a/.idea/modules.xml b/.idea/modules.xml index a52397b1f41..058cfbe2787 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -18,6 +18,7 @@ + diff --git a/build.xml b/build.xml index e93c0c00a22..f5d3c5442ae 100644 --- a/build.xml +++ b/build.xml @@ -87,6 +87,7 @@ + @@ -105,6 +106,7 @@ + @@ -199,6 +201,7 @@ + diff --git a/compiler/container/container.iml b/compiler/container/container.iml new file mode 100644 index 00000000000..c23b54ffa87 --- /dev/null +++ b/compiler/container/container.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ 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 new file mode 100644 index 00000000000..73e78d10f93 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Cache.kt @@ -0,0 +1,109 @@ +/* + * 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 + +import com.intellij.util.containers.ContainerUtil +import java.lang.reflect.Constructor +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.util.ArrayList +import java.util.LinkedHashSet + +private object ClassTraversalCache { + private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap, ClassInfo>() + + fun getClassInfo(c: Class<*>): ClassInfo { + val classInfo = cache.get(c) + if (classInfo == null) { + val newClassInfo = traverseClass(c) + cache.put(c, newClassInfo) + return newClassInfo + } + return classInfo + } +} + +fun Class<*>.getInfo(): ClassInfo { + return ClassTraversalCache.getClassInfo(this) +} + +data class ClassInfo( + val constructorInfo: ConstructorInfo?, + val setterInfos: List, + val registrations: List> +) + +data class ConstructorInfo( + val constructor: Constructor<*>, + val parameters: List> +) + +data class SetterInfo( + val method: Method, + val parameters: List> +) + +private fun traverseClass(c: Class<*>): ClassInfo { + return ClassInfo(getConstructorInfo(c), getSetterInfos(c), getRegistrations(c)) +} + +private fun getSetterInfos(c: Class<*>): List { + val setterInfos = ArrayList() + for (method in c.getMethods()) { + for (annotation in method.getDeclaredAnnotations()) { + if (annotation.annotationType().getName().endsWith(".Inject")) { + setterInfos.add(SetterInfo(method, method.getParameterTypes().toList())) + } + } + } + return setterInfos +} + +private fun getConstructorInfo(c: Class<*>): ConstructorInfo? { + if (Modifier.isAbstract(c.getModifiers()) || c.isPrimitive()) + return null + + val constructors = c.getConstructors() + val hasSinglePublicConstructor = constructors.singleOrNull()?.let { Modifier.isPublic(it.getModifiers()) } ?: false + if (!hasSinglePublicConstructor) + return null + + val constructor = constructors.single() + return ConstructorInfo(constructor, constructor.getParameterTypes().toList()) +} + + +private fun collectInterfacesRecursive(cl: Class<*>, result: MutableSet>) { + cl.getInterfaces().forEach { + if (result.add(it)) { + collectInterfacesRecursive(it, result) + } + } +} + +private fun getRegistrations(klass: Class<*>): List> { + val registrations = ArrayList>() + + val superClasses = sequence(klass) { (it as Class).getSuperclass() } + registrations.addAll(superClasses) + + val interfaces = LinkedHashSet>() + superClasses.forEach { collectInterfacesRecursive(it, interfaces) } + registrations.addAll(interfaces) + registrations.remove(javaClass()) + return registrations +} \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Components.kt b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt new file mode 100644 index 00000000000..13cb5037d1f --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Components.kt @@ -0,0 +1,28 @@ +/* + * 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 + +import java.lang.reflect.Method + +public class InstanceComponentDescriptor(val instance: Any) : ComponentDescriptor { + + override fun getValue(): Any = instance + 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 new file mode 100644 index 00000000000..80dc41f2060 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Container.kt @@ -0,0 +1,92 @@ +/* + * 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 + +import java.io.Closeable +import java.lang.reflect.Modifier +import kotlin.properties.Delegates + +class ContainerConsistencyException(message: String) : Exception(message) + +public interface ComponentContainer { + fun createResolveContext(requestingDescriptor: ValueDescriptor): ValueResolveContext +} + +object DynamicComponentDescriptor : ComponentDescriptor { + override fun getDependencies(context: ValueResolveContext): Collection> = throw UnsupportedOperationException() + override fun getRegistrations(): Iterable> = throw UnsupportedOperationException() + override fun getValue(): Any = throw UnsupportedOperationException() +} + +public class StorageComponentContainer(id: String) : ComponentContainer, Closeable { + public val unknownContext: ComponentResolveContext by Delegates.lazy { ComponentResolveContext(this, DynamicComponentDescriptor) } + val componentStorage = ComponentStorage(id) + + override fun createResolveContext(requestingDescriptor: ValueDescriptor): ValueResolveContext { + if (requestingDescriptor == DynamicComponentDescriptor) // cache unknown component descriptor + return unknownContext + return ComponentResolveContext(this, requestingDescriptor) + } + + fun compose(): StorageComponentContainer { + componentStorage.compose(unknownContext) + return this + } + + 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 + + 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) + } + + public fun resolveMultiple(request: Class<*>, context: ValueResolveContext = unknownContext): Iterable { + return componentStorage.resolveMultiple(request, context) + } + + public fun registerDescriptors(descriptors: List): StorageComponentContainer { + componentStorage.registerDescriptors(unknownContext, descriptors) + return this + } +} + +public fun StorageComponentContainer.registerSingleton(klass: Class<*>): StorageComponentContainer { + return registerDescriptors(listOf(SingletonTypeComponentDescriptor(this, klass))) +} + +public fun StorageComponentContainer.registerInstance(instance: Any): StorageComponentContainer { + return registerDescriptors(listOf(InstanceComponentDescriptor(instance))) +} + +public inline fun StorageComponentContainer.resolve(context: ValueResolveContext = unknownContext): ValueDescriptor? { + return resolve(javaClass(), context) +} + +public inline fun StorageComponentContainer.resolveMultiple(context: ValueResolveContext = unknownContext): Iterable { + return resolveMultiple(javaClass(), context) +} diff --git a/compiler/container/src/org/jetbrains/kotlin/container/DataStructures.kt b/compiler/container/src/org/jetbrains/kotlin/container/DataStructures.kt new file mode 100644 index 00000000000..34c51f56042 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/DataStructures.kt @@ -0,0 +1,51 @@ +/* + * 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 + +import java.util.ArrayList +import java.util.HashSet + +public fun topologicalSort(items: Iterable, dependencies: (T) -> Iterable): List { + val itemsInProgress = HashSet() + val completedItems = HashSet() + val result = ArrayList() + + fun DfsVisit(item: T) { + if (item in completedItems) + return + + if (item in itemsInProgress) + throw CycleInTopoSortException() + + itemsInProgress.add(item) + + for (dependency in dependencies(item)) { + DfsVisit(dependency) + } + + itemsInProgress.remove(item) + completedItems.add(item) + result.add(item) + } + + for (item in items) + DfsVisit(item) + + return result.reverse() +} + +public class CycleInTopoSortException : Exception() \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt b/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt new file mode 100644 index 00000000000..def0d70e14a --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Descriptors.kt @@ -0,0 +1,29 @@ +/* + * 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 + + +public interface ValueDescriptor { + public fun getValue(): Any +} + +internal interface ComponentDescriptor : ValueDescriptor { + fun getRegistrations(): Iterable> + fun getDependencies(context: ValueResolveContext): Collection> + val shouldInjectProperties: Boolean + get() = false +} \ No newline at end of file diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt new file mode 100644 index 00000000000..ce99ff345e2 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Dsl.kt @@ -0,0 +1,42 @@ +/* + * 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 + +import kotlin.properties.ReadOnlyProperty + +public fun createContainer(id: String, init: StorageComponentContainer.() -> Unit): StorageComponentContainer { + val c = StorageComponentContainer(id) + c.init() + c.compose() + return c +} + +public inline fun StorageComponentContainer.useImpl() { + registerSingleton(javaClass()) +} + +public inline fun StorageComponentContainer.get(): T { + return resolve(javaClass(), unknownContext)!!.getValue() as T +} + +public fun StorageComponentContainer.useInstance(instance: Any) { + registerInstance(instance) +} + +public inline fun StorageComponentContainer.get(thisRef: Any?, desc: PropertyMetadata): T { + return resolve(javaClass())!!.getValue() as T +} diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt new file mode 100644 index 00000000000..576285c0aeb --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Registry.kt @@ -0,0 +1,42 @@ +/* + * 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 + +import com.intellij.util.containers.MultiMap +import java.util.ArrayList + +internal class ComponentRegistry { + fun buildRegistrationMap(descriptors: Collection): MultiMap, ComponentDescriptor> { + val registrationMap = MultiMap, ComponentDescriptor>() + for (descriptor in descriptors) { + for (registration in descriptor.getRegistrations()) { + registrationMap.putValue(registration, descriptor) + } + } + return registrationMap + } + + private var registrationMap = MultiMap.createLinkedSet, ComponentDescriptor>() + + public fun addAll(descriptors: Collection) { + registrationMap.putAllValues(buildRegistrationMap(descriptors)) + } + + public fun tryGetEntry(request: Class<*>): 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 new file mode 100644 index 00000000000..96ffd0ea785 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Resolve.kt @@ -0,0 +1,81 @@ +/* + * 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 + +import java.lang.reflect.Constructor +import java.lang.reflect.Member +import java.lang.reflect.Method +import java.lang.reflect.Type +import java.util.ArrayList + +public interface ValueResolver { + fun resolve(request: Class<*>, context: ValueResolveContext): ValueDescriptor? +} + +public interface ValueResolveContext { + fun resolve(registration: Class<*>): ValueDescriptor? +} + +internal class ComponentResolveContext(val container: StorageComponentContainer, val requestingDescriptor: ValueDescriptor) : ValueResolveContext { + override fun resolve(registration: Class<*>): ValueDescriptor? = container.resolve(registration, this) + + override fun toString(): String = "for $requestingDescriptor in $container" +} + +public class ConstructorBinding(val constructor: Constructor<*>, val argumentDescriptors: List) + +public class MethodBinding(val method: Method, val argumentDescriptors: List) { + fun invoke(instance: Any) { + val arguments = computeArguments(argumentDescriptors).toTypedArray() + method.invoke(instance, *arguments) + } +} + +public fun computeArguments(argumentDescriptors: List): List = argumentDescriptors.map { it.getValue() } + +fun Class<*>.bindToConstructor(context: ValueResolveContext): ConstructorBinding { + val constructorInfo = getInfo().constructorInfo!! + val candidate = constructorInfo.constructor + return ConstructorBinding(candidate, candidate.bindArguments(constructorInfo.parameters, context)) +} + +fun Method.bindToMethod(context: ValueResolveContext): MethodBinding { + return MethodBinding(this, bindArguments(getParameterTypes().toList(), context)) +} + +private fun Member.bindArguments(parameters: List>, context: ValueResolveContext): List { + val bound = ArrayList(parameters.size()) + var unsatisfied: MutableList? = null + + for (parameter in parameters) { + val descriptor = context.resolve(parameter) + if (descriptor == null) { + if (unsatisfied == null) + unsatisfied = ArrayList() + unsatisfied.add(parameter) + } + else { + bound.add(descriptor) + } + } + if (unsatisfied != null) { + throw UnresolvedDependenciesException("Dependencies for `$this` cannot be satisfied:\n $unsatisfied") + } + return bound +} + +class UnresolvedDependenciesException(message: String) : Exception(message) diff --git a/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt new file mode 100644 index 00000000000..724f1aa8b85 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Singletons.kt @@ -0,0 +1,146 @@ +/* + * 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 + +import java.io.Closeable +import java.util.ArrayList +import kotlin.properties.Delegates + +enum class ComponentState { + Null, + Initializing, + Initialized, + Corrupted, + Disposing, + Disposed +} + +public abstract class SingletonDescriptor(val container: ComponentContainer) : ComponentDescriptor, Closeable { + private var instance: Any? = null + protected var state: ComponentState = ComponentState.Null + private val disposableObjects by Delegates.lazy { ArrayList() } + + public override fun getValue(): Any { + when { + state == ComponentState.Corrupted -> throw ContainerConsistencyException("Component descriptor $this is corrupted and cannot be accessed") + state == ComponentState.Disposed -> throw ContainerConsistencyException("Component descriptor $this is disposed and cannot be accessed") + instance == null -> createInstance(container) + } + return instance!! + } + + protected fun registerDisposableObject(ownedObject: Closeable) { + disposableObjects.add(ownedObject) + } + + protected abstract fun createInstance(context: ValueResolveContext): Any + + private fun createInstance(container: ComponentContainer) { + when (state) { + ComponentState.Null -> { + try { + instance = createInstance(container.createResolveContext(this)) + return + } + catch (ex: Throwable) { + state = ComponentState.Corrupted + for (disposable in disposableObjects) + disposable.close() + throw ex + } + } + ComponentState.Initializing -> + throw ContainerConsistencyException("Could not create the component $this because it is being initialized. Do we have undetected circular dependency?") + ComponentState.Initialized -> + throw ContainerConsistencyException("Could not get the component $this. Instance is null in Initialized state") + ComponentState.Corrupted -> + throw ContainerConsistencyException("Could not get the component $this because it is corrupted") + ComponentState.Disposing -> + throw ContainerConsistencyException("Could not get the component $this because it is being disposed") + ComponentState.Disposed -> + throw ContainerConsistencyException("Could not get the component $this because it is already disposed") + } + } + + private fun disposeImpl() { + val wereInstance = instance + state = ComponentState.Disposing + instance = null // cannot get instance any more + try { + if (wereInstance is Closeable) + wereInstance.close() + for (disposable in disposableObjects) + disposable.close() + } + catch(ex: Throwable) { + state = ComponentState.Corrupted + throw ex + } + state = ComponentState.Disposed + } + + override fun close() { + when (state) { + ComponentState.Initialized -> + disposeImpl() + ComponentState.Corrupted -> { + } // corrupted component is in the undefined state, ignore + ComponentState.Null -> { + } // it's ok to to remove null component, it may have been never needed + + ComponentState.Initializing -> + throw ContainerConsistencyException("The component is being initialized and cannot be disposed.") + ComponentState.Disposing -> + throw ContainerConsistencyException("The component is already in disposing state.") + ComponentState.Disposed -> + throw ContainerConsistencyException("The component has already been destroyed.") + } + } + + override val shouldInjectProperties: Boolean + get() = true +} + +public abstract class SingletonComponentDescriptor(container: ComponentContainer, val klass: Class<*>) : SingletonDescriptor(container) { + public override fun getRegistrations(): Iterable> = klass.getInfo().registrations +} + +public class SingletonTypeComponentDescriptor(container: ComponentContainer, klass: Class<*>) : SingletonComponentDescriptor(container, klass) { + override fun createInstance(context: ValueResolveContext): Any = createInstanceOf(klass, context) + + private fun createInstanceOf(klass: Class<*>, context: ValueResolveContext): Any { + val binding = klass.bindToConstructor(context) + state = ComponentState.Initializing + for (argumentDescriptor in binding.argumentDescriptors) { + if (argumentDescriptor is Closeable && argumentDescriptor !is SingletonDescriptor) { + registerDisposableObject(argumentDescriptor) + } + } + + val constructor = binding.constructor + val arguments = computeArguments(binding.argumentDescriptors) + + val instance = constructor.newInstance(*arguments.toTypedArray())!! + state = ComponentState.Initialized + return instance + } + + override fun getDependencies(context: ValueResolveContext): Collection> { + val classInfo = klass.getInfo() + return classInfo.constructorInfo?.parameters.orEmpty() + classInfo.setterInfos.flatMap { it.parameters } + } +} \ 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 new file mode 100644 index 00000000000..94cdc266321 --- /dev/null +++ b/compiler/container/src/org/jetbrains/kotlin/container/Storage.kt @@ -0,0 +1,173 @@ +/* + * 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 + +import com.intellij.util.containers.MultiMap +import java.io.Closeable +import java.lang.reflect.Modifier +import java.util.ArrayList +import java.util.HashSet +import java.util.LinkedHashSet + +public enum class ComponentStorageState { + Initial, + Initialized, + Disposing, + Disposed +} + +public class ComponentStorage(val myId: String) : ValueResolver { + var state = ComponentStorageState.Initial + val registry = ComponentRegistry() + val descriptors = LinkedHashSet() + val dependencies = MultiMap.createLinkedSet>() + + override fun resolve(request: Class<*>, context: ValueResolveContext): ValueDescriptor? { + if (state == ComponentStorageState.Initial) + throw ContainerConsistencyException("Container was not composed before resolving") + + val entry = registry.tryGetEntry(request) + if (entry.isNotEmpty()) { + registerDependency(request, context) + + return entry.singleOrNull() + } + return null + } + + private fun registerDependency(request: Class<*>, context: ValueResolveContext) { + if (context is ComponentResolveContext) { + val descriptor = context.requestingDescriptor + if (descriptor is ComponentDescriptor) { + dependencies.putValue(descriptor, request) + } + } + } + + public fun resolveMultiple(request: Class<*>, context: ValueResolveContext): Iterable { + registerDependency(request, context) + return registry.tryGetEntry(request) + } + + public fun registerDescriptors(context: ComponentResolveContext, items: List) { + if (state == ComponentStorageState.Disposed) { + throw ContainerConsistencyException("Cannot register descriptors in $state state") + } + + for (descriptor in items) + descriptors.add(descriptor) + + if (state == ComponentStorageState.Initialized) + composeDescriptors(context, items) + + } + + public fun compose(context: ComponentResolveContext) { + if (state != ComponentStorageState.Initial) + throw ContainerConsistencyException("Container $myId was already composed.") + + state = ComponentStorageState.Initialized + composeDescriptors(context, descriptors) + } + + private fun composeDescriptors(context: ComponentResolveContext, descriptors: Collection) { + if (descriptors.isEmpty()) return + + registry.addAll(descriptors) + + val implicits = inspectDependenciesAndRegisterImplicits(context, descriptors) + + injectProperties(context, descriptors + implicits) + } + + private fun injectProperties(context: ComponentResolveContext, components: Collection) { + for (component in components) { + if (component.shouldInjectProperties) { + injectProperties(component.getValue(), context) + } + } + } + + private fun inspectDependenciesAndRegisterImplicits(context: ComponentResolveContext, descriptors: Collection): LinkedHashSet { + val implicits = LinkedHashSet() + val visitedTypes = HashSet>() + for (descriptor in descriptors) { + registerImplicits(context, descriptor, visitedTypes, implicits) + } + registry.addAll(implicits) + return implicits + } + + private fun registerImplicits( + context: ComponentResolveContext, descriptor: ComponentDescriptor, + visitedTypes: HashSet>, implicitDescriptors: LinkedHashSet + ) { + val dependencies = descriptor.getDependencies(context) + for (type in dependencies) { + 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) + } + } + } + } + + private fun injectProperties(instance: Any, context: ValueResolveContext) { + val classInfo = instance.javaClass.getInfo() + + classInfo.setterInfos.forEach { setterInfo -> + val methodBinding = setterInfo.method.bindToMethod(context) + methodBinding.invoke(instance) + } + } + + public fun dispose() { + if (state != ComponentStorageState.Initialized) { + if (state == ComponentStorageState.Initial) + return // it is valid to dispose container which was not initialized + throw ContainerConsistencyException("Component container cannot be disposed in the $state state.") + } + + state = ComponentStorageState.Disposing + val disposeList = getDescriptorsInDisposeOrder() + for (descriptor in disposeList) + disposeDescriptor(descriptor) + state = ComponentStorageState.Disposed + } + + fun getDescriptorsInDisposeOrder(): List { + return topologicalSort(descriptors) { + val dependent = ArrayList() + for (interfaceType in dependencies[it]) { + for (dependency in registry.tryGetEntry(interfaceType)) { + dependent.add(dependency) + } + } + dependent + } + } + + fun disposeDescriptor(descriptor: ComponentDescriptor) { + if (descriptor is Closeable) + descriptor.close() + } +} \ No newline at end of file diff --git a/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt b/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt new file mode 100644 index 00000000000..38d2a851b5e --- /dev/null +++ b/compiler/container/tests/org/jetbrains/kotlin/container/tests/ComponentContainerTest.kt @@ -0,0 +1,207 @@ +/* + * 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 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 = createContainer("test") {} + fails { + container.resolve() + } + } + + Test fun should_resolve_to_null_when_empty() { + val container = createContainer("test") { } + assertNull(container.resolve()) + } + + Test fun should_resolve_to_instance_when_registered() { + val container = createContainer("test") { useImpl() } + + val descriptor = container.resolve() + assertNotNull(descriptor) + val instance = descriptor!!.getValue() as TestComponentInterface + assertNotNull(instance) + fails { + instance.foo() + } + } + + Test fun should_resolve_instance_dependency() { + val container = createContainer("test") { + useInstance(ManualTestComponent("name")) + useImpl() + } + + val descriptor = container.resolve() + assertNotNull(descriptor) + val instance = descriptor!!.getValue() as TestClientComponent + assertNotNull(instance) + assertNotNull(instance.dep) + fails { + instance.dep.foo() + } + assertTrue(instance.dep is ManualTestComponent) + assertEquals("name", (instance.dep as ManualTestComponent).name) + container.close() + assertTrue(instance.disposed) + assertFalse(instance.dep.disposed) // should not dispose manually passed instances + } + + Test fun should_resolve_type_dependency() { + val container = createContainer("test") { + useImpl() + useImpl() + } + + val descriptor = container.resolve() + assertNotNull(descriptor) + val instance = descriptor!!.getValue() as TestClientComponent + assertNotNull(instance) + assertNotNull(instance.dep) + fails { + instance.dep.foo() + } + container.close() + assertTrue(instance.disposed) + assertTrue(instance.dep.disposed) + } + + Test fun should_resolve_multiple_types() { + createContainer("test") { + useImpl() + useImpl() + useImpl() + }.use { + val descriptor = it.resolveMultiple() + assertNotNull(descriptor) + assertEquals(2, descriptor.count()) + } + } + + Test fun should_resolve_singleton_types_to_same_instances() { + createContainer("test") { + useImpl() + useImpl() + }.use { + val descriptor1 = it.resolve() + assertNotNull(descriptor1) + val descriptor2 = it.resolve() + assertNotNull(descriptor2) + assertTrue(descriptor1 == descriptor2) + assertTrue(descriptor1!!.getValue() == descriptor2!!.getValue()) + } + } + + class WithSetters { + var isSetterCalled = false + + var tc: TestComponent? = null + @Inject set(v) { + isSetterCalled = true + } + } + + Test fun should_inject_properties_of_singletons() { + val withSetters = createContainer("test") { + useImpl() + }.get() + + assertTrue(withSetters.isSetterCalled) + } + + Test fun should_not_inject_properties_of_instances() { + val withSetters = WithSetters() + createContainer("test") { + useInstance(withSetters) + } + + assertFalse(withSetters.isSetterCalled) + } + + Test fun should_discover_dependencies_recursively() { + class C + + class B { + var c: C? = null + @Inject set + } + + + class A { + var b: B? = null + @Inject set + } + + val a = createContainer("test") { + useImpl() + }.get() + + val b = a.b + assertTrue(b is B) + val c = b!!.c + assertTrue(c is C) + } + +} \ No newline at end of file diff --git a/compiler/frontend/frontend.iml b/compiler/frontend/frontend.iml index 6da7803e422..08bbe9c7faa 100644 --- a/compiler/frontend/frontend.iml +++ b/compiler/frontend/frontend.iml @@ -13,5 +13,6 @@ + \ No newline at end of file