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