1
2
3
4
5
6 package mockit.internal.injection.constructor;
7
8 import static java.lang.reflect.Modifier.PRIVATE;
9 import static java.lang.reflect.Modifier.PROTECTED;
10 import static java.lang.reflect.Modifier.PUBLIC;
11
12 import static mockit.internal.injection.InjectionPoint.getQualifiedName;
13 import static mockit.internal.injection.InjectionPoint.getTypeOfInjectionPointFromVarargsParameter;
14 import static mockit.internal.injection.InjectionPoint.kindOfInjectionPoint;
15
16 import edu.umd.cs.findbugs.annotations.NonNull;
17 import edu.umd.cs.findbugs.annotations.Nullable;
18
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Type;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Comparator;
25 import java.util.List;
26
27 import mockit.asm.types.JavaType;
28 import mockit.internal.injection.InjectionPoint;
29 import mockit.internal.injection.InjectionPoint.KindOfInjectionPoint;
30 import mockit.internal.injection.InjectionProvider;
31 import mockit.internal.injection.InjectionProviders;
32 import mockit.internal.injection.InjectionState;
33 import mockit.internal.injection.TestedClass;
34 import mockit.internal.state.ParameterNames;
35 import mockit.internal.util.ParameterNameExtractor;
36
37 public final class ConstructorSearch {
38 private static final int CONSTRUCTOR_ACCESS = PUBLIC + PROTECTED + PRIVATE;
39
40 @NonNull
41 private final InjectionState injectionState;
42 @NonNull
43 private final TestedClass testedClass;
44 @NonNull
45 private final String testedClassDesc;
46 @NonNull
47 public List<InjectionProvider> parameterProviders;
48 private final boolean withFullInjection;
49 @Nullable
50 private Constructor<?> constructor;
51 @Nullable
52 private StringBuilder searchResults;
53
54 public ConstructorSearch(@NonNull InjectionState injectionState, @NonNull TestedClass testedClass,
55 boolean withFullInjection) {
56 this.injectionState = injectionState;
57 this.testedClass = testedClass;
58 Class<?> declaredClass = testedClass.getDeclaredClass();
59 testedClassDesc = ParameterNameExtractor.extractNames(declaredClass);
60 parameterProviders = new ArrayList<>();
61 this.withFullInjection = withFullInjection;
62 }
63
64 @Nullable
65 public Constructor<?> findConstructorToUse() {
66 constructor = null;
67 Class<?> declaredClass = testedClass.targetClass;
68 Constructor<?>[] constructors = declaredClass.getDeclaredConstructors();
69
70 if (!findSingleAnnotatedConstructor(constructors)) {
71 findSatisfiedConstructorWithMostParameters(constructors);
72 }
73
74 return constructor;
75 }
76
77 private boolean findSingleAnnotatedConstructor(@NonNull Constructor<?>[] constructors) {
78 for (Constructor<?> c : constructors) {
79 if (kindOfInjectionPoint(c) != KindOfInjectionPoint.NotAnnotated) {
80 List<InjectionProvider> providersFound = findParameterProvidersForConstructor(c);
81
82 if (providersFound != null) {
83 parameterProviders = providersFound;
84 constructor = c;
85 }
86
87 return true;
88 }
89 }
90
91 return false;
92 }
93
94 private void findSatisfiedConstructorWithMostParameters(@NonNull Constructor<?>[] constructors) {
95 sortConstructorsWithMostAccessibleFirst(constructors);
96
97 Constructor<?> unresolvedConstructor = null;
98 List<InjectionProvider> incompleteProviders = null;
99
100 for (Constructor<?> candidateConstructor : constructors) {
101 List<InjectionProvider> providersFound = findParameterProvidersForConstructor(candidateConstructor);
102
103 if (providersFound != null) {
104 if (withFullInjection && containsUnresolvedProvider(providersFound)) {
105 if (unresolvedConstructor == null || isLargerConstructor(candidateConstructor, providersFound,
106 unresolvedConstructor, incompleteProviders)) {
107 unresolvedConstructor = candidateConstructor;
108 incompleteProviders = providersFound;
109 }
110 } else if (constructor == null
111 || isLargerConstructor(candidateConstructor, providersFound, constructor, parameterProviders)) {
112 constructor = candidateConstructor;
113 parameterProviders = providersFound;
114 }
115 }
116 }
117
118 selectConstructorWithUnresolvedParameterIfMoreAccessible(unresolvedConstructor, incompleteProviders);
119 }
120
121 private static void sortConstructorsWithMostAccessibleFirst(@NonNull Constructor<?>[] constructors) {
122 if (constructors.length > 1) {
123 Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR);
124 }
125 }
126
127 private static final Comparator<Constructor<?>> CONSTRUCTOR_COMPARATOR = ConstructorSearch::compareAccessibility;
128
129 private static int compareAccessibility(@NonNull Constructor<?> c1, @NonNull Constructor<?> c2) {
130 int m1 = getModifiers(c1);
131 int m2 = getModifiers(c2);
132 if (m1 == m2) {
133 return 0;
134 }
135 if (m1 == PUBLIC) {
136 return -1;
137 }
138 if (m2 == PUBLIC) {
139 return 1;
140 }
141 if (m1 == PROTECTED) {
142 return -1;
143 }
144 if (m2 == PROTECTED) {
145 return 1;
146 }
147 if (m2 == PRIVATE) {
148 return -1;
149 }
150 return 1;
151 }
152
153 private static boolean containsUnresolvedProvider(@NonNull List<InjectionProvider> providersFound) {
154 for (InjectionProvider provider : providersFound) {
155 if (provider instanceof ConstructorParameter && provider.getValue(null) == null) {
156 return true;
157 }
158 }
159
160 return false;
161 }
162
163 private static boolean isLargerConstructor(@NonNull Constructor<?> candidateConstructor,
164 @NonNull List<InjectionProvider> providersFound, @NonNull Constructor<?> previousSatisfiableConstructor,
165 @NonNull List<InjectionProvider> previousProviders) {
166 return getModifiers(candidateConstructor) == getModifiers(previousSatisfiableConstructor)
167 && providersFound.size() >= previousProviders.size();
168 }
169
170 private static int getModifiers(@NonNull Constructor<?> c) {
171 return CONSTRUCTOR_ACCESS & c.getModifiers();
172 }
173
174 @Nullable
175 private List<InjectionProvider> findParameterProvidersForConstructor(@NonNull Constructor<?> candidate) {
176 Type[] parameterTypes = candidate.getGenericParameterTypes();
177 Annotation[][] parameterAnnotations = candidate.getParameterAnnotations();
178 int n = parameterTypes.length;
179 List<InjectionProvider> providersFound = new ArrayList<>(n);
180 boolean varArgs = candidate.isVarArgs();
181
182 if (varArgs) {
183 n--;
184 }
185
186 printCandidateConstructorNameIfRequested(candidate);
187
188 String constructorDesc = "<init>" + JavaType.getConstructorDescriptor(candidate);
189 InjectionProviders injectionProviders = injectionState.injectionProviders;
190 KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(candidate);
191
192 for (int i = 0; i < n; i++) {
193 Type parameterType = parameterTypes[i];
194 injectionProviders.setTypeOfInjectionPoint(parameterType, kindOfInjectionPoint);
195
196 String parameterName = ParameterNames.getName(testedClassDesc, constructorDesc, i);
197 Annotation[] appliedAnnotations = parameterAnnotations[i];
198 InjectionProvider provider = findOrCreateInjectionProvider(parameterType, parameterName,
199 appliedAnnotations);
200
201 if (provider == null || providersFound.contains(provider)) {
202 printParameterOfCandidateConstructorIfRequested(parameterName, provider);
203 return null;
204 }
205
206 providersFound.add(provider);
207 }
208
209 if (varArgs) {
210 Type parameterType = parameterTypes[n];
211 InjectionProvider injectable = hasInjectedValuesForVarargsParameter(parameterType, kindOfInjectionPoint,
212 injectionProviders);
213
214 if (injectable != null) {
215 providersFound.add(injectable);
216 }
217 }
218
219 return providersFound;
220 }
221
222 @Nullable
223 private InjectionProvider findOrCreateInjectionProvider(@NonNull Type parameterType, @Nullable String parameterName,
224 @NonNull Annotation[] parameterAnnotations) {
225 String qualifiedName = getQualifiedName(parameterAnnotations);
226
227 if (parameterName == null && qualifiedName == null) {
228 return null;
229 }
230
231 boolean qualified = qualifiedName != null;
232 String targetName = qualified ? qualifiedName : parameterName;
233 InjectionProvider provider = injectionState.injectionProviders.getProviderByTypeAndOptionallyName(targetName,
234 testedClass);
235
236 if (provider != null) {
237 return provider;
238 }
239
240 InjectionPoint injectionPoint = new InjectionPoint(parameterType, targetName, qualifiedName);
241 Object valueForParameter = injectionState.getTestedValue(testedClass, injectionPoint);
242
243 if (valueForParameter == null && !withFullInjection) {
244 return null;
245 }
246
247 return new ConstructorParameter(parameterType, parameterAnnotations, targetName, valueForParameter);
248 }
249
250 @Nullable
251 private InjectionProvider hasInjectedValuesForVarargsParameter(@NonNull Type parameterType,
252 @NonNull KindOfInjectionPoint kindOfInjectionPoint, @NonNull InjectionProviders injectionProviders) {
253 Type varargsElementType = getTypeOfInjectionPointFromVarargsParameter(parameterType);
254 injectionProviders.setTypeOfInjectionPoint(varargsElementType, kindOfInjectionPoint);
255 return injectionProviders.findNextInjectableForInjectionPoint(testedClass);
256 }
257
258 private void selectConstructorWithUnresolvedParameterIfMoreAccessible(
259 @Nullable Constructor<?> unresolvedConstructor, List<InjectionProvider> incompleteProviders) {
260 if (unresolvedConstructor != null
261 && (constructor == null || compareAccessibility(unresolvedConstructor, constructor) < 0)) {
262 constructor = unresolvedConstructor;
263 parameterProviders = incompleteProviders;
264 }
265 }
266
267
268
269 @NonNull
270 public String getDescription() {
271 searchResults = new StringBuilder();
272 findConstructorToUse();
273 String contents = searchResults.toString();
274 searchResults = null;
275 return contents;
276 }
277
278 @SuppressWarnings("DynamicRegexReplaceableByCompiledPattern")
279 private void printCandidateConstructorNameIfRequested(@NonNull Constructor<?> candidate) {
280 if (searchResults != null) {
281 String constructorDesc = candidate.toGenericString().replace("java.lang.", "").replace(",", ", ");
282 searchResults.append("\r\n ").append(constructorDesc).append("\r\n");
283 }
284 }
285
286 private void printParameterOfCandidateConstructorIfRequested(@Nullable String parameterName,
287 @Nullable InjectionProvider injectableFound) {
288 if (searchResults != null) {
289 searchResults.append(" disregarded because ");
290
291 if (parameterName == null) {
292 searchResults.append("parameter names are not available");
293 } else {
294 searchResults.append("no tested/injectable value was found for parameter \"").append(parameterName)
295 .append('"');
296
297 if (injectableFound != null) {
298 searchResults.append(" that hadn't been used already");
299 }
300 }
301 }
302 }
303 }