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