View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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     // Methods used only when no satisfiable constructor is found //////////////////////////////////////////////////////
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 }