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:</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 using the <i>standard</i> 078 * pattern characters and NOT in <i>localized</i> form (see <code>java.text.DecimalFormat</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 $Id$ 083 * @since 1.8.0 084 */ 085public 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 final 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(final 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(final boolean allowDecimals, final 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(final 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.DecimalFormat</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.DecimalFormat</code> for details 162 * of how to specify the pattern. 163 * 164 * @param pattern The format pattern. 165 */ 166 public void setPattern(final 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(final 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 @Override 201 protected String convertToString(final Object value) throws Throwable { 202 203 String result = null; 204 if (useLocaleFormat && value instanceof Number) { 205 final NumberFormat format = getFormat(); 206 format.setGroupingUsed(false); 207 result = format.format(value); 208 if (log().isDebugEnabled()) { 209 log().debug(" Converted to String using format '" + result + "'"); 210 } 211 212 } else { 213 result = value.toString(); 214 if (log().isDebugEnabled()) { 215 log().debug(" Converted to String using toString() '" + result + "'"); 216 } 217 } 218 return result; 219 220 } 221 222 /** 223 * Convert the input object into a Number object of the 224 * specified type. 225 * 226 * @param <T> Target type of the conversion. 227 * @param targetType Data type to which this value should be converted. 228 * @param value The input value to be converted. 229 * @return The converted value. 230 * @throws Throwable if an error occurs converting to the specified type 231 */ 232 @Override 233 protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable { 234 235 final Class<?> sourceType = value.getClass(); 236 // Handle Number 237 if (value instanceof Number) { 238 return toNumber(sourceType, targetType, (Number)value); 239 } 240 241 // Handle Boolean 242 if (value instanceof Boolean) { 243 return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO); 244 } 245 246 // Handle Date --> Long 247 if (value instanceof Date && Long.class.equals(targetType)) { 248 return targetType.cast(new Long(((Date)value).getTime())); 249 } 250 251 // Handle Calendar --> Long 252 if (value instanceof Calendar && Long.class.equals(targetType)) { 253 return targetType.cast(new Long(((Calendar)value).getTime().getTime())); 254 } 255 256 // Convert all other types to String & handle 257 final String stringValue = value.toString().trim(); 258 if (stringValue.length() == 0) { 259 return handleMissing(targetType); 260 } 261 262 // Convert/Parse a String 263 Number number = null; 264 if (useLocaleFormat) { 265 final NumberFormat format = getFormat(); 266 number = parse(sourceType, targetType, stringValue, format); 267 } else { 268 if (log().isDebugEnabled()) { 269 log().debug(" No NumberFormat, using default conversion"); 270 } 271 number = toNumber(sourceType, targetType, stringValue); 272 } 273 274 // Ensure the correct number type is returned 275 return toNumber(sourceType, targetType, number); 276 277 } 278 279 /** 280 * Convert any Number object to the specified type for this 281 * <i>Converter</i>. 282 * <p> 283 * This method handles conversion to the following types: 284 * <ul> 285 * <li><code>java.lang.Byte</code></li> 286 * <li><code>java.lang.Short</code></li> 287 * <li><code>java.lang.Integer</code></li> 288 * <li><code>java.lang.Long</code></li> 289 * <li><code>java.lang.Float</code></li> 290 * <li><code>java.lang.Double</code></li> 291 * <li><code>java.math.BigDecimal</code></li> 292 * <li><code>java.math.BigInteger</code></li> 293 * </ul> 294 * @param sourceType The type being converted from 295 * @param targetType The Number type to convert to 296 * @param value The Number to convert. 297 * 298 * @return The converted value. 299 */ 300 private <T> T toNumber(final Class<?> sourceType, final Class<T> targetType, final Number value) { 301 302 // Correct Number type already 303 if (targetType.equals(value.getClass())) { 304 return targetType.cast(value); 305 } 306 307 // Byte 308 if (targetType.equals(Byte.class)) { 309 final long longValue = value.longValue(); 310 if (longValue > Byte.MAX_VALUE) { 311 throw new ConversionException(toString(sourceType) + " value '" + value 312 + "' is too large for " + toString(targetType)); 313 } 314 if (longValue < Byte.MIN_VALUE) { 315 throw new ConversionException(toString(sourceType) + " value '" + value 316 + "' is too small " + toString(targetType)); 317 } 318 return targetType.cast(new Byte(value.byteValue())); 319 } 320 321 // Short 322 if (targetType.equals(Short.class)) { 323 final long longValue = value.longValue(); 324 if (longValue > Short.MAX_VALUE) { 325 throw new ConversionException(toString(sourceType) + " value '" + value 326 + "' is too large for " + toString(targetType)); 327 } 328 if (longValue < Short.MIN_VALUE) { 329 throw new ConversionException(toString(sourceType) + " value '" + value 330 + "' is too small " + toString(targetType)); 331 } 332 return targetType.cast(new Short(value.shortValue())); 333 } 334 335 // Integer 336 if (targetType.equals(Integer.class)) { 337 final long longValue = value.longValue(); 338 if (longValue > Integer.MAX_VALUE) { 339 throw new ConversionException(toString(sourceType) + " value '" + value 340 + "' is too large for " + toString(targetType)); 341 } 342 if (longValue < Integer.MIN_VALUE) { 343 throw new ConversionException(toString(sourceType) + " value '" + value 344 + "' is too small " + toString(targetType)); 345 } 346 return targetType.cast(new Integer(value.intValue())); 347 } 348 349 // Long 350 if (targetType.equals(Long.class)) { 351 return targetType.cast(new Long(value.longValue())); 352 } 353 354 // Float 355 if (targetType.equals(Float.class)) { 356 if (value.doubleValue() > Float.MAX_VALUE) { 357 throw new ConversionException(toString(sourceType) + " value '" + value 358 + "' is too large for " + toString(targetType)); 359 } 360 return targetType.cast(new Float(value.floatValue())); 361 } 362 363 // Double 364 if (targetType.equals(Double.class)) { 365 return targetType.cast(new Double(value.doubleValue())); 366 } 367 368 // BigDecimal 369 if (targetType.equals(BigDecimal.class)) { 370 if (value instanceof Float || value instanceof Double) { 371 return targetType.cast(new BigDecimal(value.toString())); 372 } else if (value instanceof BigInteger) { 373 return targetType.cast(new BigDecimal((BigInteger)value)); 374 } else if (value instanceof BigDecimal) { 375 return targetType.cast(new BigDecimal(value.toString())); 376 } else { 377 return targetType.cast(BigDecimal.valueOf(value.longValue())); 378 } 379 } 380 381 // BigInteger 382 if (targetType.equals(BigInteger.class)) { 383 if (value instanceof BigDecimal) { 384 return targetType.cast(((BigDecimal)value).toBigInteger()); 385 } else { 386 return targetType.cast(BigInteger.valueOf(value.longValue())); 387 } 388 } 389 390 final String msg = toString(getClass()) + " cannot handle conversion to '" 391 + toString(targetType) + "'"; 392 if (log().isWarnEnabled()) { 393 log().warn(" " + msg); 394 } 395 throw new ConversionException(msg); 396 397 } 398 399 /** 400 * Default String to Number conversion. 401 * <p> 402 * This method handles conversion from a String to the following types: 403 * <ul> 404 * <li><code>java.lang.Byte</code></li> 405 * <li><code>java.lang.Short</code></li> 406 * <li><code>java.lang.Integer</code></li> 407 * <li><code>java.lang.Long</code></li> 408 * <li><code>java.lang.Float</code></li> 409 * <li><code>java.lang.Double</code></li> 410 * <li><code>java.math.BigDecimal</code></li> 411 * <li><code>java.math.BigInteger</code></li> 412 * </ul> 413 * @param sourceType The type being converted from 414 * @param targetType The Number type to convert to 415 * @param value The String value to convert. 416 * 417 * @return The converted Number value. 418 */ 419 private Number toNumber(final Class<?> sourceType, final Class<?> targetType, final String value) { 420 421 // Byte 422 if (targetType.equals(Byte.class)) { 423 return new Byte(value); 424 } 425 426 // Short 427 if (targetType.equals(Short.class)) { 428 return new Short(value); 429 } 430 431 // Integer 432 if (targetType.equals(Integer.class)) { 433 return new Integer(value); 434 } 435 436 // Long 437 if (targetType.equals(Long.class)) { 438 return new Long(value); 439 } 440 441 // Float 442 if (targetType.equals(Float.class)) { 443 return new Float(value); 444 } 445 446 // Double 447 if (targetType.equals(Double.class)) { 448 return new Double(value); 449 } 450 451 // BigDecimal 452 if (targetType.equals(BigDecimal.class)) { 453 return new BigDecimal(value); 454 } 455 456 // BigInteger 457 if (targetType.equals(BigInteger.class)) { 458 return new BigInteger(value); 459 } 460 461 final String msg = toString(getClass()) + " cannot handle conversion from '" + 462 toString(sourceType) + "' to '" + toString(targetType) + "'"; 463 if (log().isWarnEnabled()) { 464 log().warn(" " + msg); 465 } 466 throw new ConversionException(msg); 467 } 468 469 /** 470 * Provide a String representation of this number converter. 471 * 472 * @return A String representation of this number converter 473 */ 474 @Override 475 public String toString() { 476 final StringBuilder buffer = new StringBuilder(); 477 buffer.append(toString(getClass())); 478 buffer.append("[UseDefault="); 479 buffer.append(isUseDefault()); 480 buffer.append(", UseLocaleFormat="); 481 buffer.append(useLocaleFormat); 482 if (pattern != null) { 483 buffer.append(", Pattern="); 484 buffer.append(pattern); 485 } 486 if (locale != null) { 487 buffer.append(", Locale="); 488 buffer.append(locale); 489 } 490 buffer.append(']'); 491 return buffer.toString(); 492 } 493 494 /** 495 * Return a NumberFormat to use for Conversion. 496 * 497 * @return The NumberFormat. 498 */ 499 private NumberFormat getFormat() { 500 NumberFormat format = null; 501 if (pattern != null) { 502 if (locale == null) { 503 if (log().isDebugEnabled()) { 504 log().debug(" Using pattern '" + pattern + "'"); 505 } 506 format = new DecimalFormat(pattern); 507 } else { 508 if (log().isDebugEnabled()) { 509 log().debug(" Using pattern '" + pattern + "'" + 510 " with Locale[" + locale + "]"); 511 } 512 final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); 513 format = new DecimalFormat(pattern, symbols); 514 } 515 } else { 516 if (locale == null) { 517 if (log().isDebugEnabled()) { 518 log().debug(" Using default Locale format"); 519 } 520 format = NumberFormat.getInstance(); 521 } else { 522 if (log().isDebugEnabled()) { 523 log().debug(" Using Locale[" + locale + "] format"); 524 } 525 format = NumberFormat.getInstance(locale); 526 } 527 } 528 if (!allowDecimals) { 529 format.setParseIntegerOnly(true); 530 } 531 return format; 532 } 533 534 /** 535 * Convert a String into a <code>Number</code> object. 536 * @param sourceType the source type of the conversion 537 * @param targetType The type to convert the value to 538 * @param value The String date value. 539 * @param format The NumberFormat to parse the String value. 540 * 541 * @return The converted Number object. 542 * @throws ConversionException if the String cannot be converted. 543 */ 544 private Number parse(final Class<?> sourceType, final Class<?> targetType, final String value, final NumberFormat format) { 545 final ParsePosition pos = new ParsePosition(0); 546 final Number parsedNumber = format.parse(value, pos); 547 if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) { 548 String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'"; 549 if (format instanceof DecimalFormat) { 550 msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'"; 551 } 552 if (locale != null) { 553 msg += " for locale=[" + locale + "]"; 554 } 555 if (log().isDebugEnabled()) { 556 log().debug(" " + msg); 557 } 558 throw new ConversionException(msg); 559 } 560 return parsedNumber; 561 } 562 563}