001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.beanutils.converters; 018 019 import java.util.Calendar; 020 import java.util.Date; 021 import java.util.Locale; 022 import java.math.BigDecimal; 023 import java.math.BigInteger; 024 import java.text.NumberFormat; 025 import java.text.DecimalFormat; 026 import java.text.DecimalFormatSymbols; 027 import java.text.ParsePosition; 028 029 import org.apache.commons.beanutils.ConversionException; 030 031 /** 032 * {@link org.apache.commons.beanutils.Converter} implementaion that handles conversion 033 * to and from <b>java.lang.Number</b> objects. 034 * <p> 035 * This implementation handles conversion for the following 036 * <code>java.lang.Number</code> types. 037 * <ul> 038 * <li><code>java.lang.Byte</code></li> 039 * <li><code>java.lang.Short</code></li> 040 * <li><code>java.lang.Integer</code></li> 041 * <li><code>java.lang.Long</code></li> 042 * <li><code>java.lang.Float</code></li> 043 * <li><code>java.lang.Double</code></li> 044 * <li><code>java.math.BigDecimal</code></li> 045 * <li><code>java.math.BigInteger</code></li> 046 * </ul> 047 * 048 * <h3>String Conversions (to and from)</h3> 049 * This class provides a number of ways in which number 050 * conversions to/from Strings can be achieved: 051 * <ul> 052 * <li>Using the default format for the default Locale, configure using:</li> 053 * <ul> 054 * <li><code>setUseLocaleFormat(true)</code></li> 055 * </ul> 056 * <li>Using the default format for a specified Locale, configure using:</li> 057 * <ul> 058 * <li><code>setLocale(Locale)</code></li> 059 * </ul> 060 * <li>Using a specified pattern for the default Locale, configure using:</li> 061 * <ul> 062 * <li><code>setPattern(String)</code></li> 063 * </ul> 064 * <li>Using a specified pattern for a specified Locale, configure using:</li> 065 * <ul> 066 * <li><code>setPattern(String)</code></li> 067 * <li><code>setLocale(Locale)</code></li> 068 * </ul> 069 * <li>If none of the above are configured the 070 * <code>toNumber(String)</code> method is used to convert 071 * from String to Number and the Number's 072 * <code>toString()</code> method used to convert from 073 * Number to String.</li> 074 * </ul> 075 * 076 * <p> 077 * <strong>N.B.</strong>Patterns can only be specified used the <i>standard</i> 078 * pattern characters and NOT in <i>localized</i> form (see <code>java.text.SimpleDateFormat</code>). 079 * For example to cater for number styles used in Germany such as <code>0.000,00</code> the pattern 080 * is specified in the normal form <code>0,000.00</code> and the locale set to <code>Locale.GERMANY</code>. 081 * 082 * @version $Revision: 745081 $ $Date: 2009-02-17 14:05:20 +0000 (Tue, 17 Feb 2009) $ 083 * @since 1.8.0 084 */ 085 public abstract class NumberConverter extends AbstractConverter { 086 087 private static final Integer ZERO = new Integer(0); 088 private static final Integer ONE = new Integer(1); 089 090 private String pattern; 091 private boolean allowDecimals; 092 private boolean useLocaleFormat; 093 private Locale locale; 094 095 // ----------------------------------------------------------- Constructors 096 097 /** 098 * Construct a <b>java.lang.Number</b> <i>Converter</i> 099 * that throws a <code>ConversionException</code> if a error occurs. 100 * 101 * @param allowDecimals Indicates whether decimals are allowed 102 */ 103 public NumberConverter(boolean allowDecimals) { 104 super(); 105 this.allowDecimals = allowDecimals; 106 } 107 108 /** 109 * Construct a <code>java.lang.Number</code> <i>Converter</i> that returns 110 * a default value if an error occurs. 111 * 112 * @param allowDecimals Indicates whether decimals are allowed 113 * @param defaultValue The default value to be returned 114 */ 115 public NumberConverter(boolean allowDecimals, Object defaultValue) { 116 super(); 117 this.allowDecimals = allowDecimals; 118 setDefaultValue(defaultValue); 119 } 120 121 // --------------------------------------------------------- Public Methods 122 123 /** 124 * Return whether decimals are allowed in the number. 125 * 126 * @return Whether decimals are allowed in the number 127 */ 128 public boolean isAllowDecimals() { 129 return allowDecimals; 130 } 131 132 /** 133 * Set whether a format should be used to convert 134 * the Number. 135 * 136 * @param useLocaleFormat <code>true</code> if a number format 137 * should be used. 138 */ 139 public void setUseLocaleFormat(boolean useLocaleFormat) { 140 this.useLocaleFormat = useLocaleFormat; 141 } 142 143 /** 144 * Return the number format pattern used to convert 145 * Numbers to/from a <code>java.lang.String</code> 146 * (or <code>null</code> if none specified). 147 * <p> 148 * See <code>java.text.SimpleDateFormat</code> for details 149 * of how to specify the pattern. 150 * 151 * @return The format pattern. 152 */ 153 public String getPattern() { 154 return pattern; 155 } 156 157 /** 158 * Set a number format pattern to use to convert 159 * Numbers to/from a <code>java.lang.String</code>. 160 * <p> 161 * See <code>java.text.SimpleDateFormat</code> for details 162 * of how to specify the pattern. 163 * 164 * @param pattern The format pattern. 165 */ 166 public void setPattern(String pattern) { 167 this.pattern = pattern; 168 setUseLocaleFormat(true); 169 } 170 171 /** 172 * Return the Locale for the <i>Converter</i> 173 * (or <code>null</code> if none specified). 174 * 175 * @return The locale to use for conversion 176 */ 177 public Locale getLocale() { 178 return locale; 179 } 180 181 /** 182 * Set the Locale for the <i>Converter</i>. 183 * 184 * @param locale The locale to use for conversion 185 */ 186 public void setLocale(Locale locale) { 187 this.locale = locale; 188 setUseLocaleFormat(true); 189 } 190 191 // ------------------------------------------------------ Protected Methods 192 193 /** 194 * Convert an input Number object into a String. 195 * 196 * @param value The input value to be converted 197 * @return the converted String value. 198 * @throws Throwable if an error occurs converting to a String 199 */ 200 protected String convertToString(Object value) throws Throwable { 201 202 String result = null; 203 if (useLocaleFormat && value instanceof Number) { 204 NumberFormat format = getFormat(); 205 format.setGroupingUsed(false); 206 result = format.format(value); 207 if (log().isDebugEnabled()) { 208 log().debug(" Converted to String using format '" + result + "'"); 209 } 210 211 } else { 212 result = value.toString(); 213 if (log().isDebugEnabled()) { 214 log().debug(" Converted to String using toString() '" + result + "'"); 215 } 216 } 217 return result; 218 219 } 220 221 /** 222 * Convert the input object into a Number object of the 223 * specified type. 224 * 225 * @param targetType Data type to which this value should be converted. 226 * @param value The input value to be converted. 227 * @return The converted value. 228 * @throws Throwable if an error occurs converting to the specified type 229 */ 230 protected Object convertToType(Class targetType, Object value) throws Throwable { 231 232 Class sourceType = value.getClass(); 233 // Handle Number 234 if (value instanceof Number) { 235 return toNumber(sourceType, targetType, (Number)value); 236 } 237 238 // Handle Boolean 239 if (value instanceof Boolean) { 240 return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO); 241 } 242 243 // Handle Date --> Long 244 if (value instanceof Date && Long.class.equals(targetType)) { 245 return new Long(((Date)value).getTime()); 246 } 247 248 // Handle Calendar --> Long 249 if (value instanceof Calendar && Long.class.equals(targetType)) { 250 return new Long(((Calendar)value).getTime().getTime()); 251 } 252 253 // Convert all other types to String & handle 254 String stringValue = value.toString().trim(); 255 if (stringValue.length() == 0) { 256 return handleMissing(targetType); 257 } 258 259 // Convert/Parse a String 260 Number number = null; 261 if (useLocaleFormat) { 262 NumberFormat format = getFormat(); 263 number = parse(sourceType, targetType, stringValue, format); 264 } else { 265 if (log().isDebugEnabled()) { 266 log().debug(" No NumberFormat, using default conversion"); 267 } 268 number = toNumber(sourceType, targetType, stringValue); 269 } 270 271 // Ensure the correct number type is returned 272 return toNumber(sourceType, targetType, number); 273 274 } 275 276 /** 277 * Convert any Number object to the specified type for this 278 * <i>Converter</i>. 279 * <p> 280 * This method handles conversion to the following types: 281 * <ul> 282 * <li><code>java.lang.Byte</code></li> 283 * <li><code>java.lang.Short</code></li> 284 * <li><code>java.lang.Integer</code></li> 285 * <li><code>java.lang.Long</code></li> 286 * <li><code>java.lang.Float</code></li> 287 * <li><code>java.lang.Double</code></li> 288 * <li><code>java.math.BigDecimal</code></li> 289 * <li><code>java.math.BigInteger</code></li> 290 * </ul> 291 * @param sourceType The type being converted from 292 * @param targetType The Number type to convert to 293 * @param value The Number to convert. 294 * 295 * @return The converted value. 296 */ 297 private Number toNumber(Class sourceType, Class targetType, Number value) { 298 299 // Correct Number type already 300 if (targetType.equals(value.getClass())) { 301 return value; 302 } 303 304 // Byte 305 if (targetType.equals(Byte.class)) { 306 long longValue = value.longValue(); 307 if (longValue > Byte.MAX_VALUE) { 308 throw new ConversionException(toString(sourceType) + " value '" + value 309 + "' is too large for " + toString(targetType)); 310 } 311 if (longValue < Byte.MIN_VALUE) { 312 throw new ConversionException(toString(sourceType) + " value '" + value 313 + "' is too small " + toString(targetType)); 314 } 315 return new Byte(value.byteValue()); 316 } 317 318 // Short 319 if (targetType.equals(Short.class)) { 320 long longValue = value.longValue(); 321 if (longValue > Short.MAX_VALUE) { 322 throw new ConversionException(toString(sourceType) + " value '" + value 323 + "' is too large for " + toString(targetType)); 324 } 325 if (longValue < Short.MIN_VALUE) { 326 throw new ConversionException(toString(sourceType) + " value '" + value 327 + "' is too small " + toString(targetType)); 328 } 329 return new Short(value.shortValue()); 330 } 331 332 // Integer 333 if (targetType.equals(Integer.class)) { 334 long longValue = value.longValue(); 335 if (longValue > Integer.MAX_VALUE) { 336 throw new ConversionException(toString(sourceType) + " value '" + value 337 + "' is too large for " + toString(targetType)); 338 } 339 if (longValue < Integer.MIN_VALUE) { 340 throw new ConversionException(toString(sourceType) + " value '" + value 341 + "' is too small " + toString(targetType)); 342 } 343 return new Integer(value.intValue()); 344 } 345 346 // Long 347 if (targetType.equals(Long.class)) { 348 return new Long(value.longValue()); 349 } 350 351 // Float 352 if (targetType.equals(Float.class)) { 353 if (value.doubleValue() > Float.MAX_VALUE) { 354 throw new ConversionException(toString(sourceType) + " value '" + value 355 + "' is too large for " + toString(targetType)); 356 } 357 return new Float(value.floatValue()); 358 } 359 360 // Double 361 if (targetType.equals(Double.class)) { 362 return new Double(value.doubleValue()); 363 } 364 365 // BigDecimal 366 if (targetType.equals(BigDecimal.class)) { 367 if (value instanceof Float || value instanceof Double) { 368 return new BigDecimal(value.toString()); 369 } else if (value instanceof BigInteger) { 370 return new BigDecimal((BigInteger)value); 371 } else { 372 return BigDecimal.valueOf(value.longValue()); 373 } 374 } 375 376 // BigInteger 377 if (targetType.equals(BigInteger.class)) { 378 if (value instanceof BigDecimal) { 379 return ((BigDecimal)value).toBigInteger(); 380 } else { 381 return BigInteger.valueOf(value.longValue()); 382 } 383 } 384 385 String msg = toString(getClass()) + " cannot handle conversion to '" 386 + toString(targetType) + "'"; 387 if (log().isWarnEnabled()) { 388 log().warn(" " + msg); 389 } 390 throw new ConversionException(msg); 391 392 } 393 394 /** 395 * Default String to Number conversion. 396 * <p> 397 * This method handles conversion from a String to the following types: 398 * <ul> 399 * <li><code>java.lang.Byte</code></li> 400 * <li><code>java.lang.Short</code></li> 401 * <li><code>java.lang.Integer</code></li> 402 * <li><code>java.lang.Long</code></li> 403 * <li><code>java.lang.Float</code></li> 404 * <li><code>java.lang.Double</code></li> 405 * <li><code>java.math.BigDecimal</code></li> 406 * <li><code>java.math.BigInteger</code></li> 407 * </ul> 408 * @param sourceType The type being converted from 409 * @param targetType The Number type to convert to 410 * @param value The String value to convert. 411 * 412 * @return The converted Number value. 413 */ 414 private Number toNumber(Class sourceType, Class targetType, String value) { 415 416 // Byte 417 if (targetType.equals(Byte.class)) { 418 return new Byte(value); 419 } 420 421 // Short 422 if (targetType.equals(Short.class)) { 423 return new Short(value); 424 } 425 426 // Integer 427 if (targetType.equals(Integer.class)) { 428 return new Integer(value); 429 } 430 431 // Long 432 if (targetType.equals(Long.class)) { 433 return new Long(value); 434 } 435 436 // Float 437 if (targetType.equals(Float.class)) { 438 return new Float(value); 439 } 440 441 // Double 442 if (targetType.equals(Double.class)) { 443 return new Double(value); 444 } 445 446 // BigDecimal 447 if (targetType.equals(BigDecimal.class)) { 448 return new BigDecimal(value); 449 } 450 451 // BigInteger 452 if (targetType.equals(BigInteger.class)) { 453 return new BigInteger(value); 454 } 455 456 String msg = toString(getClass()) + " cannot handle conversion from '" + 457 toString(sourceType) + "' to '" + toString(targetType) + "'"; 458 if (log().isWarnEnabled()) { 459 log().warn(" " + msg); 460 } 461 throw new ConversionException(msg); 462 } 463 464 /** 465 * Provide a String representation of this number converter. 466 * 467 * @return A String representation of this number converter 468 */ 469 public String toString() { 470 StringBuffer buffer = new StringBuffer(); 471 buffer.append(toString(getClass())); 472 buffer.append("[UseDefault="); 473 buffer.append(isUseDefault()); 474 buffer.append(", UseLocaleFormat="); 475 buffer.append(useLocaleFormat); 476 if (pattern != null) { 477 buffer.append(", Pattern="); 478 buffer.append(pattern); 479 } 480 if (locale != null) { 481 buffer.append(", Locale="); 482 buffer.append(locale); 483 } 484 buffer.append(']'); 485 return buffer.toString(); 486 } 487 488 /** 489 * Return a NumberFormat to use for Conversion. 490 * 491 * @return The NumberFormat. 492 */ 493 private NumberFormat getFormat() { 494 NumberFormat format = null; 495 if (pattern != null) { 496 if (locale == null) { 497 if (log().isDebugEnabled()) { 498 log().debug(" Using pattern '" + pattern + "'"); 499 } 500 format = new DecimalFormat(pattern); 501 } else { 502 if (log().isDebugEnabled()) { 503 log().debug(" Using pattern '" + pattern + "'" + 504 " with Locale[" + locale + "]"); 505 } 506 DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); 507 format = new DecimalFormat(pattern, symbols); 508 } 509 } else { 510 if (locale == null) { 511 if (log().isDebugEnabled()) { 512 log().debug(" Using default Locale format"); 513 } 514 format = NumberFormat.getInstance(); 515 } else { 516 if (log().isDebugEnabled()) { 517 log().debug(" Using Locale[" + locale + "] format"); 518 } 519 format = NumberFormat.getInstance(locale); 520 } 521 } 522 if (!allowDecimals) { 523 format.setParseIntegerOnly(true); 524 } 525 return format; 526 } 527 528 /** 529 * Convert a String into a <code>Number</code> object. 530 * @param sourceType TODO 531 * @param targetType The type to convert the value to 532 * @param value The String date value. 533 * @param format The NumberFormat to parse the String value. 534 * 535 * @return The converted Number object. 536 * @throws ConversionException if the String cannot be converted. 537 */ 538 private Number parse(Class sourceType, Class targetType, String value, NumberFormat format) { 539 ParsePosition pos = new ParsePosition(0); 540 Number parsedNumber = format.parse(value, pos); 541 if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) { 542 String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'"; 543 if (format instanceof DecimalFormat) { 544 msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'"; 545 } 546 if (locale != null) { 547 msg += " for locale=[" + locale + "]"; 548 } 549 if (log().isDebugEnabled()) { 550 log().debug(" " + msg); 551 } 552 throw new ConversionException(msg); 553 } 554 return parsedNumber; 555 } 556 557 }