/**
* Copyright 2010 Wealthfront Inc. Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
* or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package com.kaching.platform.testing;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.List;
/**
* Equivalence tester streamlining tests of {@link #equals} and
* {@link #hashCode} methods. Using this tester makes it easy to verify that
* {@link #equals} is indeed an
* <a href="http://en.wikipedia.org/wiki/Equivalence_relation">equivalence relation</a>
* (reflexive, symmetric and transitive). It also verifies that
* equality between two objects implies hash code equality, as required by the
* {@link #hashCode()} contract.
*/
public final class EquivalenceTester {
private EquivalenceTester() {}
public static void check(Collection<?>... equivalenceClasses) {
List<List<Object>> ec =
newArrayListWithExpectedSize(equivalenceClasses.length);
// nothing can be equal to null
for (Collection<? extends Object> congruenceClass : equivalenceClasses) {
for (Object element : congruenceClass) {
try {
assertFalse(
format("%s can not be equal to null", element),
element.equals(null));
} catch (NullPointerException e) {
throw new AssertionError(
format("NullPointerException when comparing %s to null", element));
}
}
}
// reflexivity
for (Collection<? extends Object> congruenceClass : equivalenceClasses) {
List<Object> c = newArrayList();
ec.add(c);
for (Object element : congruenceClass) {
assertTrue(format("reflexivity of %s", element),
element.equals(element));
compareShouldReturn0(element, element);
c.add(element);
}
}
// equality within congruence classes
for (List<Object> c : ec) {
for (int i = 0; i < c.size(); i++) {
Object e1 = c.get(i);
for (int j = i + 1; j < c.size(); j++) {
Object e2 = c.get(j);
assertTrue(format("%s=%s", e1, e2), e1.equals(e2));
assertTrue(format("%s=%s", e2, e1), e2.equals(e1));
compareShouldReturn0(e1, e2);
compareShouldReturn0(e2, e1);
assertEquals(format("hashCode %s vs. %s", e1, e2), e1.hashCode(), e2.hashCode());
}
}
}
// inequality across congruence classes
for (int i = 0; i < ec.size(); i++) {
List<Object> c1 = ec.get(i);
for (int j = i + 1; j < ec.size(); j++) {
List<Object> c2 = ec.get(j);
for (Object e1 : c1) {
for (Object e2 : c2) {
assertFalse(format("%s!=%s", e1, e2), e1.equals(e2));
assertFalse(format("%s!=%s", e2, e1), e2.equals(e1));
compareShouldNotReturn0(e1, e2);
compareShouldNotReturn0(e2, e1);
}
}
}
}
}
@SuppressWarnings("unchecked")
private static void compareShouldReturn0(Object e1, Object e2) {
if (e1 instanceof Comparable<?> &&
e2 instanceof Comparable<?>) {
assertTrue(format("comparison should return 0 for %s and %s", e1, e2),
((Comparable<Object>) e1).compareTo(e2) == 0);
}
}
@SuppressWarnings("unchecked")
private static void compareShouldNotReturn0(Object e1, Object e2) {
if (e1 instanceof Comparable<?> &&
e2 instanceof Comparable<?>) {
assertFalse(format("comparison should not return 0 for %s and %s", e1, e2),
((Comparable<Object>) e1).compareTo(e2) == 0);
}
}
}