Add container module with implementation of di based on java reflection

Initial implementation by Ilya Ryzhenkov
Renovation, optimization, integration and removal of unused features by Pavel Talanov
This commit is contained in:
Ilya Ryzhenkov
2015-04-21 19:21:06 +03:00
committed by Pavel V. Talanov
parent f52bc8a2d2
commit 5db541ee24
16 changed files with 1023 additions and 0 deletions
+1
View File
@@ -43,6 +43,7 @@
<element id="module-output" name="idea-completion" />
<element id="module-output" name="idea-core" />
<element id="module-output" name="idea-js" />
<element id="module-output" name="container" />
</element>
<element id="library" level="project" name="javax.inject" />
<element id="directory" name="jps">
+1
View File
@@ -18,6 +18,7 @@
<module fileurl="file://$PROJECT_DIR$/compiler/cli/cli.iml" filepath="$PROJECT_DIR$/compiler/cli/cli.iml" group="compiler/cli" />
<module fileurl="file://$PROJECT_DIR$/compiler/cli/cli-common/cli-common.iml" filepath="$PROJECT_DIR$/compiler/cli/cli-common/cli-common.iml" group="compiler/cli" />
<module fileurl="file://$PROJECT_DIR$/compiler/tests/compiler-tests.iml" filepath="$PROJECT_DIR$/compiler/tests/compiler-tests.iml" group="compiler" />
<module fileurl="file://$PROJECT_DIR$/compiler/container/container.iml" filepath="$PROJECT_DIR$/compiler/container/container.iml" group="compiler" />
<module fileurl="file://$PROJECT_DIR$/core/descriptor.loader.java/descriptor.loader.java.iml" filepath="$PROJECT_DIR$/core/descriptor.loader.java/descriptor.loader.java.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/core/descriptors/descriptors.iml" filepath="$PROJECT_DIR$/core/descriptors/descriptors.iml" group="core" />
<module fileurl="file://$PROJECT_DIR$/core/descriptors.runtime/descriptors.runtime.iml" filepath="$PROJECT_DIR$/core/descriptors.runtime/descriptors.runtime.iml" group="core" />
+3
View File
@@ -87,6 +87,7 @@
<include name="compiler/builtins-serializer/src"/>
<include name="compiler/cli/src"/>
<include name="compiler/cli/cli-common/src"/>
<include name="compiler/container/src"/>
<include name="compiler/frontend/src"/>
<include name="compiler/frontend.java/src"/>
<include name="compiler/light-classes/src"/>
@@ -105,6 +106,7 @@
<property name="idea.out" value="${basedir}/out/production"/>
<patternset id="compilerClassesFromIDEA.fileset">
<include name="frontend/**"/>
<include name="container/**"/>
<include name="descriptors/**"/>
<include name="deserialization/**"/>
<include name="serialization/**"/>
@@ -199,6 +201,7 @@
<fileset dir="compiler/builtins-serializer/src"/>
<fileset dir="compiler/cli/src"/>
<fileset dir="compiler/cli/cli-common/src"/>
<fileset dir="compiler/container/src"/>
<fileset dir="compiler/frontend/src"/>
<fileset dir="compiler/frontend.java/src"/>
<fileset dir="compiler/light-classes/src"/>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../core/container/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="library" name="intellij-core" level="project" />
<orderEntry type="library" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" scope="TEST" name="javax.inject" level="project" />
</component>
</module>
@@ -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<Class<*>, 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<SetterInfo>,
val registrations: List<Class<*>>
)
data class ConstructorInfo(
val constructor: Constructor<*>,
val parameters: List<Class<*>>
)
data class SetterInfo(
val method: Method,
val parameters: List<Class<*>>
)
private fun traverseClass(c: Class<*>): ClassInfo {
return ClassInfo(getConstructorInfo(c), getSetterInfos(c), getRegistrations(c))
}
private fun getSetterInfos(c: Class<*>): List<SetterInfo> {
val setterInfos = ArrayList<SetterInfo>()
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<Class<*>>) {
cl.getInterfaces().forEach {
if (result.add(it)) {
collectInterfacesRecursive(it, result)
}
}
}
private fun getRegistrations(klass: Class<*>): List<Class<*>> {
val registrations = ArrayList<Class<*>>()
val superClasses = sequence(klass) { (it as Class<Any>).getSuperclass() }
registrations.addAll(superClasses)
val interfaces = LinkedHashSet<Class<*>>()
superClasses.forEach { collectInterfacesRecursive(it, interfaces) }
registrations.addAll(interfaces)
registrations.remove(javaClass<Any>())
return registrations
}
@@ -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<Class<*>> = instance.javaClass.getInfo().registrations
override fun getDependencies(context: ValueResolveContext): Collection<Class<*>> = emptyList()
}
@@ -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<Class<*>> = throw UnsupportedOperationException()
override fun getRegistrations(): Iterable<Class<*>> = 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<ValueDescriptor> {
return componentStorage.resolveMultiple(request, context)
}
public fun registerDescriptors(descriptors: List<ComponentDescriptor>): 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 <reified T> StorageComponentContainer.resolve(context: ValueResolveContext = unknownContext): ValueDescriptor? {
return resolve(javaClass<T>(), context)
}
public inline fun <reified T> StorageComponentContainer.resolveMultiple(context: ValueResolveContext = unknownContext): Iterable<ValueDescriptor> {
return resolveMultiple(javaClass<T>(), context)
}
@@ -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<T>(items: Iterable<T>, dependencies: (T) -> Iterable<T>): List<T> {
val itemsInProgress = HashSet<T>()
val completedItems = HashSet<T>()
val result = ArrayList<T>()
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()
@@ -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<Class<*>>
fun getDependencies(context: ValueResolveContext): Collection<Class<*>>
val shouldInjectProperties: Boolean
get() = false
}
@@ -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 <reified T> StorageComponentContainer.useImpl() {
registerSingleton(javaClass<T>())
}
public inline fun <reified T> StorageComponentContainer.get(): T {
return resolve(javaClass<T>(), unknownContext)!!.getValue() as T
}
public fun StorageComponentContainer.useInstance(instance: Any) {
registerInstance(instance)
}
public inline fun <reified T> StorageComponentContainer.get(thisRef: Any?, desc: PropertyMetadata): T {
return resolve(javaClass<T>())!!.getValue() as T
}
@@ -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<ComponentDescriptor>): MultiMap<Class<*>, ComponentDescriptor> {
val registrationMap = MultiMap<Class<*>, ComponentDescriptor>()
for (descriptor in descriptors) {
for (registration in descriptor.getRegistrations()) {
registrationMap.putValue(registration, descriptor)
}
}
return registrationMap
}
private var registrationMap = MultiMap.createLinkedSet<Class<*>, ComponentDescriptor>()
public fun addAll(descriptors: Collection<ComponentDescriptor>) {
registrationMap.putAllValues(buildRegistrationMap(descriptors))
}
public fun tryGetEntry(request: Class<*>): Collection<ComponentDescriptor> {
return registrationMap.get(request)
}
}
@@ -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<ValueDescriptor>)
public class MethodBinding(val method: Method, val argumentDescriptors: List<ValueDescriptor>) {
fun invoke(instance: Any) {
val arguments = computeArguments(argumentDescriptors).toTypedArray()
method.invoke(instance, *arguments)
}
}
public fun computeArguments(argumentDescriptors: List<ValueDescriptor>): List<Any> = 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<Class<*>>, context: ValueResolveContext): List<ValueDescriptor> {
val bound = ArrayList<ValueDescriptor>(parameters.size())
var unsatisfied: MutableList<Type>? = null
for (parameter in parameters) {
val descriptor = context.resolve(parameter)
if (descriptor == null) {
if (unsatisfied == null)
unsatisfied = ArrayList<Type>()
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)
@@ -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<Closeable>() }
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<Class<*>> = 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<Class<*>> {
val classInfo = klass.getInfo()
return classInfo.constructorInfo?.parameters.orEmpty() + classInfo.setterInfos.flatMap { it.parameters }
}
}
@@ -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<ComponentDescriptor>()
val dependencies = MultiMap.createLinkedSet<ComponentDescriptor, Class<*>>()
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<ValueDescriptor> {
registerDependency(request, context)
return registry.tryGetEntry(request)
}
public fun registerDescriptors(context: ComponentResolveContext, items: List<ComponentDescriptor>) {
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<ComponentDescriptor>) {
if (descriptors.isEmpty()) return
registry.addAll(descriptors)
val implicits = inspectDependenciesAndRegisterImplicits(context, descriptors)
injectProperties(context, descriptors + implicits)
}
private fun injectProperties(context: ComponentResolveContext, components: Collection<ComponentDescriptor>) {
for (component in components) {
if (component.shouldInjectProperties) {
injectProperties(component.getValue(), context)
}
}
}
private fun inspectDependenciesAndRegisterImplicits(context: ComponentResolveContext, descriptors: Collection<ComponentDescriptor>): LinkedHashSet<ComponentDescriptor> {
val implicits = LinkedHashSet<ComponentDescriptor>()
val visitedTypes = HashSet<Class<*>>()
for (descriptor in descriptors) {
registerImplicits(context, descriptor, visitedTypes, implicits)
}
registry.addAll(implicits)
return implicits
}
private fun registerImplicits(
context: ComponentResolveContext, descriptor: ComponentDescriptor,
visitedTypes: HashSet<Class<*>>, implicitDescriptors: LinkedHashSet<ComponentDescriptor>
) {
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<ComponentDescriptor> {
return topologicalSort(descriptors) {
val dependent = ArrayList<ComponentDescriptor>()
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()
}
}
@@ -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<TestComponentInterface>()
}
}
Test fun should_resolve_to_null_when_empty() {
val container = createContainer("test") { }
assertNull(container.resolve<TestComponentInterface>())
}
Test fun should_resolve_to_instance_when_registered() {
val container = createContainer("test") { useImpl<TestComponent>() }
val descriptor = container.resolve<TestComponentInterface>()
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<TestClientComponent>()
}
val descriptor = container.resolve<TestClientComponent>()
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<TestComponent>()
useImpl<TestClientComponent>()
}
val descriptor = container.resolve<TestClientComponent>()
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<TestComponent>()
useImpl<TestClientComponent>()
useImpl<TestClientComponent2>()
}.use {
val descriptor = it.resolveMultiple<TestClientComponentInterface>()
assertNotNull(descriptor)
assertEquals(2, descriptor.count())
}
}
Test fun should_resolve_singleton_types_to_same_instances() {
createContainer("test") {
useImpl<TestComponent>()
useImpl<TestClientComponent>()
}.use {
val descriptor1 = it.resolve<TestClientComponentInterface>()
assertNotNull(descriptor1)
val descriptor2 = it.resolve<TestClientComponentInterface>()
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<WithSetters>()
}.get<WithSetters>()
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<A>()
}.get<A>()
val b = a.b
assertTrue(b is B)
val c = b!!.c
assertTrue(c is C)
}
}
+1
View File
@@ -13,5 +13,6 @@
<orderEntry type="module" module-name="descriptors" exported="" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="plugin-api" exported="" />
<orderEntry type="module" module-name="container" exported="" />
</component>
</module>