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