/* * Copyright (c) 2006-2011 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.internal.expectations.invocation; import java.lang.reflect.*; import java.util.*; import mockit.external.asm.*; import mockit.external.hamcrest.*; import mockit.external.hamcrest.core.*; import mockit.internal.util.*; public final class InvocationArguments { private static final Object[] NULL_VARARGS = new Object[0]; private static final Matcher<?> ANYTHING = new IsAnything(); final String classDesc; final String methodNameAndDesc; final String genericSignature; final String[] exceptions; private final int methodAccess; private Object[] invocationArgs; private List<Matcher<?>> matchers; InvocationArguments( int access, String classDesc, String methodNameAndDesc, String genericSignature, String exceptions, Object[] args) { methodAccess = access; this.classDesc = classDesc; this.methodNameAndDesc = methodNameAndDesc; this.genericSignature = genericSignature; this.exceptions = exceptions == null ? null : exceptions.split(" "); invocationArgs = args; } public String getGenericSignature() { return genericSignature == null ? methodNameAndDesc : genericSignature; } public Object[] getValues() { return invocationArgs; } public void setValuesWithNoMatchers(Object[] argsToVerify) { invocationArgs = argsToVerify; matchers = null; } public List<Matcher<?>> getMatchers() { return matchers; } public void setMatchers(List<Matcher<?>> matchers) { this.matchers = matchers; } public Object[] prepareForVerification(Object[] argsToVerify, List<Matcher<?>> matchers) { Object[] replayArgs = invocationArgs; invocationArgs = argsToVerify; this.matchers = matchers; return replayArgs; } public boolean isMatch(Object[] replayArgs, Map<Object, Object> instanceMap) { if (matchers == null) { return areEqual(replayArgs, instanceMap); } int argCount = replayArgs.length; Object[] replayVarArgs = replayArgs; Object[] invocationVarArgs = invocationArgs; int varArgsCount = 0; if (isVarargsMethod()) { invocationVarArgs = getVarArgs(invocationArgs); replayVarArgs = getVarArgs(replayArgs); if (invocationVarArgs != NULL_VARARGS) { varArgsCount = replayVarArgs.length; if (varArgsCount != invocationVarArgs.length) { return false; } } argCount--; } int n = argCount + varArgsCount; for (int i = 0; i < n; i++) { Object actual = getArgument(replayArgs, replayVarArgs, argCount, i); Matcher<?> expected = i < matchers.size() ? matchers.get(i) : null; if (expected == null) { Object arg = getArgument(invocationArgs, invocationVarArgs, argCount, i); expected = arg == null ? ANYTHING : new IsEqual<Object>(arg); } if (!expected.matches(actual)) { return false; } } return true; } private boolean areEqual(Object[] replayArgs, Map<Object, Object> instanceMap) { int argCount = replayArgs.length; if (!isVarargsMethod()) { return areEqual(invocationArgs, replayArgs, argCount, instanceMap); } if (!areEqual(invocationArgs, replayArgs, argCount - 1, instanceMap)) { return false; } Object[] expectedValues = getVarArgs(invocationArgs); Object[] actualValues = getVarArgs(replayArgs); return expectedValues.length == actualValues.length && areEqual(expectedValues, actualValues, expectedValues.length, instanceMap); } private boolean isVarargsMethod() { return (methodAccess & Opcodes.ACC_VARARGS) != 0; } private Object[] getVarArgs(Object[] args) { Object lastArg = args[args.length - 1]; if (lastArg == null) { return NULL_VARARGS; } else if (lastArg instanceof Object[]) { return (Object[]) lastArg; } int varArgsLength = Array.getLength(lastArg); Object[] results = new Object[varArgsLength]; for (int i = 0; i < varArgsLength; i++) { results[i] = Array.get(lastArg, i); } return results; } private boolean areEqual(Object[] expectedValues, Object[] actualValues, int count, Map<Object, Object> instanceMap) { for (int i = 0; i < count; i++) { if (isNotEqual(expectedValues[i], actualValues[i], instanceMap)) { return false; } } return true; } private boolean isNotEqual(Object expected, Object actual, Map<Object, Object> instanceMap) { return actual == null && expected != null || actual != null && expected == null || actual != null && actual != expected && actual != instanceMap.get(expected) && !IsEqual.areEqual(actual, expected); } private Object getArgument(Object[] regularArgs, Object[] varArgs, int regularArgCount, int i) { return i < regularArgCount ? regularArgs[i] : varArgs[i - regularArgCount]; } public AssertionError assertMatch(Object[] replayArgs, Map<Object, Object> instanceMap) { if (matchers == null) { return assertEquality(replayArgs, instanceMap); } int argCount = replayArgs.length; Object[] replayVarArgs = replayArgs; Object[] invocationVarArgs = invocationArgs; int varArgsCount = 0; if (isVarargsMethod()) { invocationVarArgs = getVarArgs(invocationArgs); replayVarArgs = getVarArgs(replayArgs); if (invocationVarArgs != NULL_VARARGS) { varArgsCount = replayVarArgs.length; if (varArgsCount != invocationVarArgs.length) { return errorForVarargsArraysOfDifferentLengths(invocationVarArgs, replayVarArgs); } } argCount--; } int n = argCount + varArgsCount; for (int i = 0; i < n; i++) { Object actual = getArgument(replayArgs, replayVarArgs, argCount, i); Matcher<?> expected = i < matchers.size() ? matchers.get(i) : null; if (expected == null) { Object arg = getArgument(invocationArgs, invocationVarArgs, argCount, i); expected = arg == null ? ANYTHING : new IsEqual<Object>(arg); } if (!expected.matches(actual)) { return argumentMismatchMessage(i, expected, actual); } } return null; } private AssertionError assertEquality(Object[] replayArgs, Map<Object, Object> instanceMap) { int argCount = replayArgs.length; if (!isVarargsMethod()) { return assertEquals(invocationArgs, replayArgs, argCount, instanceMap); } AssertionError nonVarargsError = assertEquals(invocationArgs, replayArgs, argCount - 1, instanceMap); if (nonVarargsError != null) { return nonVarargsError; } Object[] expectedValues = getVarArgs(invocationArgs); Object[] actualValues = getVarArgs(replayArgs); if (expectedValues.length != actualValues.length) { return errorForVarargsArraysOfDifferentLengths(expectedValues, actualValues); } AssertionError varargsError = assertEquals(expectedValues, actualValues, expectedValues.length, instanceMap); if (varargsError != null) { return new AssertionError("Varargs " + varargsError); } return null; } private AssertionError errorForVarargsArraysOfDifferentLengths(Object[] expectedValues, Object[] actualValues) { return new AssertionError( "Expected " + expectedValues.length + " values for varargs parameter, got " + actualValues.length); } private AssertionError assertEquals( Object[] expectedValues, Object[] actualValues, int count, Map<Object, Object> instanceMap) { for (int i = 0; i < count; i++) { Object expected = expectedValues[i]; Object actual = actualValues[i]; if (isNotEqual(expected, actual, instanceMap)) { return argumentMismatchMessage(i, expected, actual); } } return null; } private AssertionError argumentMismatchMessage(int paramIndex, Object expected, Object actual) { StringBuilder message = new StringBuilder(50); message.append("Parameter ").append(paramIndex); message.append(" of ").append(new MethodFormatter(classDesc, methodNameAndDesc)); message.append(" expected "); appendParameterValue(message, expected); message.append(", got "); appendParameterValue(message, actual); return new AssertionError(message.toString()); } private void appendParameterValue(StringBuilder message, Object parameterValue) { if (parameterValue == null) { message.append("null"); } else if (parameterValue instanceof CharSequence || parameterValue instanceof Appendable) { message.append('"').append(parameterValue).append('"'); } else if (parameterValue instanceof Character) { message.append('\'').append(parameterValue).append('\''); } else { message.append(parameterValue); } } @Override public String toString() { StringBuilder desc = new StringBuilder(30); desc.append(":\n").append(new MethodFormatter(classDesc, methodNameAndDesc)); if (invocationArgs.length > 0) { desc.append("\nwith arguments: "); String sep = ""; for (Object arg : invocationArgs) { desc.append(sep); appendParameterValue(desc, arg); sep = ", "; } } return desc.toString(); } public boolean hasEquivalentMatchers(InvocationArguments other) { List<Matcher<?>> otherMatchers = other.matchers; if (otherMatchers == null || otherMatchers.size() != matchers.size()) { return false; } for (int i = 0; i < matchers.size(); i++) { Matcher<?> matcher = matchers.get(i); Matcher<?> otherMatcher = otherMatchers.get(i); if ( matcher != otherMatcher && (matcher.getClass() != otherMatcher.getClass() || matcher.matches(other.invocationArgs[i]) != otherMatcher.matches(invocationArgs[i])) ) { return false; } } return true; } }