package org.junit.internal; import java.lang.reflect.Array; import java.util.Arrays; import org.junit.Assert; /** * Defines criteria for finding two items "equal enough". Concrete subclasses * may demand exact equality, or, for example, equality within a given delta. */ public abstract class ComparisonCriteria { /** * Asserts that two arrays are equal, according to the criteria defined by * the concrete subclass. If they are not, an {@link AssertionError} is * thrown with the given message. If <code>expecteds</code> and * <code>actuals</code> are <code>null</code>, they are considered equal. * * @param message the identifying message for the {@link AssertionError} ( * <code>null</code> okay) * @param expecteds Object array or array of arrays (multi-dimensional array) with * expected values. * @param actuals Object array or array of arrays (multi-dimensional array) with * actual values */ public void arrayEquals(String message, Object expecteds, Object actuals) throws ArrayComparisonFailure { arrayEquals(message, expecteds, actuals, true); } private void arrayEquals(String message, Object expecteds, Object actuals, boolean outer) throws ArrayComparisonFailure { if (expecteds == actuals || Arrays.deepEquals(new Object[] {expecteds}, new Object[] {actuals})) { // The reflection-based loop below is potentially very slow, especially for primitive // arrays. The deepEquals check allows us to circumvent it in the usual case where // the arrays are exactly equal. return; } String header = message == null ? "" : message + ": "; // Only include the user-provided message in the outer exception. String exceptionMessage = outer ? header : ""; if (expecteds == null) { Assert.fail(exceptionMessage + "expected array was null"); } if (actuals == null) { Assert.fail(exceptionMessage + "actual array was null"); } int actualsLength = Array.getLength(actuals); int expectedsLength = Array.getLength(expecteds); if (actualsLength != expectedsLength) { header += "array lengths differed, expected.length=" + expectedsLength + " actual.length=" + actualsLength + "; "; } int prefixLength = Math.min(actualsLength, expectedsLength); for (int i = 0; i < prefixLength; i++) { Object expected = Array.get(expecteds, i); Object actual = Array.get(actuals, i); if (isArray(expected) && isArray(actual)) { try { arrayEquals(message, expected, actual, false); } catch (ArrayComparisonFailure e) { e.addDimension(i); throw e; } catch (AssertionError e) { // Array lengths differed. throw new ArrayComparisonFailure(header, e, i); } } else { try { assertElementsEqual(expected, actual); } catch (AssertionError e) { throw new ArrayComparisonFailure(header, e, i); } } } if (actualsLength != expectedsLength) { Object expected = getToStringableArrayElement(expecteds, expectedsLength, prefixLength); Object actual = getToStringableArrayElement(actuals, actualsLength, prefixLength); try { Assert.assertEquals(expected, actual); } catch (AssertionError e) { throw new ArrayComparisonFailure(header, e, prefixLength); } } } private static final Object END_OF_ARRAY_SENTINEL = objectWithToString("end of array"); private Object getToStringableArrayElement(Object array, int length, int index) { if (index < length) { Object element = Array.get(array, index); if (isArray(element)) { return objectWithToString(componentTypeName(element.getClass()) + "[" + Array.getLength(element) + "]"); } else { return element; } } else { return END_OF_ARRAY_SENTINEL; } } private static Object objectWithToString(final String string) { return new Object() { @Override public String toString() { return string; } }; } private String componentTypeName(Class<?> arrayClass) { Class<?> componentType = arrayClass.getComponentType(); if (componentType.isArray()) { return componentTypeName(componentType) + "[]"; } else { return componentType.getName(); } } private boolean isArray(Object expected) { return expected != null && expected.getClass().isArray(); } protected abstract void assertElementsEqual(Object expected, Object actual); }