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}