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.EMPTY_MAP;
        }
        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() {
    		StringBuffer sb = new StringBuffer();
    		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 boolean percentage;
        private 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;
        }
        
    }

}