1
2
3
4
5 package mockit.internal.expectations.invocation;
6
7 import static mockit.internal.util.TypeDescriptor.getClassForType;
8
9 import edu.umd.cs.findbugs.annotations.NonNull;
10 import edu.umd.cs.findbugs.annotations.Nullable;
11
12 import java.io.IOException;
13 import java.util.List;
14 import java.util.Map;
15
16 import mockit.asm.types.JavaType;
17 import mockit.internal.expectations.argumentMatching.ArgumentMatcher;
18 import mockit.internal.expectations.state.MockedTypeCascade;
19 import mockit.internal.reflection.GenericTypeReflection;
20 import mockit.internal.reflection.GenericTypeReflection.GenericSignature;
21 import mockit.internal.state.TestRun;
22 import mockit.internal.util.ClassLoad;
23 import mockit.internal.util.DefaultValues;
24 import mockit.internal.util.ObjectMethods;
25 import mockit.internal.util.StackTrace;
26
27 import org.checkerframework.checker.index.qual.NonNegative;
28
29 @SuppressWarnings("OverlyComplexClass")
30 public final class ExpectedInvocation {
31 @NonNull
32 private static final Object UNDEFINED_DEFAULT_RETURN = new Object();
33
34 @Nullable
35 public final Object instance;
36 @Nullable
37 public Object replacementInstance;
38 public boolean matchInstance;
39 @NonNull
40 public final InvocationArguments arguments;
41 @Nullable
42 private final ExpectationError invocationCause;
43 @Nullable
44 Object defaultReturnValue;
45
46 public ExpectedInvocation(@Nullable Object mock, @NonNull String mockedClassDesc, @NonNull String mockNameAndDesc,
47 @Nullable String genericSignature, @NonNull Object[] args) {
48 instance = mock;
49 arguments = new InvocationArguments(0, mockedClassDesc, mockNameAndDesc, genericSignature, args);
50 invocationCause = null;
51 defaultReturnValue = determineDefaultReturnValueFromMethodSignature();
52 }
53
54 public ExpectedInvocation(@Nullable Object mock, int access, @NonNull String mockedClassDesc,
55 @NonNull String mockNameAndDesc, boolean matchInstance, @Nullable String genericSignature,
56 @NonNull Object[] args) {
57 instance = mock;
58 this.matchInstance = matchInstance;
59 arguments = new InvocationArguments(access, mockedClassDesc, mockNameAndDesc, genericSignature, args);
60 invocationCause = new ExpectationError();
61 defaultReturnValue = determineDefaultReturnValueFromMethodSignature();
62 }
63
64 @Nullable
65 public AssertionError getInvocationCause() {
66 return invocationCause;
67 }
68
69 @NonNull
70 private Object determineDefaultReturnValueFromMethodSignature() {
71 if (instance != null) {
72 Object rv = ObjectMethods.evaluateOverride(instance, getMethodNameAndDescription(), getArgumentValues());
73
74 if (rv != null) {
75 return rv;
76 }
77 }
78
79 return UNDEFINED_DEFAULT_RETURN;
80 }
81
82
83
84
85 @NonNull
86 public String getClassDesc() {
87 return arguments.classDesc;
88 }
89
90 @NonNull
91 public String getClassName() {
92 return arguments.getClassName();
93 }
94
95 @NonNull
96 public String getMethodNameAndDescription() {
97 return arguments.methodNameAndDesc;
98 }
99
100 @NonNull
101 public Object[] getArgumentValues() {
102 return arguments.getValues();
103 }
104
105 public boolean isConstructor() {
106 return arguments.isForConstructor();
107 }
108
109 @NonNull
110 public String getSignatureWithResolvedReturnType() {
111 String signature = arguments.genericSignature;
112
113 if (signature != null) {
114
115 String classDesc = getClassDesc();
116 Class<?> mockedClass = instance != null ? instance.getClass() : ClassLoad.loadByInternalName(classDesc);
117 GenericTypeReflection reflection = new GenericTypeReflection(mockedClass, null);
118 signature = reflection.resolveSignature(classDesc, signature);
119
120 char firstTypeChar = signature.charAt(signature.indexOf(')') + 1);
121
122 if (firstTypeChar != 'T' && firstTypeChar != '[') {
123 return signature;
124 }
125 }
126
127 return arguments.methodNameAndDesc;
128 }
129
130
131
132
133 public boolean isMatch(@Nullable Object mock, @NonNull String invokedClassDesc, @NonNull String invokedMethod) {
134 return (invokedClassDesc.equals(getClassDesc()) || mock != null && TestRun.mockFixture().isCaptured(mock))
135 && (isMatchingGenericMethod(mock, invokedMethod) || isMatchingMethod(invokedMethod));
136 }
137
138 private boolean isMatchingGenericMethod(@Nullable Object mock, @NonNull String invokedMethod) {
139 if (mock != null && instance != null) {
140 String genericSignature = arguments.genericSignature;
141
142 if (genericSignature != null) {
143 Class<?> mockedClass = mock.getClass();
144
145 if (mockedClass != instance.getClass()) {
146 GenericTypeReflection typeReflection = new GenericTypeReflection(mockedClass, null);
147 GenericSignature parsedSignature = typeReflection.parseSignature(genericSignature);
148 return parsedSignature.satisfiesSignature(invokedMethod) && isMatchingMethodName(invokedMethod);
149 }
150 }
151 }
152
153 return false;
154 }
155
156 private boolean isMatchingMethod(@NonNull String invokedMethod) {
157 int returnTypeStartPos = getReturnTypePosition(invokedMethod);
158
159 if (returnTypeStartPos < 0) {
160 return false;
161 }
162
163 if (haveSameReturnTypes(invokedMethod, returnTypeStartPos)) {
164 return true;
165 }
166
167
168
169 return isReturnTypeOfRecordedMethodAssignableToReturnTypeOfInvokedMethod(invokedMethod, returnTypeStartPos);
170 }
171
172 private boolean isMatchingMethodName(@NonNull String invokedMethod) {
173 int methodNameEndPos = invokedMethod.indexOf('(');
174 String methodName = invokedMethod.substring(0, methodNameEndPos + 1);
175 return getMethodNameAndDescription().startsWith(methodName);
176 }
177
178
179 private int getReturnTypePosition(@NonNull String invokedMethod) {
180 String recordedMethod = getMethodNameAndDescription();
181 int i = 0;
182
183 while (true) {
184 char c = recordedMethod.charAt(i);
185
186 if (c != invokedMethod.charAt(i)) {
187 return -1;
188 }
189
190 i++;
191
192 if (c == ')') {
193 return i;
194 }
195 }
196 }
197
198 private boolean haveSameReturnTypes(@NonNull String invokedMethod, @NonNegative int returnTypeStartPos) {
199 String recordedMethod = getMethodNameAndDescription();
200 int n = invokedMethod.length();
201
202 if (n != recordedMethod.length()) {
203 return false;
204 }
205
206 int j = returnTypeStartPos;
207
208 while (true) {
209 char c = recordedMethod.charAt(j);
210
211 if (c != invokedMethod.charAt(j)) {
212 return false;
213 }
214
215 j++;
216
217 if (j == n) {
218 return true;
219 }
220 }
221 }
222
223 private boolean isReturnTypeOfRecordedMethodAssignableToReturnTypeOfInvokedMethod(@NonNull String invokedMethod,
224 @NonNegative int returnTypeStartPos) {
225 String recordedMethod = getMethodNameAndDescription();
226 JavaType recordedRT = JavaType.getType(recordedMethod.substring(returnTypeStartPos));
227 JavaType invokedRT = JavaType.getType(invokedMethod.substring(returnTypeStartPos));
228
229 return getClassForType(invokedRT).isAssignableFrom(getClassForType(recordedRT));
230 }
231
232 public boolean isMatch(@NonNull ExpectedInvocation other) {
233 return isMatch(other.instance, other.getClassDesc(), other.getMethodNameAndDescription(), null);
234 }
235
236 public boolean isMatch(@Nullable Object replayInstance, @NonNull String invokedClassDesc,
237 @NonNull String invokedMethod, @Nullable Map<Object, Object> replacementMap) {
238 return isMatch(replayInstance, invokedClassDesc, invokedMethod) && (arguments.isForConstructor()
239 || !matchInstance || isEquivalentInstance(replayInstance, replacementMap));
240 }
241
242 private boolean isEquivalentInstance(@Nullable Object mockedInstance,
243 @Nullable Map<Object, Object> replacementMap) {
244 return mockedInstance == instance || mockedInstance != null && instance != null && replacementMap != null
245 && replacementMap.get(mockedInstance) == instance;
246 }
247
248
249
250
251 @NonNull
252 public UnexpectedInvocation errorForUnexpectedInvocation() {
253 String initialMessage = "Unexpected invocation of " + this;
254 return newUnexpectedInvocationWithCause("Unexpected invocation", initialMessage);
255 }
256
257 @NonNull
258 private UnexpectedInvocation newUnexpectedInvocationWithCause(@NonNull String titleForCause,
259 @NonNull String initialMessage) {
260 UnexpectedInvocation error = new UnexpectedInvocation(initialMessage);
261 setErrorAsInvocationCause(titleForCause, error);
262 return error;
263 }
264
265 private void setErrorAsInvocationCause(@NonNull String titleForCause, @NonNull Throwable error) {
266 if (invocationCause != null) {
267 invocationCause.defineCause(titleForCause, error);
268 }
269 }
270
271 @NonNull
272 public MissingInvocation errorForMissingInvocation(@NonNull List<ExpectedInvocation> nonMatchingInvocations) {
273 StringBuilder errorMessage = new StringBuilder(200);
274 errorMessage.append("Missing invocation to:\n").append(this);
275 appendNonMatchingInvocations(errorMessage, nonMatchingInvocations);
276
277 return newMissingInvocationWithCause("Missing invocation", errorMessage.toString());
278 }
279
280 @NonNull
281 public MissingInvocation errorForMissingInvocations(@NonNegative int missingInvocations,
282 @NonNull List<ExpectedInvocation> nonMatchingInvocations) {
283 StringBuilder errorMessage = new StringBuilder(200);
284 errorMessage.append("Missing ").append(missingInvocations).append(invocationsTo(missingInvocations))
285 .append(this);
286 appendNonMatchingInvocations(errorMessage, nonMatchingInvocations);
287
288 return newMissingInvocationWithCause("Missing invocations", errorMessage.toString());
289 }
290
291 private void appendNonMatchingInvocations(@NonNull StringBuilder errorMessage,
292 @NonNull List<ExpectedInvocation> nonMatchingInvocations) {
293 if (!nonMatchingInvocations.isEmpty()) {
294 errorMessage.append("\ninstead got:\n");
295 String sep = "";
296
297 for (ExpectedInvocation nonMatchingInvocation : nonMatchingInvocations) {
298 String invocationDescription = nonMatchingInvocation.toString(instance);
299 errorMessage.append(sep).append(invocationDescription);
300 sep = "\n";
301 nonMatchingInvocation.printCause(errorMessage);
302 }
303 }
304 }
305
306 @NonNull
307 private MissingInvocation newMissingInvocationWithCause(@NonNull String titleForCause,
308 @NonNull String initialMessage) {
309 MissingInvocation error = new MissingInvocation(initialMessage);
310 setErrorAsInvocationCause(titleForCause, error);
311 return error;
312 }
313
314 @NonNull
315 private static String invocationsTo(@NonNegative int invocations) {
316 return invocations == 1 ? " invocation to:\n" : " invocations to:\n";
317 }
318
319 @NonNull
320 public UnexpectedInvocation errorForUnexpectedInvocation(@NonNull Object[] replayArgs) {
321 String message = "Unexpected invocation to:\n" + toString(replayArgs);
322 return newUnexpectedInvocationWithCause("Unexpected invocation", message);
323 }
324
325 @NonNull
326 public UnexpectedInvocation errorForUnexpectedInvocations(@NonNull Object[] replayArgs, int numUnexpected) {
327 String message = numUnexpected + " unexpected" + invocationsTo(numUnexpected) + toString(replayArgs);
328 String titleForCause = numUnexpected == 1 ? "Unexpected invocation" : "Unexpected invocations";
329 return newUnexpectedInvocationWithCause(titleForCause, message);
330 }
331
332 @NonNull
333 @Override
334 public String toString() {
335 return toString((Object) null);
336 }
337
338 @NonNull
339 public String toString(@Nullable Object otherInstance) {
340 StringBuilder desc = new StringBuilder().append(arguments.toString());
341
342 if (instance != otherInstance && instance != null) {
343 desc.append("\n on mock instance: ").append(ObjectMethods.objectIdentity(instance));
344 }
345
346 return desc.toString();
347 }
348
349 @NonNull
350 String toString(@NonNull Object[] actualInvocationArguments) {
351 Object[] invocationArgs = arguments.getValues();
352 List<ArgumentMatcher<?>> matchers = arguments.getMatchers();
353 arguments.setValues(actualInvocationArguments);
354 arguments.setMatchers(null);
355 String description = toString();
356 arguments.setMatchers(matchers);
357 arguments.setValues(invocationArgs);
358 return description;
359 }
360
361 private void printCause(@NonNull Appendable errorMessage) {
362 if (invocationCause != null) {
363 try {
364 errorMessage.append('\n');
365 } catch (IOException ignore) {
366 }
367
368 StackTrace st = new StackTrace(invocationCause);
369 st.filter();
370 st.print(errorMessage);
371 }
372 }
373
374
375
376
377 @Nullable
378 public Object getDefaultValueForReturnType() {
379 if (defaultReturnValue == UNDEFINED_DEFAULT_RETURN) {
380 Class<?> resolvedReturnType = getReturnTypeAsResolvedFromClassArgument();
381
382 if (resolvedReturnType != null) {
383 defaultReturnValue = DefaultValues.computeForType(resolvedReturnType);
384
385 if (defaultReturnValue == null) {
386 String returnTypeDesc = 'L' + resolvedReturnType.getName().replace('.', '/') + ';';
387 String mockedTypeDesc = getClassDesc();
388 defaultReturnValue = MockedTypeCascade.getMock(mockedTypeDesc, arguments.methodNameAndDesc,
389 instance, returnTypeDesc, resolvedReturnType);
390 }
391
392 return defaultReturnValue;
393 }
394
395 String returnTypeDesc = DefaultValues.getReturnTypeDesc(arguments.methodNameAndDesc);
396
397 if ("V".equals(returnTypeDesc)) {
398 return null;
399 }
400
401 defaultReturnValue = DefaultValues.computeForType(returnTypeDesc);
402
403 if (defaultReturnValue == null) {
404 String mockedTypeDesc = getClassDesc();
405 defaultReturnValue = MockedTypeCascade.getMock(mockedTypeDesc, arguments.methodNameAndDesc, instance,
406 returnTypeDesc, arguments.genericSignature);
407 }
408 }
409
410 return defaultReturnValue;
411 }
412
413 @Nullable
414 private Class<?> getReturnTypeAsResolvedFromClassArgument() {
415 String genericSignature = arguments.genericSignature;
416
417 if (genericSignature != null) {
418 int returnTypePos = genericSignature.lastIndexOf(')') + 1;
419 char c = genericSignature.charAt(returnTypePos);
420
421 if (c == 'T') {
422 for (Object arg : arguments.getValues()) {
423 if (arg instanceof Class<?>) {
424 return (Class<?>) arg;
425 }
426 }
427 }
428 }
429
430 return null;
431 }
432
433 public void copyDefaultReturnValue(@NonNull ExpectedInvocation other) {
434 defaultReturnValue = other.defaultReturnValue;
435 }
436 }