1 /* 2 * Redistribution and use of this software and associated documentation ("Software"), with or 3 * without modification, are permitted provided that the following conditions are met: 4 * 5 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions 6 * must also contain a copy of this document. 7 * 8 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 9 * conditions and the following disclaimer in the documentation and/or other materials provided with 10 * the distribution. 11 * 12 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software 13 * without prior written permission of Intalio, Inc. For written permission, please contact 14 * info@exolab.org. 15 * 16 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in 17 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of 18 * Intalio, Inc. 19 * 20 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/). 21 * 22 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 24 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS 25 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 29 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 * 31 * Copyright 2000-2003 (C) Intalio, Inc. All Rights Reserved. 32 * 33 * $Id$ Date Author Changes 10/31/2000 Arnaud Blandin Created 34 */ 35 package org.exolab.castor.xml.validators; 36 37 import java.lang.reflect.InvocationTargetException; 38 import java.lang.reflect.Method; 39 import java.math.BigDecimal; 40 41 import org.exolab.castor.xml.TypeValidator; 42 import org.exolab.castor.xml.ValidationContext; 43 import org.exolab.castor.xml.ValidationException; 44 45 /** 46 * The Decimal Validation class. This class handles validation for the <code>BigDecimal</code> type. 47 * 48 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a> 49 * @author <a href="mailto:edward.kuns@aspect.com">Edward Kuns</a> 50 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $ 51 */ 52 public class DecimalValidator extends PatternValidator implements TypeValidator { 53 54 /** Reference to the Method BigDecimal.toPlainString(), only in JDK 5 or later. */ 55 private static Method _bdMethodToPlainString = null; 56 57 static { 58 try { 59 _bdMethodToPlainString = BigDecimal.class.getMethod("toPlainString", (Class[]) null); 60 } catch (NoSuchMethodException e) { 61 // If it does not exist, we're in Java 1.4.2 or earlier 62 } 63 } 64 65 /** Fixed value of this short. (Not used if null.) */ 66 private BigDecimal _fixed = null; 67 /** Minimum value (inclusive or exclusive) for this BigDecimal. (Not used if null.) */ 68 private BigDecimal _min = null; 69 /** Maximum value (inclusive or exclusive) for this BigDecimal. (Not used if null.) */ 70 private BigDecimal _max = null; 71 /** Maximum number of significant digits in this BigDecimal. (Not applied if < 0.) */ 72 private int _totalDigits = -1; 73 /** Maximum number of fractional digits in this BigDecimal. (Not applied if < 0.) */ 74 private int _fractionDigits = -1; 75 /** If true, the minimum value is an <b>exclusive</b> value. */ 76 private boolean _hasMinExclusive = false; 77 /** If true, the maximum value is an <b>exclusive</b> value. */ 78 private boolean _hasMaxExclusive = false; 79 80 /** 81 * Creates a new DecimalValidator with no restrictions. 82 */ 83 public DecimalValidator() { 84 super(); 85 } // -- decimalValidator 86 87 /** 88 * Clears the fixed value for this BigIntegerValidator. 89 */ 90 public void clearFixed() { 91 _fixed = null; 92 } // -- clearFixed 93 94 /** 95 * Clears the maximum value for this DecimalValidator. 96 */ 97 public void clearMax() { 98 _max = null; 99 _hasMaxExclusive = false; 100 } // -- clearMax 101 102 /** 103 * Clears the minimum value for this DecimalValidator. 104 */ 105 public void clearMin() { 106 _min = null; 107 _hasMinExclusive = false; 108 } // -- clearMin 109 110 /** 111 * Returns the configured fixed value for BigDecimal validation. Returns null if no fixed value 112 * has been configured. 113 * 114 * @return the fixed value to validate against. 115 */ 116 public BigDecimal getFixed() { 117 return _fixed; 118 } // -- getFixed 119 120 /** 121 * Returns the configured inclusive maximum value for BigDecimal validation. Returns null if no 122 * inclusive maximum has been configured. 123 * 124 * @return the maximum inclusive value to validate against. 125 */ 126 public BigDecimal getMaxInclusive() { 127 return (_hasMaxExclusive) ? null : _max; 128 } // -- getMaxInclusive 129 130 /** 131 * Returns the configured exclusive maximum value for BigDecimal validation. Returns null if no 132 * exclusive maximum has been configured. 133 * 134 * @return the maximum exclusive value to validate against. 135 */ 136 public BigDecimal getMaxExclusive() { 137 return (_hasMaxExclusive) ? _max : null; 138 } // -- getMaxInclusive 139 140 /** 141 * Returns the configured inclusive minimum value for BigDecimal validation. Returns null if no 142 * inclusive minimum has been configured. 143 * 144 * @return the minimum inclusive value to validate against. 145 */ 146 public BigDecimal getMinInclusive() { 147 return (_hasMinExclusive) ? null : _min; 148 } // -- getMinInclusive 149 150 /** 151 * Returns the configured exclusive minimum value for BigDecimal validation. Returns null if no 152 * exclusive minimum has been configured. 153 * 154 * @return the minimum exclusive value to validate against. 155 */ 156 public BigDecimal getMinExclusive() { 157 return (_hasMinExclusive) ? _min : null; 158 } // -- getMinInclusive 159 160 /** 161 * Returns true if a fixed value to validate against has been set. 162 * 163 * @return true if a fixed value has been set. 164 */ 165 public boolean hasFixed() { 166 return (_fixed != null); 167 } // -- hasFixed 168 169 /** 170 * Sets the fixed value for BigDecimal validation. 171 * <p> 172 * NOTE: If maximum and/or minimum values have been set and the fixed value is not within that 173 * max/min range, then no BigDecimal will pass validation. This is as according to the XML Schema 174 * spec. 175 * 176 * @param fixedValue the fixed value that a BigDecimal validated with this validator must be equal 177 * to. 178 */ 179 public void setFixed(final BigDecimal fixedValue) { 180 _fixed = fixedValue; 181 } // -- setMinExclusive 182 183 /** 184 * Sets the minimum (exclusive) value for BigDecimal validation. To pass validation, a BigDecimal 185 * must be greater than this value. 186 * 187 * @param minValue the minimum (exclusive) value for BigDecimal validation. 188 */ 189 public void setMinExclusive(final BigDecimal minValue) { 190 if (minValue == null) { 191 throw new IllegalArgumentException("argument 'minValue' must not be null."); 192 } 193 _min = minValue; 194 _hasMinExclusive = true; 195 } // -- setMinExclusive 196 197 /** 198 * Sets the minimum (inclusive) value for BigDecimal validation. To pass validation, a BigDecimal 199 * must be greater than or equal to this value. 200 * 201 * @param minValue the minimum (inclusive) value for BigDecimal validation. 202 */ 203 public void setMinInclusive(final BigDecimal minValue) { 204 _min = minValue; 205 _hasMinExclusive = false; 206 } // -- setMinInclusive 207 208 /** 209 * Sets the maximum (exclusive) value for BigDecimal validation. To pass validation, a BigDecimal 210 * must be less than this value. 211 * 212 * @param maxValue the maximum (exclusive) value for BigDecimal validation. 213 */ 214 public void setMaxExclusive(final BigDecimal maxValue) { 215 if (maxValue == null) { 216 throw new IllegalArgumentException("argument 'maxValue' must not be null."); 217 } 218 _max = maxValue; 219 _hasMaxExclusive = true; 220 } // -- setMaxExclusive 221 222 /** 223 * Sets the maximum (inclusive) value for BigDecimal validation. To pass validation, a BigDecimal 224 * must be less than or equal to this value. 225 * 226 * @param maxValue the maximum (inclusive) value for BigDecimal validation. 227 */ 228 public void setMaxInclusive(final BigDecimal maxValue) { 229 _max = maxValue; 230 _hasMaxExclusive = false; 231 } // -- setMaxInclusive 232 233 /** 234 * Sets the maximum number of digits for BigDecimal validation. To pass validation, a BigDecimal 235 * must have this many digits or fewer. Leading zeros are not counted. Trailing zeros after the 236 * decimal point are not counted. 237 * 238 * @param totalDig the maximum (inclusive) number of digits for BigDecimal validation. (must be > 239 * 0) 240 */ 241 public void setTotalDigits(final int totalDig) { 242 if (totalDig <= 0) { 243 throw new IllegalArgumentException( 244 "DecimalValidator: the totalDigits facet must be positive"); 245 } 246 _totalDigits = totalDig; 247 } 248 249 /** 250 * Sets the maximum number of fraction digits for BigDecimal validation. To pass validation, a 251 * BigDecimal must have this many digits or fewer following the decimal point. Trailing zeros 252 * after the decimal point are not counted. 253 * 254 * @param fractionDig the maximum (inclusive) number of fraction digits for BigDecimal validation. 255 * (must be > 0) 256 */ 257 public void setFractionDigits(final int fractionDig) { 258 if (fractionDig < 0) { 259 throw new IllegalArgumentException( 260 "DecimalValidator: the fractionDigits facet must be positive"); 261 } 262 _fractionDigits = fractionDig; 263 } 264 265 /** 266 * Validates the given Object. 267 * 268 * @param bd the BigDecimal to validate 269 * @param context the ValidationContext 270 * @throws ValidationException if the object fails validation. 271 */ 272 public void validate(final BigDecimal bd, final ValidationContext context) 273 throws ValidationException { 274 if (_fixed != null && !bd.equals(_fixed)) { 275 String err = "BigDecimal " + bd + " is not equal to the fixed value: " + _fixed; 276 throw new ValidationException(err); 277 } 278 279 if (_min != null) { 280 if (bd.compareTo(_min) == -1) { 281 String err = "BigDecimal " + bd + " is less than the minimum allowed value: " + _min; 282 throw new ValidationException(err); 283 } else if ((bd.compareTo(_min) == 0) && (_hasMinExclusive)) { 284 String err = 285 "BigDecimal " + bd + " cannot be equal to the minimum exclusive value: " + _min; 286 throw new ValidationException(err); 287 } 288 } 289 290 if (_max != null) { 291 if (bd.compareTo(_max) == 1) { 292 String err = "BigDecimal " + bd + " is greater than the maximum allowed value: " + _max; 293 throw new ValidationException(err); 294 } else if ((bd.compareTo(_max) == 0) && (_hasMaxExclusive)) { 295 String err = 296 "BigDecimal " + bd + " cannot be equal to the maximum exclusive value: " + _max; 297 throw new ValidationException(err); 298 } 299 } 300 301 // For digit counting, we are not supposed to count leading or trailing zeros 302 BigDecimal clean = stripTrailingZeros(bd); 303 304 if (_totalDigits != -1) { 305 String temp = toStringForBigDecimal(clean); 306 int length = temp.length(); 307 if (temp.indexOf('-') == 0) { 308 --length; 309 } 310 if (temp.indexOf('.') != -1) { 311 --length; 312 } 313 if (length > _totalDigits) { 314 String err = "BigDecimal " + bd + " has too many significant digits -- must have " 315 + _totalDigits + " or fewer"; 316 throw new ValidationException(err); 317 } 318 temp = null; 319 } 320 321 if (_fractionDigits != -1 && clean.scale() > _fractionDigits) { 322 String err = "BigDecimal " + bd + " has too many fraction digits -- must have " 323 + _fractionDigits + " fraction digits or fewer"; 324 throw new ValidationException(err); 325 } 326 327 if (hasPattern()) { 328 super.validate(toStringForBigDecimal(bd), context); 329 } 330 } // -- validate 331 332 /** 333 * Validates the given Object. 334 * 335 * @param object the Object to validate 336 * @throws ValidationException if the object fails validation. 337 */ 338 public void validate(final Object object) throws ValidationException { 339 validate(object, (ValidationContext) null); 340 } // -- validate 341 342 /** 343 * Validates the given Object. 344 * 345 * @param object the Object to validate 346 * @param context the ValidationContext 347 * @throws ValidationException if the object fails validation. 348 */ 349 public void validate(final Object object, final ValidationContext context) 350 throws ValidationException { 351 if (object == null) { 352 String err = "decimalValidator cannot validate a null object."; 353 throw new ValidationException(err); 354 } 355 356 BigDecimal value = null; 357 if (object instanceof BigDecimal) { 358 value = (BigDecimal) object; 359 } else { 360 try { 361 value = new java.math.BigDecimal(object.toString()); 362 } catch (Exception ex) { 363 String err = "Expecting a decimal, received instead: " + object.getClass().getName(); 364 throw new ValidationException(err); 365 } 366 } 367 validate(value, context); 368 } // -- validate 369 370 /** 371 * Because Sun broke API compatibility between Java 1.4 and Java 5, we have to do this the hard 372 * way. 373 * 374 * @param bd the BigDecimal to toString() on. 375 * @return what <i>should be</i> toString() for BigDecimal 376 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6364896"> This Sun bug report 377 * calling for better documentation of this change</a> 378 */ 379 private String toStringForBigDecimal(final BigDecimal bd) { 380 if (_bdMethodToPlainString != null) { 381 try { 382 // For Java 1.5 or later, use toPlainString() to get what we want 383 return (String) _bdMethodToPlainString.invoke(bd, (Object[]) null); 384 } catch (IllegalAccessException e) { 385 // Cannot occur, so just fall through to toString() 386 } catch (InvocationTargetException e) { 387 // Cannot occur, so just fall through to toString() 388 } 389 } 390 391 // For Java 1.4.2 or earlier, use toString() to get what we want 392 return bd.toString(); 393 } 394 395 /** 396 * Trims trailing zeros from the provided BigDecimal. Since BigDecimals are immutable, the value 397 * passed in is not changed. 398 * <p> 399 * The JDK 5 API provides a method to do this, but earlier releases of Java do not. Rather than 400 * reflectively use this method for early releases and the API-provided one for releases after 401 * Java 5, we'll just use this method for all Java releases. 402 * 403 * @param bd the BigDecimal to trim 404 * @return a new BigDecimal with trailing zeros removed 405 */ 406 private BigDecimal stripTrailingZeros(final BigDecimal bd) { 407 BigDecimal ret = null; 408 try { 409 for (int i = bd.scale(); i >= 0; i--) { 410 ret = bd.setScale(i); 411 } 412 } catch (ArithmeticException e) { 413 // We've removed all trailing zeros 414 } 415 return (ret == null) ? bd : ret; 416 } 417 418 } // -- decimalValidator