View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2004, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  package org.dbunit.dataset.datatype;
22  
23  import java.math.BigDecimal;
24  
25  import org.dbunit.dataset.datatype.ToleratedDeltaMap.Precision;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  /**
30   * Extended version of the {@link NumberDataType}. Extends the 
31   * {@link #compare(Object, Object)} method in order to respect precision tolerance.
32   * This is comparable to the JUnit method 
33   * <code>assert(double val1, double val2, double toleratedDelta)</code>.
34   * 
35   * @author gommma
36   * @author Last changed by: $Author$
37   * @version $Revision$ $Date$
38   * @since 2.3.0
39   */
40  public class NumberTolerantDataType extends NumberDataType
41  {
42  
43      /**
44       * Logger for this class
45       */
46      private static final Logger logger = LoggerFactory.getLogger(NumberTolerantDataType.class);
47  
48      private static final BigDecimal C_100 = new BigDecimal("100");
49      
50      /**
51       * The allowed/tolerated difference 
52       */
53      private Precision toleratedDelta;
54  
55      /**
56       * Creates a new number tolerant datatype
57       * @param name
58       * @param sqlType
59       * @param delta The tolerated delta to be used for the comparison
60       */
61      NumberTolerantDataType(String name, int sqlType, Precision delta)
62      {
63          super(name, sqlType);
64          
65          if (delta == null) {
66              throw new NullPointerException(
67                      "The parameter 'delta' must not be null");
68          }
69          this.toleratedDelta = delta;
70      }
71  
72      public Precision getToleratedDelta() 
73      {
74  		return toleratedDelta;
75  	}
76  
77  
78      /**
79       * The only method overwritten from the base implementation to compare numbers allowing a tolerance
80       * @see org.dbunit.dataset.datatype.AbstractDataType#compareNonNulls(java.lang.Object, java.lang.Object)
81       */
82      protected int compareNonNulls(Object value1cast, Object value2cast)
83          throws TypeCastException 
84      {
85          logger.debug("compareNonNulls(value1={}, value2={}) - start", value1cast, value2cast);
86          
87          try
88          {
89              // Start of special handling
90              if(value1cast instanceof BigDecimal && value2cast instanceof BigDecimal){
91                  BigDecimal bdValue1 = (BigDecimal)value1cast;
92                  BigDecimal bdValue2 = (BigDecimal)value2cast;
93                  BigDecimal diff = bdValue1.subtract(bdValue2);
94                  // Exact match
95                  if(isZero(diff)) 
96                  {
97                      return 0;
98                  }
99                  
100                 BigDecimal toleratedDeltaValue = this.toleratedDelta.getDelta();
101                 if(!this.toleratedDelta.isPercentage()) 
102                 {
103                     if(diff.abs().compareTo(toleratedDeltaValue) <= 0) 
104                     {
105                         // within tolerance delta, so accept
106                         if(logger.isDebugEnabled())
107                             logger.debug("Values val1={}, val2={} differ but are within tolerated delta {}",
108                                     new Object[] {bdValue1, bdValue2, toleratedDeltaValue } );
109                         return 0;
110                     } 
111                     else {
112                         // TODO it would be beautiful to report a precise description about difference and tolerated delta values in the assertion
113                         // Therefore think about introducing a method "DataType.getCompareInfo()"
114                         return diff.signum();
115                     }
116                 }
117                 else {
118                     // percentage comparison
119                     int scale = toleratedDeltaValue.scale() + 2;
120                     BigDecimal toleratedValue = bdValue1.multiply( toleratedDeltaValue.divide(C_100, scale, BigDecimal.ROUND_HALF_UP) );
121                     if(diff.abs().compareTo(toleratedValue) <= 0) 
122                     {
123                         // within tolerance delta, so accept
124                         if(logger.isDebugEnabled())
125                             logger.debug("Values val1={}, val2={} differ but are within tolerated delta {}",
126                                     new Object[] {bdValue1, bdValue2, toleratedValue } );
127                         return 0;
128                     }
129                     else {
130                         // TODO it would be beautiful to report a precise description about difference and tolerated delta values in the assertion
131                         // Therefore think about introducing a method "DataType.getCompareInfo()"
132                         return diff.signum();
133                     }
134                 }
135                 
136             }
137             else {
138                 Comparable value1 = (Comparable)value1cast;
139                 Comparable value2 = (Comparable)value2cast;
140                 return value1.compareTo(value2);
141             }
142         }
143         catch (ClassCastException e)
144         {
145             throw new TypeCastException(e);
146         }
147     }
148 
149     /**
150      * Checks if the given value is zero.
151      * @param value
152      * @return <code>true</code> if and only if the given value is zero.
153      */
154     public static final boolean isZero(BigDecimal value)
155     {
156         return value.signum()==0;
157     }
158     
159 }