Coverage Report - org.apache.commons.beanutils.converters.NumberConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
NumberConverter
88%
146/165
85%
103/120
7.333
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.beanutils.converters;
 18  
 
 19  
 import java.util.Calendar;
 20  
 import java.util.Date;
 21  
 import java.util.Locale;
 22  
 import java.math.BigDecimal;
 23  
 import java.math.BigInteger;
 24  
 import java.text.NumberFormat;
 25  
 import java.text.DecimalFormat;
 26  
 import java.text.DecimalFormatSymbols;
 27  
 import java.text.ParsePosition;
 28  
 
 29  
 import org.apache.commons.beanutils.ConversionException;
 30  
 
 31  
 /**
 32  
  * {@link org.apache.commons.beanutils.Converter} implementaion that handles conversion
 33  
  * to and from <b>java.lang.Number</b> objects.
 34  
  * <p>
 35  
  * This implementation handles conversion for the following
 36  
  * <code>java.lang.Number</code> types.
 37  
  * <ul>
 38  
  *     <li><code>java.lang.Byte</code></li>
 39  
  *     <li><code>java.lang.Short</code></li>
 40  
  *     <li><code>java.lang.Integer</code></li>
 41  
  *     <li><code>java.lang.Long</code></li>
 42  
  *     <li><code>java.lang.Float</code></li>
 43  
  *     <li><code>java.lang.Double</code></li>
 44  
  *     <li><code>java.math.BigDecimal</code></li>
 45  
  *     <li><code>java.math.BigInteger</code></li>
 46  
  * </ul>
 47  
  *
 48  
  * <h3>String Conversions (to and from)</h3>
 49  
  * This class provides a number of ways in which number
 50  
  * conversions to/from Strings can be achieved:
 51  
  * <ul>
 52  
  *    <li>Using the default format for the default Locale, configure using:</li>
 53  
  *        <ul>
 54  
  *           <li><code>setUseLocaleFormat(true)</code></li>
 55  
  *        </ul>
 56  
  *    <li>Using the default format for a specified Locale, configure using:</li>
 57  
  *        <ul>
 58  
  *           <li><code>setLocale(Locale)</code></li>
 59  
  *        </ul>
 60  
  *    <li>Using a specified pattern for the default Locale, configure using:</li>
 61  
  *        <ul>
 62  
  *           <li><code>setPattern(String)</code></li>
 63  
  *        </ul>
 64  
  *    <li>Using a specified pattern for a specified Locale, configure using:</li>
 65  
  *        <ul>
 66  
  *           <li><code>setPattern(String)</code></li>
 67  
  *           <li><code>setLocale(Locale)</code></li>
 68  
  *        </ul>
 69  
  *    <li>If none of the above are configured the
 70  
  *        <code>toNumber(String)</code> method is used to convert
 71  
  *        from String to Number and the Number's
 72  
  *        <code>toString()</code> method used to convert from
 73  
  *        Number to String.</li>
 74  
  * </ul>
 75  
  *
 76  
  * <p>
 77  
  * <strong>N.B.</strong>Patterns can only be specified used the <i>standard</i>
 78  
  * pattern characters and NOT in <i>localized</i> form (see <code>java.text.SimpleDateFormat</code>).
 79  
  * For example to cater for number styles used in Germany such as <code>0.000,00</code> the pattern
 80  
  * is specified in the normal form <code>0,000.00</code> and the locale set to <code>Locale.GERMANY</code>.
 81  
  *
 82  
  * @version $Revision: 745081 $ $Date: 2009-02-17 09:05:20 -0500 (Tue, 17 Feb 2009) $
 83  
  * @since 1.8.0
 84  
  */
 85  
 public abstract class NumberConverter extends AbstractConverter {
 86  
 
 87  1
     private static final Integer ZERO = new Integer(0);
 88  1
     private static final Integer ONE  = new Integer(1);
 89  
 
 90  
     private String pattern;
 91  
     private boolean allowDecimals;
 92  
     private boolean useLocaleFormat;
 93  
     private Locale locale;
 94  
 
 95  
     // ----------------------------------------------------------- Constructors
 96  
 
 97  
     /**
 98  
      * Construct a <b>java.lang.Number</b> <i>Converter</i>
 99  
      * 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  6021
         super();
 105  6021
         this.allowDecimals = allowDecimals;
 106  6021
     }
 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  5001
         super();
 117  5001
         this.allowDecimals = allowDecimals;
 118  5001
         setDefaultValue(defaultValue);
 119  5001
     }
 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  0
         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  74
         this.useLocaleFormat = useLocaleFormat;
 141  74
     }
 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  0
         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  17
         this.pattern = pattern;
 168  17
         setUseLocaleFormat(true);
 169  17
     }
 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  0
         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  33
         this.locale = locale;
 188  33
         setUseLocaleFormat(true);
 189  33
     }
 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  123
         String result = null;
 203  123
         if (useLocaleFormat && value instanceof Number) {
 204  78
             NumberFormat format = getFormat();
 205  78
             format.setGroupingUsed(false);
 206  78
             result = format.format(value);
 207  78
             if (log().isDebugEnabled()) {
 208  0
                 log().debug("    Converted  to String using format '" + result + "'");
 209  
             }
 210  
 
 211  78
         } else {
 212  45
             result = value.toString();
 213  45
             if (log().isDebugEnabled()) {
 214  0
                 log().debug("    Converted  to String using toString() '" + result + "'");
 215  
             }
 216  
         }
 217  123
         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  4461
         Class sourceType = value.getClass();
 233  
         // Handle Number
 234  4461
         if (value instanceof Number) {
 235  3939
             return toNumber(sourceType, targetType, (Number)value);
 236  
         }
 237  
 
 238  
         // Handle Boolean
 239  522
         if (value instanceof Boolean) {
 240  16
             return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
 241  
         }
 242  
 
 243  
         // Handle Date --> Long
 244  506
         if (value instanceof Date && Long.class.equals(targetType)) {
 245  8
             return new Long(((Date)value).getTime());
 246  
         }
 247  
 
 248  
         // Handle Calendar --> Long
 249  498
         if (value instanceof Calendar  && Long.class.equals(targetType)) {
 250  8
             return new Long(((Calendar)value).getTime().getTime());
 251  
         }
 252  
 
 253  
         // Convert all other types to String & handle
 254  490
         String stringValue = value.toString().trim();
 255  490
         if (stringValue.length() == 0) {
 256  2
             return handleMissing(targetType);
 257  
         }
 258  
 
 259  
         // Convert/Parse a String
 260  488
         Number number = null;
 261  488
         if (useLocaleFormat) {
 262  116
             NumberFormat format = getFormat();
 263  116
             number = parse(sourceType, targetType, stringValue, format);
 264  84
         } else {
 265  372
             if (log().isDebugEnabled()) {
 266  0
                 log().debug("    No NumberFormat, using default conversion");
 267  
             }
 268  372
             number = toNumber(sourceType, targetType, stringValue);
 269  
         }
 270  
 
 271  
         // Ensure the correct number type is returned
 272  389
         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  4344
         if (targetType.equals(value.getClass())) {
 301  315
             return value;
 302  
         }
 303  
 
 304  
         // Byte
 305  4029
         if (targetType.equals(Byte.class)) {
 306  769
             long longValue = value.longValue();
 307  769
             if (longValue > Byte.MAX_VALUE) {
 308  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 309  
                         + "' is too large for " + toString(targetType));
 310  
             }
 311  768
             if (longValue < Byte.MIN_VALUE) {
 312  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 313  
                         + "' is too small " + toString(targetType));
 314  
             }
 315  767
             return new Byte(value.byteValue());
 316  
         }
 317  
 
 318  
         // Short
 319  3260
         if (targetType.equals(Short.class)) {
 320  769
             long longValue = value.longValue();
 321  769
             if (longValue > Short.MAX_VALUE) {
 322  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 323  
                         + "' is too large for " + toString(targetType));
 324  
             }
 325  768
             if (longValue < Short.MIN_VALUE) {
 326  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 327  
                         + "' is too small " + toString(targetType));
 328  
             }
 329  767
             return new Short(value.shortValue());
 330  
         }
 331  
 
 332  
         // Integer
 333  2491
         if (targetType.equals(Integer.class)) {
 334  101
             long longValue = value.longValue();
 335  101
             if (longValue > Integer.MAX_VALUE) {
 336  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 337  
                         + "' is too large for " + toString(targetType));
 338  
             }
 339  100
             if (longValue < Integer.MIN_VALUE) {
 340  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 341  
                         + "' is too small " + toString(targetType));
 342  
             }
 343  99
             return new Integer(value.intValue());
 344  
         }
 345  
 
 346  
         // Long
 347  2390
         if (targetType.equals(Long.class)) {
 348  776
             return new Long(value.longValue());
 349  
         }
 350  
 
 351  
         // Float
 352  1614
         if (targetType.equals(Float.class)) {
 353  773
             if (value.doubleValue() > Float.MAX_VALUE) {
 354  1
                 throw new ConversionException(toString(sourceType) + " value '" + value
 355  
                         + "' is too large for " + toString(targetType));
 356  
             }
 357  772
             return new Float(value.floatValue());
 358  
         }
 359  
 
 360  
         // Double
 361  841
         if (targetType.equals(Double.class)) {
 362  771
             return new Double(value.doubleValue());
 363  
         }
 364  
 
 365  
         // BigDecimal
 366  70
         if (targetType.equals(BigDecimal.class)) {
 367  31
             if (value instanceof Float || value instanceof Double) {
 368  6
                 return new BigDecimal(value.toString());
 369  25
             } else if (value instanceof BigInteger) {
 370  1
                 return new BigDecimal((BigInteger)value);
 371  
             } else {
 372  24
                 return BigDecimal.valueOf(value.longValue());
 373  
             }
 374  
         }
 375  
 
 376  
         // BigInteger
 377  39
         if (targetType.equals(BigInteger.class)) {
 378  31
             if (value instanceof BigDecimal) {
 379  1
                 return ((BigDecimal)value).toBigInteger();
 380  
             } else {
 381  30
                 return BigInteger.valueOf(value.longValue());
 382  
             }
 383  
         }
 384  
 
 385  8
         String msg = toString(getClass()) + " cannot handle conversion to '"
 386  
                    + toString(targetType) + "'";
 387  8
         if (log().isWarnEnabled()) {
 388  8
             log().warn("    " + msg);
 389  
         }
 390  8
         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  372
         if (targetType.equals(Byte.class)) {
 418  35
             return new Byte(value);
 419  
         }
 420  
 
 421  
         // Short
 422  337
         if (targetType.equals(Short.class)) {
 423  38
             return new Short(value);
 424  
         }
 425  
 
 426  
         // Integer
 427  299
         if (targetType.equals(Integer.class)) {
 428  164
             return new Integer(value);
 429  
         }
 430  
 
 431  
         // Long
 432  135
         if (targetType.equals(Long.class)) {
 433  33
             return new Long(value);
 434  
         }
 435  
 
 436  
         // Float
 437  102
         if (targetType.equals(Float.class)) {
 438  32
             return new Float(value);
 439  
         }
 440  
 
 441  
         // Double
 442  70
         if (targetType.equals(Double.class)) {
 443  38
             return new Double(value);
 444  
         }
 445  
 
 446  
         // BigDecimal
 447  32
         if (targetType.equals(BigDecimal.class)) {
 448  14
             return new BigDecimal(value);
 449  
         }
 450  
 
 451  
         // BigInteger
 452  18
         if (targetType.equals(BigInteger.class)) {
 453  18
             return new BigInteger(value);
 454  
         }
 455  
 
 456  0
         String msg = toString(getClass()) + " cannot handle conversion from '" +
 457  
                      toString(sourceType) + "' to '" + toString(targetType) + "'";
 458  0
         if (log().isWarnEnabled()) {
 459  0
             log().warn("    " + msg);
 460  
         }
 461  0
         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  155
         StringBuffer buffer = new StringBuffer();
 471  155
         buffer.append(toString(getClass()));
 472  155
         buffer.append("[UseDefault=");
 473  155
         buffer.append(isUseDefault());
 474  155
         buffer.append(", UseLocaleFormat=");
 475  155
         buffer.append(useLocaleFormat);
 476  155
         if (pattern != null) {
 477  0
             buffer.append(", Pattern=");
 478  0
             buffer.append(pattern);
 479  
         }
 480  155
         if (locale != null) {
 481  0
             buffer.append(", Locale=");
 482  0
             buffer.append(locale);
 483  
         }
 484  155
         buffer.append(']');
 485  155
         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  194
         NumberFormat format = null;
 495  194
         if (pattern != null) {
 496  114
             if (locale == null) {
 497  32
                 if (log().isDebugEnabled()) {
 498  0
                     log().debug("    Using pattern '" + pattern + "'");
 499  
                 }
 500  32
                 format = new DecimalFormat(pattern);
 501  
             } else {
 502  82
                 if (log().isDebugEnabled()) {
 503  0
                     log().debug("    Using pattern '" + pattern + "'" +
 504  
                               " with Locale[" + locale + "]");
 505  
                 }
 506  82
                 DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
 507  82
                 format = new DecimalFormat(pattern, symbols);
 508  82
             }
 509  
         } else {
 510  80
             if (locale == null) {
 511  40
                 if (log().isDebugEnabled()) {
 512  0
                     log().debug("    Using default Locale format");
 513  
                 }
 514  40
                 format = NumberFormat.getInstance();
 515  
             } else {
 516  40
                 if (log().isDebugEnabled()) {
 517  0
                     log().debug("    Using Locale[" + locale + "] format");
 518  
                 }
 519  40
                 format = NumberFormat.getInstance(locale);
 520  
             }
 521  
         }
 522  194
         if (!allowDecimals) {
 523  134
             format.setParseIntegerOnly(true);
 524  
         }
 525  194
         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  116
         ParsePosition pos = new ParsePosition(0);
 540  116
         Number parsedNumber = format.parse(value, pos);
 541  116
         if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) {
 542  32
             String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
 543  32
             if (format instanceof DecimalFormat) {
 544  32
                 msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'";
 545  
             }
 546  32
             if (locale != null) {
 547  24
                 msg += " for locale=[" + locale + "]";
 548  
             }
 549  32
             if (log().isDebugEnabled()) {
 550  0
                 log().debug("    " + msg);
 551  
             }
 552  32
             throw new ConversionException(msg);
 553  
         }
 554  84
         return parsedNumber;
 555  
     }
 556  
 
 557  
 }