blob: 2bee343e0966874a76a5567e7bb8f5d7f56be09a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
<<<<<<< Updated upstream
*
* 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
=======
*
* https://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
>>>>>>> Stashed changes
* limitations under the License.
*/
package org.apache.jdo.tck.util;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* This is a utility class to support equality checking. An EqualityHelper object defines the
* context of a deepEquals call, because it keeps track of objects that have already been processed.
* This avoids endless recursion when comparing cyclic data structures for deep equality.
*
* <p>Furthermore, EqualityHelper provides convenience methods for checking deep equality, equality
* and close enough (for floating point values).
*
* @author Michael Bouschen
* @since 1.1
*/
public class EqualityHelper {
/** Logger */
protected final Log logger = LogFactory.getFactory().getInstance("org.apache.jdo.tck");
/** true if debug logging in enabled. */
protected boolean debug = logger.isDebugEnabled();
/** Used when comparing float values close enough. */
public static final float FLOAT_EPSILON = (float) Math.pow(2.0, -20.0);
/** Used when comparing double values close enough. */
public static final double DOUBLE_EPSILON = Math.pow(2.0, -52.0);
/** Message for null vs. not null */
static final String MSG_ME_NULL = "\nExpected null, actual not null";
/** Message for not null vs. null */
static final String MSG_OTHER_NULL = "\nExpected not null, actual null";
/** Message for incompatible types */
static final String MSG_INCOMPATIBLE_TYPES = "\nIncompatible types for comparison";
/** Message for wrong class for counting via iterator */
static final String MSG_PARAMETER_MUST_BE_COLLECTION_OR_MAP =
"Parameter must be a Collection or Map.";
/**
* Comparator used in method deepEquals comparing maps. This comparator is used to order Maps
* whose keys are Comparable so the entries can be compared using deepCompareFields.
*/
@SuppressWarnings("unchecked")
private static final Comparator<Map.Entry<?, ?>> entryKeyComparator =
(o1, o2) -> {
Object key1 = o1.getKey();
Object key2 = o2.getKey();
return ((Comparable<Object>) key1).compareTo(key2);
};
/** Utility counter for maps and collections */
static int countIterator(Object o) {
int result = 0;
Iterator<?> it;
if (o instanceof Collection) {
it = ((Collection<?>) o).iterator();
} else if (o instanceof Map) {
it = ((Map<?, ?>) o).entrySet().iterator();
} else {
throw new ClassCastException(MSG_PARAMETER_MUST_BE_COLLECTION_OR_MAP);
}
while (it.hasNext()) {
it.next();
result++;
}
return result;
}
/** Comparator used in method deepEquals comparing maps of DeepEquality. */
private static class DeepEqualityEntryKeyComparator<K, V> implements Comparator<Map.Entry<K, V>> {
final Comparator<K> comparator;
DeepEqualityEntryKeyComparator(Comparator<K> comp) {
this.comparator = comp;
}
public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
K key1 = o1.getKey();
K key2 = o2.getKey();
return comparator.compare(key1, key2);
}
}
/**
* Collection of instances that have been processed already in the context of this EqualityHelper
* instance
*/
private final Collection<Object> processed = new HashSet<>();
/** StringBuffer of logged differences. */
final StringBuffer unequalBuffer = new StringBuffer();
/** Context is a stack of navigational paths. */
final Stack<String> contextStack = new Stack<>();
// Methods to support keeping track of instances that have been
// processed already.
/**
* Returns <code>true</code> if the specified instance has been processed already in the context
* of this <code>EqualityHelper</code>.
*
* @param obj the instance to be checked.
* @return <code>true</code> if the instance has been processed already; <code>false</code>
* otherwise.
*/
public boolean isProcessed(Object obj) {
return processed.contains(obj);
}
/**
* Marks the specified instance as processed in the context of this <code>EqualityHelper</code>.
* This means the instance is added to the collection of processed instances.
*
* @param obj instance marked as processed
*/
public void markProcessed(Object obj) {
processed.add(obj);
}
/**
* Clears the collection of processed instances of this <code>EqualityHelper</code>. No instance
* is marked as processed in the context of this <code>EqualityHelper</code> after calling this
* method.
*/
public void clearProcessed() {
processed.clear();
}
// Deep equality support methods
/**
* Returns <code>true</code> if the specified instances are "deep equal".
*
* @param me one object to be tested for deep equality
* @param other the other object to be tested for deep equality
* @return <code>true</code> if the objects are deep equal.
*/
public boolean deepEquals(DeepEquality me, DeepEquality other) {
if (me == other) return true;
if ((me == null) || (other == null)) return false;
if (isProcessed(me)) return true;
markProcessed(me);
return me.deepCompareFields(other, this);
}
/**
* Returns <code>true</code> if the specified instances are "deep equal". The method compares the
* two instances via the deepEquals method if they implement DeepEquals; compares the two
* instances via deepEquals if they implement Collection or Map, and otherwise compares the
* instances using equals.
*
* @param me one object to be tested for deep equality
* @param other the other object to be tested for deep equality
* @return <code>true</code> if the objects are deep equal.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public boolean deepEquals(Object me, Object other) {
if (me == other) return true;
if ((me == null) || (other == null)) return false;
if ((me instanceof DeepEquality) && (other instanceof DeepEquality))
return deepEquals((DeepEquality) me, (DeepEquality) other);
if ((me instanceof Collection<?>) && (other instanceof Collection<?>))
return deepEquals((Collection) me, (Collection) other);
if ((me instanceof Map<?, ?>) && (other instanceof Map<?, ?>))
return deepEquals((Map) me, (Map) other);
return me.equals(other);
}
/**
* Returns <code>true</code> if the specified collections are "deep equal". Two collections are
* deep equal, if they have the same size and their corresponding elements are deep equal after
* sorting using the natural ordering of the elements. The method throws a <code>
* ClassCastException</code> if the elements are not Comparable or if they are not mutually
* comparable.
*
* @param mine one collection to be tested for deep equality
* @param other the other collection to be tested for deep equality
* @return <code>true</code> if the collections are deep equal.
* @throws ClassCastException if the collections contain elements that are not mutually
* comparable.
*/
@SuppressWarnings("unchecked")
public <T extends Comparable<T>> boolean deepEquals(Collection<T> mine, Collection<T> other) {
if (mine == other) return true;
if ((mine == null) || (other == null)) return false;
// Return false, if the size differs
if (mine.size() != other.size()) return false;
if (mine.size() == 0) return true;
// Now check the elements
List<T> myList = new ArrayList<>(mine);
Collections.sort(myList);
List<T> otherList = new ArrayList<>(other);
/* Any collection of elements to be compared must implement Comparator
* to avoid the other side having to implement Comparable. */
Comparator<T> comparator = (Comparator<T>) myList.get(0);
Collections.sort(otherList, comparator);
for (int i = 0; i < myList.size(); i++) {
if (!deepEquals(myList.get(i), otherList.get(i))) return false;
}
return true;
}
/**
* Returns <code>true</code> if the specified maps are "deep equal". Two maps are deep equal, if
* they have the same size and the values of the corresponding keys compare deep equal. The method
* throws a <code>ClassCastException</code> if keys or values are not Comparable or if they are
* not mutually comparable.
*
* @param mine one map to be tested for deep equality
* @param other the other map to be tested for deep equality
* @return <code>true</code> if the maps are deep equal.
* @throws ClassCastException if the maps contain keys or values that are not mutually comparable.
*/
@SuppressWarnings("unchecked")
public <K, V> boolean deepEquals(Map<K, V> mine, Map<K, V> other) {
if (mine == other) return true;
if ((mine == null) || (other == null)) return false;
// Return false, if the size differs
if (mine.size() != other.size()) return false;
if (mine.size() == 0) return true;
// Now check the elements
List<Map.Entry<K, V>> myList = new ArrayList<>(mine.entrySet());
Collections.sort(myList, entryKeyComparator);
List<Map.Entry<K, V>> otherList = new ArrayList<>(other.entrySet());
/* Any collection of elements to be compared must implement Comparator
* to avoid the other side having to implement Comparable. */
Comparator<K> comparator = (Comparator<K>) ((Map.Entry<V, K>) myList.get(0)).getKey();
Collections.sort(otherList, new DeepEqualityEntryKeyComparator<>(comparator));
for (int i = 0; i < myList.size(); i++) {
Map.Entry<K, V> entry1 = myList.get(i);
Map.Entry<K, V> entry2 = otherList.get(i);
// compare the keys
if (!deepEquals(entry1.getKey(), entry2.getKey())) return false;
// compare the values
if (!deepEquals(entry1.getValue(), entry2.getValue())) return false;
}
return true;
}
// Shallow equality support methods
/**
* Returns <code>true</code> if the specified collections are "shallow equal". Two collections are
* shallow equal, if they have the same size and their corresponding elements are equal after
* sorting using the natural ordering.
*
* @param mine one collection to be tested for shallow equality
* @param other the other collection to be tested for shallow equality
* @return <code>true</code> if the collections are deep equal.
*/
@SuppressWarnings("unchecked")
public <T extends Comparable<T>> boolean shallowEquals(Collection<T> mine, Collection<T> other) {
if (mine == other) return true;
if ((mine == null) || (other == null)) return false;
// Return false, if the size differs
if (mine.size() != other.size()) return false;
if (mine.size() == 0) return true;
// Now check the elements
List<T> myList = new ArrayList<>(mine);
Collections.sort(myList);
List<T> otherList = new ArrayList<>(other);
/* Any collection of elements to be compared must implement Comparator
* to avoid the other side having to implement Comparable. */
Comparator<T> comparator = (Comparator<T>) myList.get(0);
Collections.sort(otherList, comparator);
return myList.equals(otherList);
}
// Deep equality support methods with logging
public String getUnequalBuffer() {
return unequalBuffer.toString();
}
/** Context is nested via navigation through relationships. */
void pushContext(String ctx) {
contextStack.push(ctx);
}
String popContext() {
return contextStack.pop();
}
/**
* Log differences between objects that don't compare equal.
*
* @param o1 the first object
* @param o2 the second object
* @param where the field where the objects are found
*/
void logUnequal(Object o1, Object o2, String where) {
unequalBuffer.append("Context: ");
Iterator<String> it = contextStack.iterator();
StringBuilder offset = new StringBuilder("\n");
while (it.hasNext()) {
unequalBuffer.append(it.next());
unequalBuffer.append("-> ");
offset.append(" ");
}
unequalBuffer.append(where);
unequalBuffer.append(offset);
unequalBuffer.append("expected '");
unequalBuffer.append(o1);
unequalBuffer.append("'");
unequalBuffer.append(offset);
unequalBuffer.append(" actual '");
unequalBuffer.append(o2);
unequalBuffer.append("'\n");
}
/**
* Returns <code>true</code> if the specified instances are "deep equal". If unequal, log the
* location of the inequality.
*
* @param me one object to be tested for deep equality
* @param other the other object to be tested for deep equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the objects are deep equal.
*/
public boolean deepEquals(DeepEquality me, Object other, String where) {
if (me == other) return true;
if (me == null) {
logUnequal(me, other, where + MSG_ME_NULL);
return false;
}
if (other == null) {
logUnequal(me, other, where + MSG_OTHER_NULL);
return false;
}
if (isProcessed(me)) return true;
markProcessed(me);
pushContext(where);
boolean result = true;
if (!me.deepCompareFields(other, this)) {
// logUnequal(me, other, where);
result = false;
}
popContext();
return result;
}
/**
* Returns <code>true</code> if the specified instances are "deep equal". The method compares the
* two instances via the deepEquals method if they implement DeepEquals; compares the two
* instances via deepEquals if they implement Collection or Map, and otherwise compares the
* instances using equals.
*
* @param me one object to be tested for deep equality
* @param other the other object to be tested for deep equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the objects are deep equal.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public boolean deepEquals(Object me, Object other, String where) {
if (me == other) return true;
if (me == null) {
logUnequal(me, other, where + MSG_ME_NULL);
return false;
}
if (other == null) {
logUnequal(me, other, where + MSG_OTHER_NULL);
return false;
}
if (me instanceof DeepEquality) {
return deepEquals((DeepEquality) me, other, where);
} else if ((me instanceof Collection<?>) && (other instanceof Collection<?>)) {
return deepEqualsCollection((Collection) me, (Collection) other, where);
} else if ((me instanceof Map<?, ?>) && (other instanceof Map<?, ?>)) {
return deepEqualsMap((Map) me, (Map) other, where);
} else {
return equals(me, other, where);
}
}
/**
* Returns <code>true</code> if the specified collections are "deep equal". Two collections are
* deep equal, if they have the same size and their corresponding elements are deep equal after
* sorting using the natural ordering of the elements. The method throws a <code>
* ClassCastException</code> if the elements are not Comparable or if they are not mutually
* comparable.
*
* @param me one collection to be tested for deep equality
* @param other the other collection to be tested for deep equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the collections are deep equal.
* @throws ClassCastException if the collections contain elements that are not mutually
* comparable.
*/
@SuppressWarnings("unchecked")
public <S extends Comparable<S>, T> boolean deepEqualsCollection(
Collection<S> me, Collection<T> other, String where) {
if (me == other) return true;
if (me == null) {
logUnequal(me, other, where + MSG_ME_NULL);
return false;
}
if (other == null) {
logUnequal(me, other, where + MSG_OTHER_NULL);
return false;
}
int mysize = me.size();
int othersize = other.size();
// Return false, if the size differs
if (mysize != othersize) {
int count = countIterator(other);
logUnequal(
me,
other,
where
+ "\nSize mismatch: expected size= "
+ me.size()
+ ", original size= "
+ othersize
+ ", current size= "
+ other.size()
+ ", counted size= "
+ count);
return false;
}
if (mysize == 0) return true;
// Now check each element for equality or deep equality
List<S> myList = new ArrayList<>(me);
// Use the natural ordering of me; must implement Comparable
Collections.sort(myList);
List<T> otherList = new ArrayList<>(other);
/* Any collection of elements to be compared must implement Comparator
* to avoid the other side having to implement Comparable. */
Comparator<T> comparator = (Comparator<T>) myList.get(0);
otherList.sort(comparator);
boolean result = true;
for (int i = 0; i < myList.size(); i++) {
DeepEquality o1 = (DeepEquality) myList.get(i);
Object o2 = otherList.get(i);
/* Compare corresponding elements of the ordered list. */
if (!deepEquals(o1, o2, where + "[" + i + "]")) {
result = false;
}
}
return result;
}
/**
* Returns <code>true</code> if the specified maps are "deep equal". Two maps are deep equal, if
* they have the same size and the values of the corresponding keys compare deep equal. The method
* throws a <code>ClassCastException</code> if keys or values are not Comparable or if they are
* not mutually comparable.
*
* @param me one map to be tested for deep equality
* @param other the other map to be tested for deep equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the maps are deep equal.
* @throws ClassCastException if the maps contain keys or values that are not mutually comparable.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public <K, V> boolean deepEqualsMap(Map<K, V> me, Map<K, V> other, String where) {
if (me == other) return true;
if (me == null) {
logUnequal(me, other, where + MSG_ME_NULL);
return false;
}
if (other == null) {
logUnequal(me, other, where + MSG_OTHER_NULL);
return false;
}
// Return false, if the size differs
int mysize = me.size();
int othersize = other.size();
if (mysize != othersize) {
int count = countIterator(other);
logUnequal(
me,
other,
where
+ "\nSize mismatch: expected size= "
+ me.size()
+ ", original size= "
+ othersize
+ ", current size= "
+ other.size()
+ ", counted size= "
+ count);
return false;
}
if (mysize == 0) return true;
// Now check the elements
List<Map.Entry<K, V>> myList = new ArrayList<>(me.entrySet());
// Use the natural ordering of me; must implement Comparable
Collections.sort(myList, entryKeyComparator);
List<Map.Entry<K, V>> otherList = new ArrayList<>(other.entrySet());
Comparator<Map.Entry<?, ?>> comparator = entryKeyComparator;
// Use the Comparator to avoid the other side implementing Comparable
Object key = myList.get(0).getKey();
if (key instanceof Comparator) {
comparator = new DeepEqualityEntryKeyComparator((Comparator) key);
}
Collections.sort(otherList, comparator);
boolean result = true;
for (int i = 0; i < myList.size(); i++) {
Map.Entry<K, V> entry1 = myList.get(i);
Object key1 = entry1.getKey();
Object value1 = entry1.getValue();
Map.Entry<K, V> entry2 = otherList.get(i);
K key2 = entry2.getKey();
V value2 = entry2.getValue();
// compare the keys
if (!deepEquals(key1, key2, where + "[" + i + "].key")) {
result = false;
}
// compare the values
if (!deepEquals(value1, value2, where + "[" + i + "].value")) {
result = false;
}
}
return result;
}
// Shallow equality support methods
/**
* Returns <code>true</code> if the specified collections are "shallow equal". Two collections are
* shallow equal, if they have the same size and their corresponding elements are equal after
* sorting using the natural ordering.
*
* @param me one collection to be tested for shallow equality
* @param other the other collection to be tested for shallow equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the collections are deep equal.
*/
@SuppressWarnings("unchecked")
public <T extends Comparable<T>> boolean shallowEquals(
Collection<T> me, Collection<T> other, String where) {
if (me == other) return true;
if (me == null) {
logUnequal(me, other, where + MSG_ME_NULL);
return false;
}
if (other == null) {
logUnequal(me, other, where + MSG_OTHER_NULL);
return false;
}
// Return false, if the size differs
int mysize = me.size();
int othersize = other.size();
if (mysize != othersize) {
// debug size...
Iterator<T> it = other.iterator();
int count = 0;
while (it.hasNext()) {
it.next();
++count;
}
logUnequal(
me,
other,
where
+ "\nSize mismatch: expected size= "
+ me.size()
+ ", original size= "
+ othersize
+ ", current size= "
+ other.size()
+ ", counted size= "
+ count);
return false;
}
if (me.size() == 0) return true;
// Now check the elements
List<T> myList = new ArrayList<>(me);
Collections.sort(myList);
List<T> otherList = new ArrayList<>(other);
/* Any collection of elements to be compared must implement Comparator
* to avoid the other side having to implement Comparable. */
Comparator<T> comparator = (Comparator<T>) myList.get(0);
Collections.sort(otherList, comparator);
boolean result = myList.equals(otherList);
if (!result) logUnequal(me, other, where + "\nCollections do not compare equal");
return result;
}
// Equality support methods
/**
* Returns <code>true</code> if the specified objects are equal. This is a helper method checking
* for identical and <code>null</code> objects before delegating to the regular equals method.
*
* @param o1 one object to be tested for equality
* @param o2 the other object to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the specified objects are equal.
*/
public boolean equals(Object o1, Object o2, String where) {
if (o1 == o2) return true;
if (o1 == null) {
logUnequal(o1, o2, where + MSG_ME_NULL);
return false;
}
if (o2 == null) {
logUnequal(o1, o2, where + MSG_OTHER_NULL);
return false;
}
if (!o1.equals(o2)) {
logUnequal(o1, o2, where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if compare called for the specified BigDecimal objects returns
* <code>0</code>. Please note, two BigDecimal instances are not equal (using equals) if their
* scale differs, and this method compares the values, ignoring scale.
*
* @param o1 one object to be tested for equality
* @param o2 the other object to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the specified BigDecimal objects are equal.
*/
public boolean equals(BigDecimal o1, BigDecimal o2, String where) {
if (o1 == o2) return true;
if ((o1 == null) || (o2 == null)) {
logUnequal(o1, o2, where);
return false;
}
boolean result = o1.equals(o2);
if (!result) logUnequal(o1, o2, where);
return result;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(boolean p1, boolean p2, String where) {
if (p1 != p2) {
logUnequal(Boolean.toString(p1), Boolean.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(char p1, char p2, String where) {
if (p1 != p2) {
logUnequal(Character.toString(p1), Character.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(byte p1, byte p2, String where) {
if (p1 != p2) {
logUnequal(Byte.toString(p1), Byte.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(short p1, short p2, String where) {
if (p1 != p2) {
logUnequal(Short.toString(p1), Short.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(int p1, int p2, String where) {
if (p1 != p2) {
logUnequal(Integer.toString(p1), Integer.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code>, if two parameters are equal.
*
* @param p1 one to be tested for equality
* @param p2 the other to be tested for equality
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the parameters are equal.
*/
public boolean equals(long p1, long p2, String where) {
if (p1 != p2) {
logUnequal(Long.toString(p1), Long.toString(p2), where);
return false;
}
return true;
}
/**
* Returns <code>true</code> if the specified objects are equal. This is a helper method checking
* for identical and <code>null</code> objects before delegating to the regular equals method.
*
* @param o1 one object to be tested for equality
* @param o2 the other object to be tested for equality
* @return <code>true</code> if the specified objects are equal.
*/
public static boolean equals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
return o1.equals(o2);
}
// Methods to support "close enough" comparison
/**
* Returns <code>true</code> if the specified objects are close enough to be considered to be
* equal for a deep equals comparison. The method delegates to the method taking double or float
* values if the specified objects are Float or Double wrappers. Otherwise it delegates to equals.
*
* @param o1 one object to be tested for close enough
* @param o2 the other object to be tested for close enough
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the specified values are close enough.
*/
public boolean closeEnough(Object o1, Object o2, String where) {
if (o1 == o2) return true;
if ((o1 == null) || (o2 == null)) {
logUnequal(o1, o2, where);
return false;
}
boolean result = true;
if ((o1 instanceof Double) && (o2 instanceof Double)) {
return closeEnough(((Double) o1).doubleValue(), ((Double) o2).doubleValue(), where);
} else if ((o1 instanceof Float) && (o2 instanceof Float)) {
return closeEnough(((Float) o1).floatValue(), ((Float) o2).floatValue(), where);
} else if ((o1 instanceof BigDecimal) && (o2 instanceof BigDecimal)) {
return ((BigDecimal) o1).compareTo((BigDecimal) o2) == 0;
} else {
result = o1.equals(o2);
}
if (!result) logUnequal(o1, o2, where);
return result;
}
/**
* Returns <code>true</code> if the specified float values are close enough to be considered to be
* equal for a deep equals comparison. Floating point values are not exact, so comparing them
* using <code>==</code> might not return useful results. This method checks that both double
* values are within some percent of each other.
*
* @param d1 one double to be tested for close enough
* @param d2 the other double to be tested for close enough
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the specified values are close enough.
*/
public boolean closeEnough(double d1, double d2, String where) {
if (d1 == d2) return true;
double diff = Math.abs(d1 - d2);
boolean result = diff < Math.abs((d1 + d2) * DOUBLE_EPSILON);
if (!result) logUnequal(Double.toString(d1), Double.toString(d2), where);
return result;
}
/**
* Returns <code>true</code> if the specified float values are close enough to be considered to be
* equal for a deep equals comparison. Floating point values are not exact, so comparing them
* using <code>==</code> might not return useful results. This method checks that both float
* values are within some percent of each other.
*
* @param f1 one float to be tested for close enough
* @param f2 the other float to be tested for close enough
* @param where the location of the inequality (provided by the caller)
* @return <code>true</code> if the specified values are close enough.
*/
public boolean closeEnough(float f1, float f2, String where) {
if (f1 == f2) return true;
float diff = Math.abs(f1 - f2);
boolean result = diff < Math.abs((f1 + f2) * FLOAT_EPSILON);
if (!result) logUnequal(Float.toString(f1), Float.toString(f2), where);
return result;
}
// Methods to support compare methods as specified in Comparator
/**
* Compares its two arguments for order. Returns a negative integer, zero, or a positive integer
* as the first argument is less than, equal to, or greater than the second.
*
* @param l1 the first long to be compared
* @param l2 the second long to be compared
* @return a negative integer, zero, or a positive integer as the first argument is less than,
* equal to, or greater than the second.
*/
public static int compare(long l1, long l2) {
return (Long.compare(l1, l2));
}
}