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