fb6e65a4e5ca8484e6f902374d21c4a00c31d6d3
[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.lang.reflect.Constructor
12 import java.lang.reflect.InvocationTargetException
13
14 internal fun <T> instantiateUsingPicoContainer(aClass: Class<*>, requestorKey: Any, componentManager: PlatformComponentManagerImpl, parameterResolver: ConstructorParameterResolver): T {
15   val result = getGreediestSatisfiableConstructor(aClass, requestorKey, componentManager, parameterResolver)
16   try {
17     result.first.isAccessible = true
18     val parameterTypes = result.second
19     @Suppress("UNCHECKED_CAST")
20     return result.first.newInstance(*Array(parameterTypes.size) {
21       parameterResolver.resolveInstance(componentManager, requestorKey, parameterTypes[it])
22     }) as T
23   }
24   catch (e: InvocationTargetException) {
25     throw e.cause ?: e
26   }
27 }
28
29 private fun getGreediestSatisfiableConstructor(aClass: Class<*>, requestorKey: Any, componentManager: PlatformComponentManagerImpl, parameterResolver: ConstructorParameterResolver): Pair<Constructor<*>, Array<Class<*>>> {
30   var conflicts: MutableSet<Constructor<*>>? = null
31   var unsatisfiableDependencyTypes: MutableSet<Array<Class<*>>>? = null
32   val sortedMatchingConstructors = getSortedMatchingConstructors(aClass)
33   var greediestConstructor: Constructor<*>? = null
34   var greediestConstructorParameterTypes: Array<Class<*>>? = null
35   var lastSatisfiableConstructorSize = -1
36   var unsatisfiedDependencyType: Class<*>? = null
37
38   loop@ for (constructor in sortedMatchingConstructors) {
39     if (sortedMatchingConstructors.size > 1 &&
40         (constructor.isAnnotationPresent(java.lang.Deprecated::class.java) || constructor.isAnnotationPresent(Deprecated::class.java))) {
41       continue
42     }
43
44     var failedDependency = false
45     val parameterTypes = constructor.parameterTypes
46
47     for (expectedType in parameterTypes) {
48       if (expectedType.isPrimitive || expectedType.isEnum || expectedType.isArray || Collection::class.java.isAssignableFrom(expectedType)) {
49         continue@loop
50       }
51
52       // check whether this constructor is satisfiable
53       if (parameterResolver.isResolvable(componentManager, requestorKey, expectedType)) {
54         continue
55       }
56
57       if (unsatisfiableDependencyTypes == null) {
58         unsatisfiableDependencyTypes = THashSet()
59       }
60       unsatisfiableDependencyTypes.add(parameterTypes)
61       unsatisfiedDependencyType = expectedType
62       failedDependency = true
63       break
64     }
65
66     if (greediestConstructor != null && parameterTypes.size != lastSatisfiableConstructorSize) {
67       if (conflicts.isNullOrEmpty()) {
68         // we found our match (greedy and satisfied)
69         return Pair(greediestConstructor, greediestConstructorParameterTypes!!)
70       }
71       else {
72         // fits although not greedy
73         conflicts.add(constructor)
74       }
75     }
76     else if (!failedDependency && lastSatisfiableConstructorSize == parameterTypes.size) {
77       // satisfied and same size as previous one?
78       if (conflicts == null) {
79         conflicts = THashSet()
80       }
81       conflicts.add(constructor)
82       greediestConstructor?.let {
83         conflicts.add(it)
84       }
85     }
86     else if (!failedDependency) {
87       greediestConstructor = constructor
88       greediestConstructorParameterTypes = parameterTypes
89       lastSatisfiableConstructorSize = parameterTypes.size
90     }
91   }
92
93   when {
94     !conflicts.isNullOrEmpty() -> {
95       throw TooManySatisfiableConstructorsException(aClass, conflicts)
96     }
97     greediestConstructor != null -> {
98       return Pair(greediestConstructor, greediestConstructorParameterTypes!!)
99     }
100     !unsatisfiableDependencyTypes.isNullOrEmpty() -> {
101       throw PicoIntrospectionException("$requestorKey has unsatisfied dependency: $unsatisfiedDependencyType among unsatisfiable dependencies: " +
102                                        "$unsatisfiableDependencyTypes where $componentManager was the leaf container being asked for dependencies.")
103     }
104     else -> {
105       throw PicoInitializationException("The specified parameters not match any of the following constructors: " +
106                                         "${aClass.declaredConstructors.joinToString(separator = "\n") { it.toString() }}\n" +
107                                         "for $aClass")
108     }
109   }
110 }
111
112 private val constructorComparator = Comparator<Constructor<*>> { c0, c1 -> c1.parameterCount - c0.parameterCount }
113
114 // filter out all constructors that will definitely not match
115 // optimize list of constructors moving the longest at the beginning
116 private fun getSortedMatchingConstructors(componentImplementation: Class<*>): Array<Constructor<*>> {
117   val declaredConstructors = componentImplementation.declaredConstructors
118   declaredConstructors.sortWith(constructorComparator)
119   return declaredConstructors
120 }
121
122 internal abstract class ConstructorParameterResolver {
123   open fun isResolvable(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Boolean {
124     return resolveAdapter(componentManager, requestorKey, expectedType) != null
125   }
126
127   open fun resolveInstance(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): Any? {
128     val adapter = resolveAdapter(componentManager, requestorKey, expectedType) ?: return null
129     return when (adapter) {
130       is BaseComponentAdapter -> {
131         // project level service Foo wants application level service Bar - adapter component manager should be used instead of current
132         adapter.getInstance(adapter.componentManager)
133       }
134       else -> {
135         if (componentManager.parent == null) {
136           adapter.getComponentInstance(componentManager.picoContainer)
137         }
138         else {
139           componentManager.picoContainer.getComponentInstance(adapter.componentKey)
140         }
141       }
142     }
143   }
144
145   private fun resolveAdapter(componentManager: PlatformComponentManagerImpl, requestorKey: Any, expectedType: Class<*>): ComponentAdapter? {
146     val result = getTargetAdapter(componentManager.picoContainer, expectedType, requestorKey) ?: return null
147     if (expectedType.isAssignableFrom(result.componentImplementation)) {
148       return result
149     }
150     return null
151   }
152
153   private fun getTargetAdapter(container: DefaultPicoContainer, expectedType: Class<*>, excludeKey: Any): ComponentAdapter? {
154     val byKey = container.getComponentAdapter(expectedType)
155     if (byKey != null && excludeKey != byKey.componentKey) {
156       return byKey
157     }
158
159     val found = container.getComponentAdaptersOfType(expectedType)
160     found.removeIf { it.componentKey == excludeKey }
161     return when {
162       found.size == 0 -> container.parent?.getComponentAdapterOfType(expectedType)
163       found.size == 1 -> found[0]
164       else -> throw AmbiguousComponentResolutionException(expectedType, Array(found.size) { found[it].componentImplementation })
165     }
166   }
167 }