001package org.junit.internal;
002
003import java.lang.reflect.Array;
004import java.util.Arrays;
005
006import org.junit.Assert;
007
008/**
009 * Defines criteria for finding two items "equal enough". Concrete subclasses
010 * may demand exact equality, or, for example, equality within a given delta.
011 */
012public abstract class ComparisonCriteria {
013    /**
014     * Asserts that two arrays are equal, according to the criteria defined by
015     * the concrete subclass. If they are not, an {@link AssertionError} is
016     * thrown with the given message. If <code>expecteds</code> and
017     * <code>actuals</code> are <code>null</code>, they are considered equal.
018     *
019     * @param message the identifying message for the {@link AssertionError} (
020     * <code>null</code> okay)
021     * @param expecteds Object array or array of arrays (multi-dimensional array) with
022     * expected values.
023     * @param actuals Object array or array of arrays (multi-dimensional array) with
024     * actual values
025     */
026    public void arrayEquals(String message, Object expecteds, Object actuals)
027            throws ArrayComparisonFailure {
028        arrayEquals(message, expecteds, actuals, true);
029    }
030
031    private void arrayEquals(String message, Object expecteds, Object actuals, boolean outer)
032            throws ArrayComparisonFailure {
033        if (expecteds == actuals
034            || Arrays.deepEquals(new Object[] {expecteds}, new Object[] {actuals})) {
035            // The reflection-based loop below is potentially very slow, especially for primitive
036            // arrays. The deepEquals check allows us to circumvent it in the usual case where
037            // the arrays are exactly equal.
038            return;
039        }
040        String header = message == null ? "" : message + ": ";
041
042        // Only include the user-provided message in the outer exception.
043        String exceptionMessage = outer ? header : "";
044
045        if (expecteds == null) {
046            Assert.fail(exceptionMessage + "expected array was null");
047        }
048        if (actuals == null) {
049            Assert.fail(exceptionMessage + "actual array was null");
050        }
051
052        int actualsLength = Array.getLength(actuals);
053        int expectedsLength = Array.getLength(expecteds);
054        if (actualsLength != expectedsLength) {
055            header += "array lengths differed, expected.length="
056                    + expectedsLength + " actual.length=" + actualsLength + "; ";
057        }
058        int prefixLength = Math.min(actualsLength, expectedsLength);
059
060        for (int i = 0; i < prefixLength; i++) {
061            Object expected = Array.get(expecteds, i);
062            Object actual = Array.get(actuals, i);
063
064            if (isArray(expected) && isArray(actual)) {
065                try {
066                    arrayEquals(message, expected, actual, false);
067                } catch (ArrayComparisonFailure e) {
068                    e.addDimension(i);
069                    throw e;
070                } catch (AssertionError e) {
071                    // Array lengths differed.
072                    throw new ArrayComparisonFailure(header, e, i);
073                }
074            } else {
075                try {
076                    assertElementsEqual(expected, actual);
077                } catch (AssertionError e) {
078                    throw new ArrayComparisonFailure(header, e, i);
079                }
080            }
081        }
082
083        if (actualsLength != expectedsLength) {
084            Object expected = getToStringableArrayElement(expecteds, expectedsLength, prefixLength);
085            Object actual = getToStringableArrayElement(actuals, actualsLength, prefixLength);
086            try {
087                Assert.assertEquals(expected, actual);
088            } catch (AssertionError e) {
089                throw new ArrayComparisonFailure(header, e, prefixLength);
090            }
091        }
092    }
093
094    private static final Object END_OF_ARRAY_SENTINEL = objectWithToString("end of array");
095
096    private Object getToStringableArrayElement(Object array, int length, int index) {
097        if (index < length) {
098            Object element = Array.get(array, index);
099            if (isArray(element)) {
100                return objectWithToString(componentTypeName(element.getClass()) + "[" + Array.getLength(element) + "]");
101            } else {
102                return element;
103            }
104        } else {
105            return END_OF_ARRAY_SENTINEL;
106        }
107    }
108
109    private static Object objectWithToString(final String string) {
110        return new Object() {
111            @Override
112            public String toString() {
113                return string;
114            }
115        };
116    }
117
118    private String componentTypeName(Class<?> arrayClass) {
119        Class<?> componentType = arrayClass.getComponentType();
120        if (componentType.isArray()) {
121            return componentTypeName(componentType) + "[]";
122        } else {
123            return componentType.getName();
124        }
125    }
126
127    private boolean isArray(Object expected) {
128        return expected != null && expected.getClass().isArray();
129    }
130
131    protected abstract void assertElementsEqual(Object expected, Object actual);
132}