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: NumberConverter.html 893732 2014-01-11 19:35:15Z oheger $ 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(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.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(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 @Override 201 protected String convertToString(Object value) throws Throwable { 202 203 String result = null; 204 if (useLocaleFormat && value instanceof Number) { 205 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(Class<T> targetType, Object value) throws Throwable { 234 235 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 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 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(Class<?> sourceType, Class<T> targetType, 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 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 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 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 { 375 return targetType.cast(BigDecimal.valueOf(value.longValue())); 376 } 377 } 378 379 // BigInteger 380 if (targetType.equals(BigInteger.class)) { 381 if (value instanceof BigDecimal) { 382 return targetType.cast(((BigDecimal)value).toBigInteger()); 383 } else { 384 return targetType.cast(BigInteger.valueOf(value.longValue())); 385 } 386 } 387 388 String msg = toString(getClass()) + " cannot handle conversion to '" 389 + toString(targetType) + "'"; 390 if (log().isWarnEnabled()) { 391 log().warn(" " + msg); 392 } 393 throw new ConversionException(msg); 394 395 } 396 397 /** 398 * Default String to Number conversion. 399 * <p> 400 * This method handles conversion from a String to the following types: 401 * <ul> 402 * <li><code>java.lang.Byte</code></li> 403 * <li><code>java.lang.Short</code></li> 404 * <li><code>java.lang.Integer</code></li> 405 * <li><code>java.lang.Long</code></li> 406 * <li><code>java.lang.Float</code></li> 407 * <li><code>java.lang.Double</code></li> 408 * <li><code>java.math.BigDecimal</code></li> 409 * <li><code>java.math.BigInteger</code></li> 410 * </ul> 411 * @param sourceType The type being converted from 412 * @param targetType The Number type to convert to 413 * @param value The String value to convert. 414 * 415 * @return The converted Number value. 416 */ 417 private Number toNumber(Class<?> sourceType, Class<?> targetType, String value) { 418 419 // Byte 420 if (targetType.equals(Byte.class)) { 421 return new Byte(value); 422 } 423 424 // Short 425 if (targetType.equals(Short.class)) { 426 return new Short(value); 427 } 428 429 // Integer 430 if (targetType.equals(Integer.class)) { 431 return new Integer(value); 432 } 433 434 // Long 435 if (targetType.equals(Long.class)) { 436 return new Long(value); 437 } 438 439 // Float 440 if (targetType.equals(Float.class)) { 441 return new Float(value); 442 } 443 444 // Double 445 if (targetType.equals(Double.class)) { 446 return new Double(value); 447 } 448 449 // BigDecimal 450 if (targetType.equals(BigDecimal.class)) { 451 return new BigDecimal(value); 452 } 453 454 // BigInteger 455 if (targetType.equals(BigInteger.class)) { 456 return new BigInteger(value); 457 } 458 459 String msg = toString(getClass()) + " cannot handle conversion from '" + 460 toString(sourceType) + "' to '" + toString(targetType) + "'"; 461 if (log().isWarnEnabled()) { 462 log().warn(" " + msg); 463 } 464 throw new ConversionException(msg); 465 } 466 467 /** 468 * Provide a String representation of this number converter. 469 * 470 * @return A String representation of this number converter 471 */ 472 @Override 473 public String toString() { 474 StringBuilder buffer = new StringBuilder(); 475 buffer.append(toString(getClass())); 476 buffer.append("[UseDefault="); 477 buffer.append(isUseDefault()); 478 buffer.append(", UseLocaleFormat="); 479 buffer.append(useLocaleFormat); 480 if (pattern != null) { 481 buffer.append(", Pattern="); 482 buffer.append(pattern); 483 } 484 if (locale != null) { 485 buffer.append(", Locale="); 486 buffer.append(locale); 487 } 488 buffer.append(']'); 489 return buffer.toString(); 490 } 491 492 /** 493 * Return a NumberFormat to use for Conversion. 494 * 495 * @return The NumberFormat. 496 */ 497 private NumberFormat getFormat() { 498 NumberFormat format = null; 499 if (pattern != null) { 500 if (locale == null) { 501 if (log().isDebugEnabled()) { 502 log().debug(" Using pattern '" + pattern + "'"); 503 } 504 format = new DecimalFormat(pattern); 505 } else { 506 if (log().isDebugEnabled()) { 507 log().debug(" Using pattern '" + pattern + "'" + 508 " with Locale[" + locale + "]"); 509 } 510 DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); 511 format = new DecimalFormat(pattern, symbols); 512 } 513 } else { 514 if (locale == null) { 515 if (log().isDebugEnabled()) { 516 log().debug(" Using default Locale format"); 517 } 518 format = NumberFormat.getInstance(); 519 } else { 520 if (log().isDebugEnabled()) { 521 log().debug(" Using Locale[" + locale + "] format"); 522 } 523 format = NumberFormat.getInstance(locale); 524 } 525 } 526 if (!allowDecimals) { 527 format.setParseIntegerOnly(true); 528 } 529 return format; 530 } 531 532 /** 533 * Convert a String into a <code>Number</code> object. 534 * @param sourceType the source type of the conversion 535 * @param targetType The type to convert the value to 536 * @param value The String date value. 537 * @param format The NumberFormat to parse the String value. 538 * 539 * @return The converted Number object. 540 * @throws ConversionException if the String cannot be converted. 541 */ 542 private Number parse(Class<?> sourceType, Class<?> targetType, String value, NumberFormat format) { 543 ParsePosition pos = new ParsePosition(0); 544 Number parsedNumber = format.parse(value, pos); 545 if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) { 546 String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'"; 547 if (format instanceof DecimalFormat) { 548 msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'"; 549 } 550 if (locale != null) { 551 msg += " for locale=[" + locale + "]"; 552 } 553 if (log().isDebugEnabled()) { 554 log().debug(" " + msg); 555 } 556 throw new ConversionException(msg); 557 } 558 return parsedNumber; 559 } 560 561}