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 mockit.internal.injection.InjectionPoint.getQualifiedName;
9   import static mockit.internal.injection.InjectionPoint.getTypeOfInjectionPointFromVarargsParameter;
10  import static mockit.internal.injection.InjectionPoint.kindOfInjectionPoint;
11  import static mockit.internal.injection.InjectionPoint.wrapInProviderIfNeeded;
12  import static mockit.internal.injection.InjectionProvider.NULL;
13  import static mockit.internal.reflection.ConstructorReflection.invokeAccessible;
14  import static mockit.internal.util.Utilities.NO_ARGS;
15  import static mockit.internal.util.Utilities.ensureThatMemberIsAccessible;
16  import static mockit.internal.util.Utilities.getClassType;
17  
18  import edu.umd.cs.findbugs.annotations.NonNull;
19  import edu.umd.cs.findbugs.annotations.Nullable;
20  
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Constructor;
23  import java.lang.reflect.Type;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import mockit.asm.types.JavaType;
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.Injector;
33  import mockit.internal.injection.TestedClass;
34  import mockit.internal.injection.full.FullInjection;
35  import mockit.internal.state.ParameterNames;
36  import mockit.internal.state.TestRun;
37  import mockit.internal.util.MethodFormatter;
38  import mockit.internal.util.StackTrace;
39  
40  public final class ConstructorInjection extends Injector {
41      @NonNull
42      private final Constructor<?> constructor;
43  
44      public ConstructorInjection(@NonNull InjectionState injectionState, @Nullable FullInjection fullInjection,
45              @NonNull Constructor<?> constructor) {
46          super(injectionState, fullInjection);
47          ensureThatMemberIsAccessible(constructor);
48          this.constructor = constructor;
49      }
50  
51      @Nullable
52      public Object instantiate(@NonNull List<InjectionProvider> parameterProviders, @NonNull TestedClass testedClass,
53              boolean required, boolean needToConstruct) {
54          Type[] parameterTypes = constructor.getGenericParameterTypes();
55          int n = parameterTypes.length;
56          List<InjectionProvider> consumedInjectables = n == 0 ? null
57                  : injectionState.injectionProviders.saveConsumedInjectionProviders();
58          Object[] arguments = n == 0 ? NO_ARGS : new Object[n];
59          boolean varArgs = constructor.isVarArgs();
60  
61          if (varArgs) {
62              n--;
63          }
64  
65          for (int i = 0; i < n; i++) {
66              @NonNull
67              InjectionProvider parameterProvider = parameterProviders.get(i);
68              Object value;
69  
70              if (parameterProvider instanceof ConstructorParameter) {
71                  value = createOrReuseArgumentValue((ConstructorParameter) parameterProvider, required);
72  
73                  if (value == null && !needToConstruct) {
74                      return null;
75                  }
76              } else {
77                  value = getArgumentValueToInject(parameterProvider, i);
78              }
79  
80              if (value != NULL) {
81                  Type parameterType = parameterTypes[i];
82                  arguments[i] = wrapInProviderIfNeeded(parameterType, value);
83              }
84          }
85  
86          if (varArgs) {
87              Type parameterType = parameterTypes[n];
88              arguments[n] = obtainInjectedVarargsArray(parameterType, testedClass);
89          }
90  
91          if (consumedInjectables != null) {
92              injectionState.injectionProviders.restoreConsumedInjectionProviders(consumedInjectables);
93          }
94  
95          return invokeConstructor(arguments);
96      }
97  
98      @Nullable
99      private Object createOrReuseArgumentValue(@NonNull ConstructorParameter constructorParameter, boolean required) {
100         Object givenValue = constructorParameter.getValue(null);
101 
102         if (givenValue != null) {
103             return givenValue;
104         }
105 
106         assert fullInjection != null;
107 
108         Class<?> parameterClass = constructorParameter.getClassOfDeclaredType();
109         Object newOrReusedValue = null;
110 
111         if (FullInjection.isInstantiableType(parameterClass)) {
112             Type parameterType = constructorParameter.getDeclaredType();
113             KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(constructor);
114             injectionState.injectionProviders.setTypeOfInjectionPoint(parameterType, kindOfInjectionPoint);
115             String qualifiedName = getQualifiedName(constructorParameter.getAnnotations());
116             TestedClass nextTestedClass = new TestedClass(parameterType, parameterClass);
117 
118             newOrReusedValue = fullInjection.createOrReuseInstance(nextTestedClass, this, constructorParameter,
119                     qualifiedName);
120         } else {
121             fullInjection.setInjectionProvider(constructorParameter);
122         }
123 
124         if (newOrReusedValue == null && required) {
125             String parameterName = constructorParameter.getName();
126             String message = "Missing @Tested or @Injectable" + missingValueDescription(parameterName)
127                     + "\r\n  when initializing " + fullInjection;
128             IllegalStateException injectionFailure = new IllegalStateException(message);
129             StackTrace.filterStackTrace(injectionFailure);
130             throw injectionFailure;
131         }
132 
133         return newOrReusedValue;
134     }
135 
136     @NonNull
137     private Object getArgumentValueToInject(@NonNull InjectionProvider injectable, int parameterIndex) {
138         Object argument = injectionState.getValueToInject(injectable);
139 
140         if (argument == null) {
141             String classDesc = getClassDesc();
142             String constructorDesc = getConstructorDesc();
143             String parameterName = ParameterNames.getName(classDesc, constructorDesc, parameterIndex);
144 
145             if (parameterName == null) {
146                 parameterName = injectable.getName();
147             }
148 
149             throw new IllegalArgumentException(
150                     "No injectable value available" + missingValueDescription(parameterName));
151         }
152 
153         return argument;
154     }
155 
156     @NonNull
157     private String getClassDesc() {
158         return JavaType.getInternalName(constructor.getDeclaringClass());
159     }
160 
161     @NonNull
162     private String getConstructorDesc() {
163         return "<init>" + JavaType.getConstructorDescriptor(constructor);
164     }
165 
166     @NonNull
167     private Object obtainInjectedVarargsArray(@NonNull Type parameterType, @NonNull TestedClass testedClass) {
168         Type varargsElementType = getTypeOfInjectionPointFromVarargsParameter(parameterType);
169         KindOfInjectionPoint kindOfInjectionPoint = kindOfInjectionPoint(constructor);
170         InjectionProviders injectionProviders = injectionState.injectionProviders;
171         injectionProviders.setTypeOfInjectionPoint(varargsElementType, kindOfInjectionPoint);
172 
173         List<Object> varargValues = new ArrayList<>();
174         InjectionProvider injectable;
175 
176         while ((injectable = injectionProviders.findNextInjectableForInjectionPoint(testedClass)) != null) {
177             Object value = injectionState.getValueToInject(injectable);
178 
179             if (value != null) {
180                 value = wrapInProviderIfNeeded(varargsElementType, value);
181                 varargValues.add(value);
182             }
183         }
184 
185         return newArrayFromList(varargsElementType, varargValues);
186     }
187 
188     @NonNull
189     private static Object newArrayFromList(@NonNull Type elementType, @NonNull List<Object> values) {
190         Class<?> componentType = getClassType(elementType);
191         int elementCount = values.size();
192         Object array = Array.newInstance(componentType, elementCount);
193 
194         for (int i = 0; i < elementCount; i++) {
195             Array.set(array, i, values.get(i));
196         }
197 
198         return array;
199     }
200 
201     @NonNull
202     private String missingValueDescription(@NonNull String name) {
203         String classDesc = getClassDesc();
204         String constructorDesc = getConstructorDesc();
205         String constructorDescription = new MethodFormatter(classDesc, constructorDesc).toString();
206         int p = constructorDescription.indexOf('#');
207         // noinspection DynamicRegexReplaceableByCompiledPattern
208         String friendlyConstructorDesc = constructorDescription.substring(p + 1).replace("java.lang.", "");
209 
210         return " for parameter \"" + name + "\" in constructor " + friendlyConstructorDesc;
211     }
212 
213     @NonNull
214     private Object invokeConstructor(@NonNull Object[] arguments) {
215         TestRun.exitNoMockingZone();
216 
217         try {
218             return invokeAccessible(constructor, arguments);
219         } finally {
220             TestRun.enterNoMockingZone();
221         }
222     }
223 }