fix "IDEA-221944 Deadlock on opening second project" and support preloading for proje...
[idea/community.git] / platform / service-container / src / com / intellij / serviceContainer / ConstructorInjectionComponentAdapter.kt
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.serviceContainer
3
4 import com.intellij.util.pico.DefaultPicoContainer
5 import gnu.trove.THashSet
6 import org.picocontainer.ComponentAdapter
7 import org.picocontainer.PicoInitializationException
8 import org.picocontainer.PicoIntrospectionException
9 import org.picocontainer.defaults.AmbiguousComponentResolutionException
10 import org.picocontainer.defaults.TooManySatisfiableConstructorsException
11 import java.io.File
12 import java.lang.reflect.Constructor
13 import java.lang.reflect.InvocationTargetException
14 import java.nio.file.Path
15
16 internal fun <T> instantiateUsingPicoContainer(aClass: Class<*>, requestorKey: Any, componentManager: PlatformComponentManagerImpl, parameterResolver: ConstructorParameterResolver): T {
17   val result = getGreediestSatisfiableConstructor(aClass, requestorKey, componentManager, parameterResolver)
18   try {
19     result.first.isAccessible = true
20     val parameterTypes = result.second
21     @Suppress("UNCHECKED_CAST")
22     return result.first.newInstance(*Array(parameterTypes.size) {
23       parameterResolver.resolveInstance(componentManager, requestorKey, parameterTypes[it])
24     }) as T
25   }
26   catch (e: InvocationTargetException) {
27     throw e.cause ?: e
28   }
29 }
30
31 private fun getGreediestSatisfiableConstructor(aClass: Class<*>, requestorKey: Any, componentManager: PlatformComponentManagerImpl, parameterResolver: ConstructorParameterResolver): Pair<Constructor<*>, Array<Class<*>>> {
32   var conflicts: MutableSet<Constructor<*>>? = null
33   var unsatisfiableDependencyTypes: MutableSet<Array<Class<*>>>? = null
34   val sortedMatchingConstructors = getSortedMatchingConstructors(aClass)
35   var greediestConstructor: Constructor<*>? = null
36   var greediestConstructorParameterTypes: Array<Class<*>>? = null
37   var lastSatisfiableConstructorSize = -1
38   var unsatisfiedDependencyType: Class<*>? = null
39
40   loop@ for (constructor in sortedMatchingConstructors) {
41     if (sortedMatchingConstructors.size > 1 &&
42         (constructor.isAnnotationPresent(java.lang.Deprecated::class.java) || constructor.isAnnotationPresent(Deprecated::class.java))) {
43       continue
44     }
45
46     var failedDependency = false
47     val parameterTypes = constructor.parameterTypes
48
49     for (expectedType in parameterTypes) {
50       if (expectedType.isPrimitive || expectedType.isEnum || expectedType.isArray ||
51           Collection::class.java.isAssignableFrom(expectedType) ||
52           expectedType === File::class.java || expectedType === Path::class.java) {
53         continue@loop
54       }
55
56       // check whether this constructor is satisfiable
57       if (parameterResolver.isResolvable(componentManager, requestorKey, expectedType)) {
58         continue
59       }
60
61       if (unsatisfiableDependencyTypes == null) {
62         unsatisfiableDependencyTypes = THashSet()
63       }
64       unsatisfiableDependencyTypes.add(parameterTypes)
65       unsatisfiedDependencyType = expectedType
66       failedDependency = true
67       break
68     }
69
70     if (greediestConstructor != null && parameterTypes.size != lastSatisfiableConstructorSize) {
71       if (conflicts.isNullOrEmpty()) {
72         // we found our match (greedy and satisfied)
73         return Pair(greediestConstructor, greediestConstructorParameterTypes!!)
74       }
75       else {
76         // fits although not greedy
77         conflicts.add(constructor)
78       }
79     }
80     else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.size) {
81       // satisfied and same size as previous one?
82       if (conflicts == null) {
83         conflicts = THashSet()
84       }
85       conflicts.add(constructor)
86       greediestConstructor?.let {
87         conflicts.add(it)
88       }
89     }
90     else if (!failedDependency) {
91       greediestConstructor = constructor
92       greediestConstructorParameterTypes = parameterTypes
93       lastSatisfiableConstructorSize = parameterTypes.size
94     }
95   }
96
97   when {
98     !conflicts.isNullOrEmpty() -> {
99       throw TooManySatisfiableConstructorsException(aClass, conflicts)
100     }
101     greediestConstructor != null -> {
102       return Pair(greediestConstructor, greediestConstructorParameterTypes!!)
103     }
104     !unsatisfiableDependencyTypes.isNullOrEmpty() -> {
105       throw PicoIntrospectionException("${aClass.name} has unsatisfied dependency: $unsatisfiedDependencyType among unsatisfiable dependencies: " +
106                                        "$unsatisfiableDependencyTypes where $componentManager was the leaf container being asked for dependencies.")
107     }
108     else -> {
109       throw PicoInitializationException("The specified parameters not match any of the following constructors: " +
110                                         "${aClass.declaredConstructors.joinToString(separator = "\n") { it.toString() }}\n" +
111                                         "for $aClass")
112     }
113   }
114 }
115
116 private val constructorComparator = Comparator<Constructor<*>> { c0, c1 -> c1.parameterCount - c0.parameterCount }
117
118 // filter out all constructors that will definitely not match
119 // optimize list of constructors moving the longest at the beginning
120 private fun getSortedMatchingConstructors(componentImplementation: Class<*>): Array<Constructor<*>> {
121   val declaredConstructors = componentImplementation.declaredConstructors
122   declaredConstructors.sortWith(constructorComparator)
123   return declaredConstructors
124 }
125
126 internal abstract class ConstructorParameterResolver {
127   open fun isResolvable(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Boolean {
128     return resolveAdapter(componentManager, requestorKey, expectedType) != null
129   }
130
131   open fun resolveInstance(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Any? {
132     val adapter = resolveAdapter(componentManager, requestorKey, expectedType) ?: return null
133     return when (adapter) {
134       is BaseComponentAdapter -> {
135         // project level service Foo wants application level service Bar - adapter component manager should be used instead of current
136         adapter.getInstance(adapter.componentManager)
137       }
138       else -> {
139         if (componentManager.parent == null) {
140           adapter.getComponentInstance(componentManager.picoContainer)
141         }
142         else {
143           componentManager.picoContainer.getComponentInstance(adapter.componentKey)
144         }
145       }
146     }
147   }
148
149   private fun resolveAdapter(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): ComponentAdapter? {
150     val result = getTargetAdapter(componentManager.picoContainer, expectedType, requestorKey) ?: return null
151     if (expectedType.isAssignableFrom(result.componentImplementation)) {
152       return result
153     }
154     return null
155   }
156
157   private fun getTargetAdapter(container: DefaultPicoContainer, expectedType: Class<*>, excludeKey: Any): ComponentAdapter? {
158     val byKey = container.getComponentAdapter(expectedType)
159     if (byKey != null && excludeKey != byKey.componentKey) {
160       return byKey
161     }
162
163     // see UndoManagerImpl / RunManager / JavaModuleExternalPathsImpl for example
164     val expectedClassName = expectedType.name
165
166     if (container.parent == null) {
167       if (expectedClassName == "com.intellij.openapi.project.Project") {
168         return null
169       }
170     }
171     else {
172       if (expectedClassName == "com.intellij.configurationStore.StreamProvider" ||
173           expectedClassName == "com.intellij.openapi.roots.LanguageLevelModuleExtensionImpl" ||
174           expectedClassName == "com.intellij.openapi.roots.impl.CompilerModuleExtensionImpl" ||
175           expectedClassName == "com.intellij.openapi.roots.impl.JavaModuleExternalPathsImpl") {
176         return null
177       }
178     }
179
180     val found = container.getComponentAdaptersOfType(expectedType)
181     found.removeIf { it.componentKey == excludeKey }
182     return when {
183       found.size == 0 -> container.parent?.getComponentAdapterOfType(expectedType)
184       found.size == 1 -> found[0]
185       else -> throw AmbiguousComponentResolutionException(expectedType, Array(found.size) { found[it].componentImplementation })
186     }
187   }
188 }