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