ToleratedDeltaMap.java

/*
 *
 * The DbUnit Database Testing Framework
 * Copyright (C)2002-2004, DbUnit.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
package org.dbunit.dataset.datatype;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Container that manages a map of {@link ToleratedDelta} objects to be used for
 * numeric comparisons with an allowed deviation of two values
 * 
 * @author gommma
 * @author Last changed by: $Author$
 * @version $Revision$ $Date$
 * @since 2.3.0
 */
public class ToleratedDeltaMap
{
    /**
     * List of {@link ToleratedDelta} objects
     */
    private Map _toleratedDeltas;
    /**
     * The logger
     */
    private Logger logger = LoggerFactory.getLogger(ToleratedDeltaMap.class);

    /**
     * Lookup a tolerated delta object by tableName and ColumnName.
     * 
     * @param tableName
     * @param columnName
     * @return The object from the map or <code>null</code> if no such object
     *         was found
     */
    public ToleratedDelta findToleratedDelta(String tableName,
            String columnName)
    {
        Map toleratedDeltas = getToleratedDeltasNullSafe();
        String mapKey = ToleratedDeltaMap.buildMapKey(tableName, columnName);
        ToleratedDelta deltaObj = (ToleratedDelta) toleratedDeltas.get(mapKey);
        return deltaObj;
    }

    private final Map getToleratedDeltasNullSafe()
    {
        Map res = getToleratedDeltas();
        if (res == null)
        {
            return Collections.emptyMap();
        }
        return res;
    }

    public Map getToleratedDeltas()
    {
        return _toleratedDeltas;
    }

    /**
     * Adds a new object to the map of tolerated deltas
     * 
     * @param delta
     *            The object to be added to the map
     */
    public void addToleratedDelta(ToleratedDelta delta)
    {
        if (delta == null)
        {
            throw new NullPointerException(
                    "The parameter 'delta' must not be null");
        }

        if (this._toleratedDeltas == null)
        {
            this._toleratedDeltas = new HashMap();
        }
        String key = ToleratedDeltaMap.buildMapKey(delta);
        // Put the new object into the map
        ToleratedDelta removed =
                (ToleratedDelta) _toleratedDeltas.put(key, delta);
        // Give a hint to the user when an already existing object has been
        // overwritten/replaced
        if (removed != null)
        {
            logger.debug(
                    "Replaced old tolerated delta object from map with key {}. Old replaced object={}",
                    key, removed);
        }
    }

    /**
     * Utility method to create a map key from the input parameters
     * 
     * @param tableName
     * @param columnName
     * @return The key for the tolerated delta object map, consisting of the
     *         tableName and the columnName
     */
    static String buildMapKey(String tableName, String columnName)
    {
        return tableName + "." + columnName;
    }

    /**
     * Utility method to create a map key from the input parameters
     * 
     * @param delta
     * @return The key for the tolerated delta object map, consisting of the
     *         tableName and the columnName
     */
    static String buildMapKey(ToleratedDelta delta)
    {
        return buildMapKey(delta.getTableName(), delta.getColumnName());
    }

    /**
     * Simple bean that holds the tolerance for floating point comparisons for a
     * specific database column.
     */
    public static class ToleratedDelta
    {
        private String tableName;
        private String columnName;
        private Precision toleratedDelta;

        /**
         * @param tableName
         *            The name of the table
         * @param columnName
         *            The name of the column for which the tolerated delta
         *            should be applied
         * @param toleratedDelta
         *            The tolerated delta. For example 1E-5 means that the
         *            comparison must match the first 5 decimal digits. All
         *            subsequent decimals are ignored.
         */
        public ToleratedDelta(String tableName, String columnName,
                double toleratedDelta)
        {
            this(tableName, columnName, new Precision(
                    new BigDecimal(String.valueOf(toleratedDelta))));
        }

        /**
         * @param tableName
         *            The name of the table
         * @param columnName
         *            The name of the column for which the tolerated delta
         *            should be applied
         * @param toleratedDelta
         *            The tolerated delta. For example 1E-5 means that the
         *            comparison must match the first 5 decimal digits. All
         *            subsequent decimals are ignored.
         */
        public ToleratedDelta(String tableName, String columnName,
                BigDecimal toleratedDelta)
        {
            this(tableName, columnName, new Precision(toleratedDelta));
        }

        /**
         * @param tableName
         *            The name of the table
         * @param columnName
         *            The name of the column for which the tolerated delta
         *            should be applied
         * @param toleratedDelta
         *            The tolerated delta. For example 1E-5 means that the
         *            comparison must match the first 5 decimal digits. All
         *            subsequent decimals are ignored.
         * @param isPercentage
         *            Whether or not the given toleratedDelta value is a
         *            percentage. See {@link Precision} for more.
         */
        public ToleratedDelta(String tableName, String columnName,
                BigDecimal toleratedDelta, boolean isPercentage)
        {
            this(tableName, columnName,
                    new Precision(toleratedDelta, isPercentage));
        }

        /**
         * @param tableName
         *            The name of the table
         * @param columnName
         *            The name of the column for which the tolerated delta
         *            should be applied
         * @param toleratedDelta
         *            The tolerated delta. For example 1E-5 means that the
         *            comparison must match the first 5 decimal digits. All
         *            subsequent decimals are ignored.
         */
        public ToleratedDelta(String tableName, String columnName,
                Precision toleratedDelta)
        {
            super();
            this.tableName = tableName;
            this.columnName = columnName;
            this.toleratedDelta = toleratedDelta;
        }

        public String getTableName()
        {
            return tableName;
        }

        public String getColumnName()
        {
            return columnName;
        }

        public Precision getToleratedDelta()
        {
            return toleratedDelta;
        }

        /**
         * Checks whether or not the <code>tableName</code> and the
         * <code>columnName</code> match the ones of this object.
         * 
         * @param tableName
         * @param columnName
         * @return <code>true</code> if both given values match those of this
         *         object.
         */
        public boolean matches(String tableName, String columnName)
        {
            if (this.tableName.equals(tableName)
                    && this.columnName.equals(columnName))
            {
                return true;
            } else
            {
                return false;
            }
        }

        public String toString()
        {
            final StringBuilder sb = new StringBuilder();
            sb.append("tableName=").append(tableName);
            sb.append(", columnName=").append(columnName);
            sb.append(", toleratedDelta=").append(toleratedDelta);
            return sb.toString();
        }
    }

    /**
     * Container for the tolerated delta of two values that are compared to each
     * other.
     * 
     * @author gommma (gommma AT users.sourceforge.net)
     * @author Last changed by: $Author$
     * @version $Revision$ $Date$
     * @since 2.4.0
     */
    public static class Precision
    {
        private static final BigDecimal ZERO = new BigDecimal("0.0");

        private final boolean percentage;
        private final BigDecimal delta;

        /**
         * @param delta
         *            The allowed/tolerated difference
         */
        public Precision(BigDecimal delta)
        {
            this(delta, false);
        }

        /**
         * @param delta
         *            The allowed/tolerated difference
         * @param percentage
         *            Whether or not the given <code>delta</code> should be
         *            interpreted as percentage or not during the comparison
         */
        public Precision(BigDecimal delta, boolean percentage)
        {
            super();

            if (delta.compareTo(ZERO) < 0)
            {
                throw new IllegalArgumentException(
                        "The given delta '" + delta + "' must be >= 0");
            }

            this.delta = delta;
            this.percentage = percentage;
        }

        public boolean isPercentage()
        {
            return percentage;
        }

        public BigDecimal getDelta()
        {
            return delta;
        }

    }

}