View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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     // Methods used only when no satisfiable constructor is found //////////////////////////////////////////////////////
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 }