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