/*
 * 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
 * 
 *     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 org.apache.jdo.tck.util;

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 java.math.BigDecimal;

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 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 float FLOAT_EPSILON = (float)Math.pow(2.0, -20.0);

    /** Used when comparing double values close enough. */
    public static double DOUBLE_EPSILON = Math.pow(2.0, -52.0);

    /** Message for null vs. not null */
    static final String msgMeNull = "\nExpected null, actual not null";
    
    /** Message for not null vs. null */
    static final String msgOtherNull = "\nExpected not null, actual null";
    
    /** Message for incompatible types */
    static final String msgIncompatibleTypes = "\nIncompatible types for comparison";
    
    /** Message for wrong class for counting via iterator */
    static final String msgParameterMustBeCollectionOrMap =
            "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.
     */
    private static Comparator entryKeyComparator = new Comparator() {
            public int compare(Object o1, Object o2) {
                Object key1 = ((Map.Entry)o1).getKey();
                Object key2 = ((Map.Entry)o2).getKey();
                return ((Comparable)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(msgParameterMustBeCollectionOrMap);
        }
        while (it.hasNext()) {
            it.next();
            result++;
        }
        return result;
    }
    /** Comparator used in method deepEquals comparing maps of
     * DeepEquality.
     */
    private static class DeepEqualityEntryKeyComparator 
            implements Comparator {
        Comparator comparator;
        DeepEqualityEntryKeyComparator(Comparator comp) {
            this.comparator = comparator;
        }
        public int compare(Object o1, Object o2) {
            Object key1 = ((Map.Entry)o1).getKey();
            Object key2 = ((Map.Entry)o2).getKey();
            return comparator.compare(key1, key2);
        }
    }

    /** Collection of instances that have been processed already in the
     * context of this EqualityHelper instance 
     */
    private Collection processed = new HashSet();
    
    /** StringBuffer of logged differences.
     */
    StringBuffer unequalBuffer = new StringBuffer();
    
    /**
     * Context is a stack of navigational paths.
     */
    Stack 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.
     */
    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.
     */
    public boolean deepEquals(Collection mine, Collection 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 myList = new ArrayList(mine);
        Collections.sort(myList);
        List otherList = new ArrayList(other);
        /* Any collection of elements to be compared must implement Comparator
         * to avoid the other side having to implement Comparable. */
        Comparator comparator = 
                (Comparator)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.
     */
    public boolean deepEquals(Map mine, Map 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 myList = new ArrayList(mine.entrySet());
        Collections.sort(myList, entryKeyComparator);
        List 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 comparator = 
                (Comparator)((Map.Entry)myList.get(0)).getKey();
        Collections.sort(otherList, 
                new DeepEqualityEntryKeyComparator(comparator));

        for (int i = 0; i < myList.size(); i++) {
            Map.Entry entry1 = (Map.Entry)myList.get(i);
            Map.Entry entry2 = (Map.Entry)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.
     */
    public boolean shallowEquals(Collection mine, Collection 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 myList = new ArrayList(mine);
        Collections.sort(myList);
        List otherList = new ArrayList(other);
        /* Any collection of elements to be compared must implement Comparator
         * to avoid the other side having to implement Comparable. */
        Comparator comparator = 
                (Comparator)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 (String)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 it = contextStack.iterator();
        StringBuffer offset = new StringBuffer("\n");
        while (it.hasNext()) {
            unequalBuffer.append(it.next());
            unequalBuffer.append("-> ");
            offset.append("    ");
        }
        unequalBuffer.append(where);
        unequalBuffer.append(offset.toString());
        unequalBuffer.append("expected '");
        unequalBuffer.append(o1);
        unequalBuffer.append("'");
        unequalBuffer.append(offset.toString());
        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 + msgMeNull);
            return false;
        }
        if (other == null) {
            logUnequal(me, other, where + msgOtherNull);
            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.
     */
    public  boolean deepEquals(Object me, Object other, String where) {
        if (me == other)
            return true;
        if (me == null) {
            logUnequal(me, other, where + msgMeNull);
            return false;
        }
        if (other == null) {
            logUnequal(me, other, where + msgOtherNull);
            return false;
        }
        if (me instanceof DeepEquality) {
            return deepEquals((DeepEquality)me, other, where);
        } else if ((me instanceof Collection) && (other instanceof Collection)) {
            return deepEquals((Collection)me, (Collection)other, where);
        } else if ((me instanceof Map) && (other instanceof Map)) {
            return deepEquals((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.
     */
    public boolean deepEquals(Collection me, Collection other, 
            String where) {
        if (me == other)
            return true;
        if (me == null) {
            logUnequal(me, other, where + msgMeNull);
            return false;
        }
        if (other == null) {
            logUnequal(me, other, where + msgOtherNull);
            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 myList = new ArrayList(me);
        // Use the natural ordering of me; must implement Comparable
        Collections.sort(myList);
        List otherList = new ArrayList(other);
        /* Any collection of elements to be compared must implement Comparator
         * to avoid the other side having to implement Comparable. */
        Comparator comparator = 
                (Comparator)myList.get(0);
        Collections.sort(otherList, 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.
     */
    public boolean deepEquals(Map me, Map other, String where) {
        if (me == other)
            return true;
        if (me == null) {
            logUnequal(me, other, where + msgMeNull);
            return false;
        }
        if (other == null) {
            logUnequal(me, other, where + msgOtherNull);
            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 myList = new ArrayList(me.entrySet());
        // Use the natural ordering of me; must implement Comparable
        Collections.sort(myList, entryKeyComparator);
        List otherList = new ArrayList(other.entrySet());
        Comparator comparator = entryKeyComparator;
        // Use the Comparator to avoid the other side implementing Comparable
        Object key = ((Map.Entry)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 entry1 = (Map.Entry)myList.get(i);
            Object key1 = entry1.getKey();
            Object value1 = entry1.getValue();
            Map.Entry entry2 = (Map.Entry)otherList.get(i);
            Object key2 = entry2.getKey();
            Object 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.
     */
    public boolean shallowEquals(Collection me, Collection other, 
            String where) {
        if (me == other)
            return true;
        if (me == null) {
            logUnequal(me, other, where + msgMeNull);
            return false;
        }
        if (other == null) {
            logUnequal(me, other, where + msgOtherNull);
            return false;
        }

        // Return false, if the size differs
        int mysize = me.size();
        int othersize = other.size();
        if (mysize != othersize) {
            // debug size...
            Iterator 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 myList = new ArrayList(me);
        Collections.sort(myList);
        List otherList = new ArrayList(other);
        /* Any collection of elements to be compared must implement Comparator
         * to avoid the other side having to implement Comparable. */
        Comparator comparator = 
                (Comparator)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 + msgMeNull);
            return false;
        }
        if (o2 == null) {
            logUnequal(o1, o2, where + msgOtherNull);
            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 (l1 < l2 ? -1 : (l1 == l2 ? 0 : 1));
    }
}
