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