1
2
3
4
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
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 }