Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.airlift.testing.EquivalenceTester Maven / Gradle / Ivy
/*
* Copyright 2010 Proofpoint, 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 io.airlift.testing;
/**
* Derived from http://code.google.com/p/kawala
*
* Licensed under Apache License, Version 2.0
*/
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.util.Collection;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.COMPARE_CLASS_CAST_EXCEPTION;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.COMPARE_EQUAL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.COMPARE_EQUAL_TO_NULL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.COMPARE_NOT_EQUAL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.COMPARE_NOT_REFLEXIVE;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.EQUAL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.EQUAL_NULL_EXCEPTION;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.EQUAL_TO_NULL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.EQUAL_TO_UNRELATED_CLASS;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.EQUAL_TO_UNRELATED_CLASS_CLASS_CAST_EXCEPTION;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.HASH_CODE_NOT_SAME;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.NOT_EQUAL;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.NOT_GREATER_THAN;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.NOT_LESS_THAN;
import static io.airlift.testing.EquivalenceTester.EquivalenceFailureType.NOT_REFLEXIVE;
import static java.lang.String.format;
/**
* Equivalence tester streamlining tests of {@link #equals(Object)} and {@link #hashCode} methods. Using this tester makes it
* easy to verify that {@link #equals(Object)} is indeed an equivalence
* relation (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
{
@Deprecated
public static void check(Collection>... equivalenceClasses)
{
EquivalenceCheck tester = equivalenceTester();
for (Collection> equivalenceClass : equivalenceClasses) {
tester.addEquivalentGroup((Iterable)equivalenceClass);
}
tester.check();
}
public static EquivalenceCheck equivalenceTester()
{
return new EquivalenceCheck();
}
public static class EquivalenceCheck
{
private final List> equivalenceClasses = newArrayList();
private EquivalenceCheck()
{
}
public EquivalenceCheck addEquivalentGroup(T value, T... moreValues)
{
equivalenceClasses.add(Lists.asList(value, moreValues));
return this;
}
public EquivalenceCheck addEquivalentGroup(Iterable objects)
{
equivalenceClasses.add(newArrayList(objects));
return this;
}
public void check()
{
List failures = checkEquivalence();
if (!failures.isEmpty()) {
throw new EquivalenceAssertionError(failures);
}
}
@SuppressWarnings({"ObjectEqualsNull"})
private List checkEquivalence()
{
ImmutableList.Builder errors = new ImmutableList.Builder();
//
// equal(null)
//
int classNumber = 0;
for (Collection> congruenceClass : equivalenceClasses) {
int elementNumber = 0;
for (Object element : congruenceClass) {
// nothing can be equal to null
try {
if (element.equals(null)) {
errors.add(new ElementCheckFailure(EQUAL_TO_NULL, classNumber, elementNumber, element));
}
}
catch (NullPointerException e) {
errors.add(new ElementCheckFailure(EQUAL_NULL_EXCEPTION, classNumber, elementNumber, element));
}
// if a class implements comparable, object.compareTo(null) must throw NPE
if (element instanceof Comparable) {
try {
((Comparable>) element).compareTo(null);
errors.add(new ElementCheckFailure(COMPARE_EQUAL_TO_NULL, classNumber, elementNumber, element));
}
catch (NullPointerException e) {
// ok
}
}
// nothing can be equal to object of an unrelated class
try {
if (element.equals(new OtherClass())) {
errors.add(new ElementCheckFailure(EQUAL_TO_UNRELATED_CLASS, classNumber, elementNumber, element));
}
} catch (ClassCastException e) {
errors.add(new ElementCheckFailure(EQUAL_TO_UNRELATED_CLASS_CLASS_CAST_EXCEPTION, classNumber, elementNumber, element));
}
++elementNumber;
}
++classNumber;
}
//
// reflexivity
//
classNumber = 0;
for (Collection> congruenceClass : equivalenceClasses) {
int elementNumber = 0;
for (Object element : congruenceClass) {
if (!element.equals(element)) {
errors.add(new ElementCheckFailure(NOT_REFLEXIVE, classNumber, elementNumber, element));
}
if (!doesCompareReturn0(element, element)) {
errors.add(new ElementCheckFailure(COMPARE_NOT_REFLEXIVE, classNumber, elementNumber, element));
}
++elementNumber;
}
++classNumber;
}
//
// equality within congruence classes
//
classNumber = 0;
for (List> congruenceClass : equivalenceClasses) {
for (int primaryElementNumber = 0; primaryElementNumber < congruenceClass.size(); primaryElementNumber++) {
Object primary = congruenceClass.get(primaryElementNumber);
for (int secondaryElementNumber = primaryElementNumber + 1; secondaryElementNumber < congruenceClass.size(); secondaryElementNumber++) {
Object secondary = congruenceClass.get(secondaryElementNumber);
if (!primary.equals(secondary)) {
errors.add(new PairCheckFailure(NOT_EQUAL, classNumber, primaryElementNumber, primary, classNumber, secondaryElementNumber, secondary));
}
if (!secondary.equals(primary)) {
errors.add(new PairCheckFailure(NOT_EQUAL, classNumber, secondaryElementNumber, secondary, classNumber, primaryElementNumber, primary));
}
try {
if (!doesCompareReturn0(primary, secondary)) {
errors.add(new PairCheckFailure(COMPARE_NOT_EQUAL,
classNumber,
primaryElementNumber,
primary,
classNumber,
secondaryElementNumber,
secondary));
}
}
catch (ClassCastException e) {
errors.add(new PairCheckFailure(COMPARE_CLASS_CAST_EXCEPTION, classNumber, primaryElementNumber, primary, classNumber, secondaryElementNumber, secondary));
}
try {
if (!doesCompareReturn0(secondary, primary)) {
errors.add(new PairCheckFailure(COMPARE_NOT_EQUAL,
classNumber,
secondaryElementNumber,
secondary,
classNumber,
primaryElementNumber,
primary));
}
}
catch (ClassCastException e) {
errors.add(new PairCheckFailure(COMPARE_CLASS_CAST_EXCEPTION, classNumber, secondaryElementNumber, secondary, classNumber, primaryElementNumber, primary));
}
if (primary.hashCode() != secondary.hashCode()) {
errors.add(new PairCheckFailure(HASH_CODE_NOT_SAME, classNumber, primaryElementNumber, primary, classNumber, secondaryElementNumber, secondary));
}
}
}
++classNumber;
}
//
// inequality across congruence classes
//
for (int primaryClassNumber = 0; primaryClassNumber < equivalenceClasses.size(); primaryClassNumber++) {
List> primaryCongruenceClass = equivalenceClasses.get(primaryClassNumber);
for (int secondaryClassNumber = primaryClassNumber + 1; secondaryClassNumber < equivalenceClasses.size(); secondaryClassNumber++) {
List> secondaryCongruenceClass = equivalenceClasses.get(secondaryClassNumber);
int primaryElementNumber = 0;
for (Object primary : primaryCongruenceClass) {
int secondaryElementNumber = 0;
for (Object secondary : secondaryCongruenceClass) {
if (primary.equals(secondary)) {
errors.add(new PairCheckFailure(EQUAL, primaryClassNumber, primaryElementNumber, primary, secondaryClassNumber, secondaryElementNumber, secondary));
}
if (secondary.equals(primary)) {
errors.add(new PairCheckFailure(EQUAL, secondaryClassNumber, secondaryElementNumber, secondary, primaryClassNumber, primaryElementNumber, primary));
}
try {
if (!doesCompareNotReturn0(primary, secondary)) {
errors.add(new PairCheckFailure(COMPARE_EQUAL, primaryClassNumber, primaryElementNumber, primary, secondaryClassNumber, secondaryElementNumber, secondary));
}
}
catch (ClassCastException e) {
errors.add(new PairCheckFailure(COMPARE_CLASS_CAST_EXCEPTION,
primaryClassNumber,
primaryElementNumber,
primary,
secondaryClassNumber,
secondaryElementNumber,
secondary));
}
try {
if (!doesCompareNotReturn0(secondary, primary)) {
errors.add(new PairCheckFailure(COMPARE_EQUAL, secondaryClassNumber, secondaryElementNumber, secondary, primaryClassNumber, primaryElementNumber, primary));
}
}
catch (ClassCastException e) {
errors.add(new PairCheckFailure(COMPARE_CLASS_CAST_EXCEPTION,
secondaryClassNumber,
secondaryElementNumber,
secondary,
primaryClassNumber,
primaryElementNumber,
primary));
}
secondaryElementNumber++;
}
primaryElementNumber++;
}
}
}
return errors.build();
}
@SuppressWarnings("unchecked")
private static boolean doesCompareReturn0(T e1, T e2)
{
if (!(e1 instanceof Comparable>)) {
return true;
}
Comparable comparable = (Comparable) e1;
return comparable.compareTo(e2) == 0;
}
@SuppressWarnings("unchecked")
private static boolean doesCompareNotReturn0(T e1, T e2)
{
if (!(e1 instanceof Comparable>)) {
return true;
}
Comparable comparable = (Comparable) e1;
return comparable.compareTo(e2) != 0;
}
private static class OtherClass
{
}
}
@SafeVarargs
@Deprecated
public static > void checkComparison(Iterable initialGroup, Iterable greaterGroup, Iterable... moreGreaterGroup)
{
ComparisonCheck tester = comparisonTester()
.addLesserGroup(initialGroup)
.addGreaterGroup(greaterGroup);
for (Iterable equivalenceClass : moreGreaterGroup) {
tester.addGreaterGroup(equivalenceClass);
}
tester.check();
}
public static InitialComparisonCheck comparisonTester()
{
return new InitialComparisonCheck();
}
public static class InitialComparisonCheck
{
private InitialComparisonCheck()
{
}
public > ComparisonCheck addLesserGroup(T value, T... moreValues)
{
ComparisonCheck comparisonCheck = new ComparisonCheck();
comparisonCheck.addGreaterGroup(Lists.asList(value, moreValues));
return comparisonCheck;
}
public > ComparisonCheck addLesserGroup(Iterable objects)
{
ComparisonCheck comparisonCheck = new ComparisonCheck();
comparisonCheck.addGreaterGroup(objects);
return comparisonCheck;
}
}
public static class ComparisonCheck >
{
private final EquivalenceCheck equivalence = new EquivalenceCheck();
private ComparisonCheck()
{
}
public ComparisonCheck addGreaterGroup(T value, T... moreValues)
{
equivalence.addEquivalentGroup(Lists.asList(value, moreValues));
return this;
}
public ComparisonCheck addGreaterGroup(Iterable objects)
{
equivalence.addEquivalentGroup(objects);
return this;
}
public void check()
{
ImmutableList.Builder builder = new ImmutableList.Builder();
builder.addAll(equivalence.checkEquivalence());
List> equivalenceClasses = equivalence.equivalenceClasses;
for (int lesserClassNumber = 0; lesserClassNumber < equivalenceClasses.size(); lesserClassNumber++) {
List lesserBag = equivalenceClasses.get(lesserClassNumber);
for (int greaterClassNumber = lesserClassNumber + 1; greaterClassNumber < equivalenceClasses.size(); greaterClassNumber++) {
List greaterBag = equivalenceClasses.get(greaterClassNumber);
for (int lesserElementNumber = 0; lesserElementNumber < lesserBag.size(); lesserElementNumber++) {
T lesser = lesserBag.get(lesserElementNumber);
for (int greaterElementNumber = 0; greaterElementNumber < greaterBag.size(); greaterElementNumber++) {
T greater = greaterBag.get(greaterElementNumber);
try {
if (lesser.compareTo(greater) >= 0) {
builder.add(new PairCheckFailure(NOT_LESS_THAN, lesserClassNumber, lesserElementNumber, lesser, greaterClassNumber, greaterElementNumber, greater));
}
}
catch (ClassCastException e) {
// this has already been reported in the checkEquivalence section
}
try {
if (greater.compareTo(lesser) <= 0) {
builder.add(new PairCheckFailure(NOT_GREATER_THAN, greaterClassNumber, greaterElementNumber, greater, lesserClassNumber, lesserElementNumber, lesser));
}
}
catch (ClassCastException e) {
// this has already been reported in the checkEquivalence section
}
}
}
}
}
List failures = builder.build();
if (!failures.isEmpty()) {
throw new EquivalenceAssertionError(failures);
}
}
}
public static enum EquivalenceFailureType {
EQUAL_TO_NULL("Element (%d, %d):<%s> returns true when compared to null via equals()"),
EQUAL_NULL_EXCEPTION("Element (%d, %d):<%s> throws NullPointerException when when compared to null via equals()"),
COMPARE_EQUAL_TO_NULL("Element (%d, %d):<%s> implements Comparable but does not throw NullPointerException when compared to null"),
EQUAL_TO_UNRELATED_CLASS("Element (%d, %d):<%s> returns true when compared to an unrelated class via equals()"),
EQUAL_TO_UNRELATED_CLASS_CLASS_CAST_EXCEPTION("Element (%d, %d):<%s> throws a ClassCastException when compared to an unrelated class via equals()"),
NOT_REFLEXIVE("Element (%d, %d):<%s> is not equal to itself when compared via equals()"),
COMPARE_NOT_REFLEXIVE("Element (%d, %d):<%s> implements Comparable but compare does not return 0 when compared to itself"),
NOT_EQUAL("Element (%d, %d):<%s> is not equal to element (%d, %d):<%s> when compared via equals()"),
COMPARE_NOT_EQUAL("Element (%d, %d):<%s> is not equal to element (%d, %d):<%s> when compared via compareTo(T)"),
COMPARE_CLASS_CAST_EXCEPTION("Element (%d, %d):<%s> throws a ClassCastException when compared to element (%d, %d):<%s> via compareTo(T)"),
HASH_CODE_NOT_SAME("Elements (%d, %d):<%s> and (%d, %d):<%s> have different hash codes"),
EQUAL("Element (%d, %d):<%s> is equal to element (%d, %d):<%s> when compared via equals()"),
COMPARE_EQUAL("Element (%d, %d):<%s> implements Comparable and returns 0 when compared to element (%d, %d):<%s>"),
NOT_LESS_THAN("Element (%d, %d):<%s> is not less than (%d, %d):<%s>"),
NOT_GREATER_THAN("Element (%d, %d):<%s> is not greater than (%d, %d):<%s>"),
;
private final String message;
EquivalenceFailureType(String message)
{
this.message = message;
}
public String getMessage()
{
return message;
}
}
public static class ElementCheckFailure
{
protected final EquivalenceFailureType type;
protected final int primaryClassNumber;
protected final int primaryElementNumber;
protected final Object primaryObject;
public ElementCheckFailure(EquivalenceFailureType type, int primaryClassNumber, int primaryElementNumber, Object primaryObject)
{
Preconditions.checkNotNull(type, "type is null");
this.type = type;
this.primaryClassNumber = primaryClassNumber;
this.primaryElementNumber = primaryElementNumber;
this.primaryObject = primaryObject;
}
public EquivalenceFailureType getType()
{
return type;
}
public int getPrimaryClassNumber()
{
return primaryClassNumber;
}
public int getPrimaryElementNumber()
{
return primaryElementNumber;
}
@Override
public String toString()
{
return format(type.getMessage(), primaryClassNumber, primaryElementNumber, primaryObject);
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ElementCheckFailure that = (ElementCheckFailure) o;
if (primaryClassNumber != that.primaryClassNumber) {
return false;
}
if (primaryElementNumber != that.primaryElementNumber) {
return false;
}
if (!type.equals(that.type)) {
return false;
}
if (primaryObject != that.primaryObject && // Some testing objects not reflexive
!primaryObject.equals(that.primaryObject)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
int result = type.hashCode();
result = 31 * result + primaryClassNumber;
result = 31 * result + primaryElementNumber;
result = 31 * result + primaryObject.hashCode();
return result;
}
}
public static class PairCheckFailure extends ElementCheckFailure
{
private final int secondaryClassNumber;
private final int secondaryElementNumber;
private final Object secondaryObject;
public PairCheckFailure(EquivalenceFailureType type, int primaryClassNumber, int primaryElementNumber, Object primaryObject, int secondaryClassNumber, int secondaryElementNumber, Object secondaryObject)
{
super(type, primaryClassNumber, primaryElementNumber, primaryObject);
this.secondaryClassNumber = secondaryClassNumber;
this.secondaryElementNumber = secondaryElementNumber;
this.secondaryObject = secondaryObject;
}
public int getSecondaryClassNumber()
{
return secondaryClassNumber;
}
public int getSecondaryElementNumber()
{
return secondaryElementNumber;
}
@Override
public String toString()
{
return format(type.getMessage(), primaryClassNumber, primaryElementNumber, primaryObject, secondaryClassNumber, secondaryElementNumber, secondaryObject);
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
PairCheckFailure that = (PairCheckFailure) o;
if (primaryClassNumber != that.primaryClassNumber) {
return false;
}
if (primaryElementNumber != that.primaryElementNumber) {
return false;
}
if (primaryObject != that.primaryObject && // Some testing objects not reflexive
!primaryObject.equals(that.primaryObject)) {
return false;
}
if (secondaryClassNumber != that.secondaryClassNumber) {
return false;
}
if (secondaryElementNumber != that.secondaryElementNumber) {
return false;
}
if (secondaryObject != that.secondaryObject && // Some testing objects not reflexive
!secondaryObject.equals(that.secondaryObject)) {
return false;
}
if (!type.equals(that.type)) {
return false;
}
return true;
}
@Override
public int hashCode()
{
int result = super.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + primaryClassNumber;
result = 31 * result + primaryElementNumber;
result = 31 * result + primaryObject.hashCode();
result = 31 * result + secondaryClassNumber;
result = 31 * result + secondaryElementNumber;
result = 31 * result + secondaryObject.hashCode();
return result;
}
}
}