View Javadoc

    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: 555845 $ $Date: 2007-07-13 03:52:05 +0100 (Fri, 13 Jul 2007) $
    83   * @since 1.8.0
    84   */
    85  public class NumberConverter extends AbstractConverter {
    86
    87      private static final Integer ZERO = new Integer(0);
    88      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 defaultType The default type this <code>Converter</code>
    102      * handles
    103      * @param allowDecimals Indicates whether decimals are allowed
    104      */
    105     public NumberConverter(Class defaultType, boolean allowDecimals) {
    106         super(defaultType);
    107         this.allowDecimals = allowDecimals;
    108     }
    109
    110     /***
    111      * Construct a <code>java.lang.Number</code> <i>Converter</i> that returns
    112      * a default value if an error occurs.
    113      *
    114      * @param defaultType The default type this <code>Converter</code>
    115      * handles
    116      * @param allowDecimals Indicates whether decimals are allowed
    117      * @param defaultValue The default value to be returned
    118      */
    119     public NumberConverter(Class defaultType, boolean allowDecimals, Object defaultValue) {
    120         super(defaultType);
    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(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.SimpleDateFormat</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.SimpleDateFormat</code> for details
    166      * of how to specify the pattern.
    167      *
    168      * @param pattern The format pattern.
    169      */
    170     public void setPattern(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(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     protected String convertToString(Object value) throws Throwable {
    205
    206         String result = null;
    207         if (useLocaleFormat && value instanceof Number) {
    208             NumberFormat format = getFormat();
    209             format.setGroupingUsed(false);
    210             result = format.format(value);
    211             if (log().isDebugEnabled()) {
    212                 log().debug("    Converted  to String using format '" + result + "'");
    213             }
    214
    215         } else {
    216             result = value.toString();
    217             if (log().isDebugEnabled()) {
    218                 log().debug("    Converted  to String using toString() '" + result + "'");
    219             }
    220         }
    221         return result;
    222
    223     }
    224
    225     /***
    226      * Convert the input object into a Number object of the
    227      * specified type.
    228      *
    229      * @param targetType Data type to which this value should be converted.
    230      * @param value The input value to be converted.
    231      * @return The converted value.
    232      * @throws Throwable if an error occurs converting to the specified type
    233      */
    234     protected Object convertToType(Class targetType, Object value) throws Throwable {
    235
    236         Class sourceType = value.getClass();
    237         // Handle Number
    238         if (value instanceof Number) {
    239             return toNumber(sourceType, targetType, (Number)value);
    240         }
    241
    242         // Handle Boolean
    243         if (value instanceof Boolean) {
    244             return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
    245         }
    246
    247         // Handle Date --> Long
    248         if (value instanceof Date && Long.class.equals(targetType)) {
    249             return new Long(((Date)value).getTime());
    250         }
    251
    252         // Handle Calendar --> Long
    253         if (value instanceof Calendar  && Long.class.equals(targetType)) {
    254             return new Long(((Calendar)value).getTime().getTime());
    255         }
    256
    257         // Convert all other types to String & handle
    258         String stringValue = value.toString().trim();
    259         if (stringValue.length() == 0) {
    260             return handleMissing(targetType);
    261         }
    262
    263         // Convert/Parse a String
    264         Number number = null;
    265         if (useLocaleFormat) {
    266             NumberFormat format = getFormat();
    267             number = parse(sourceType, targetType, stringValue, format);
    268         } else {
    269             if (log().isDebugEnabled()) {
    270                 log().debug("    No NumberFormat, using default conversion");
    271             }
    272             number = toNumber(sourceType, targetType, stringValue);
    273         }
    274
    275         // Ensure the correct number type is returned
    276         return toNumber(sourceType, targetType, number);
    277
    278     }
    279
    280     /***
    281      * Convert any Number object to the specified type for this
    282      * <i>Converter</i>.
    283      * <p>
    284      * This method handles conversion to the following types:
    285      * <ul>
    286      *     <li><code>java.lang.Byte</code></li>
    287      *     <li><code>java.lang.Short</code></li>
    288      *     <li><code>java.lang.Integer</code></li>
    289      *     <li><code>java.lang.Long</code></li>
    290      *     <li><code>java.lang.Float</code></li>
    291      *     <li><code>java.lang.Double</code></li>
    292      *     <li><code>java.math.BigDecimal</code></li>
    293      *     <li><code>java.math.BigInteger</code></li>
    294      * </ul>
    295      * @param sourceType The type being converted from
    296      * @param targetType The Number type to convert to
    297      * @param value The Number to convert.
    298      *
    299      * @return The converted value.
    300      */
    301     private Number toNumber(Class sourceType, Class targetType, Number value) {
    302
    303         // Correct Number type already
    304         if (targetType.equals(value.getClass())) {
    305             return value;
    306         }
    307
    308         // Byte
    309         if (targetType.equals(Byte.class)) {
    310             long longValue = value.longValue();
    311             if (longValue > Byte.MAX_VALUE) {
    312                 throw new ConversionException(toString(sourceType) + " value '" + value
    313                         + "' is too large for " + toString(targetType));
    314             }
    315             if (longValue < Byte.MIN_VALUE) {
    316                 throw new ConversionException(toString(sourceType) + " value '" + value
    317                         + "' is too small " + toString(targetType));
    318             }
    319             return new Byte(value.byteValue());
    320         }
    321
    322         // Short
    323         if (targetType.equals(Short.class)) {
    324             long longValue = value.longValue();
    325             if (longValue > Short.MAX_VALUE) {
    326                 throw new ConversionException(toString(sourceType) + " value '" + value
    327                         + "' is too large for " + toString(targetType));
    328             }
    329             if (longValue < Short.MIN_VALUE) {
    330                 throw new ConversionException(toString(sourceType) + " value '" + value
    331                         + "' is too small " + toString(targetType));
    332             }
    333             return new Short(value.shortValue());
    334         }
    335
    336         // Integer
    337         if (targetType.equals(Integer.class)) {
    338             long longValue = value.longValue();
    339             if (longValue > Integer.MAX_VALUE) {
    340                 throw new ConversionException(toString(sourceType) + " value '" + value
    341                         + "' is too large for " + toString(targetType));
    342             }
    343             if (longValue < Integer.MIN_VALUE) {
    344                 throw new ConversionException(toString(sourceType) + " value '" + value
    345                         + "' is too small " + toString(targetType));
    346             }
    347             return new Integer(value.intValue());
    348         }
    349
    350         // Long
    351         if (targetType.equals(Long.class)) {
    352             return new Long(value.longValue());
    353         }
    354
    355         // Float
    356         if (targetType.equals(Float.class)) {
    357             if (value.doubleValue() > Float.MAX_VALUE) {
    358                 throw new ConversionException(toString(sourceType) + " value '" + value
    359                         + "' is too large for " + toString(targetType));
    360             }
    361             return new Float(value.floatValue());
    362         }
    363
    364         // Double
    365         if (targetType.equals(Double.class)) {
    366             return new Double(value.doubleValue());
    367         }
    368
    369         // BigDecimal
    370         if (targetType.equals(BigDecimal.class)) {
    371             if (value instanceof Float || value instanceof Double) {
    372                 return new BigDecimal(value.toString());
    373             } else if (value instanceof BigInteger) {
    374                 return new BigDecimal((BigInteger)value);
    375             } else {
    376                 return BigDecimal.valueOf(value.longValue());
    377             }
    378         }
    379
    380         // BigInteger
    381         if (targetType.equals(BigInteger.class)) {
    382             if (value instanceof BigDecimal) {
    383                 return ((BigDecimal)value).toBigInteger();
    384             } else {
    385                 return BigInteger.valueOf(value.longValue());
    386             }
    387         }
    388
    389         String msg = toString(getClass()) + " cannot handle conversion to '"
    390                    + toString(targetType) + "'";
    391         if (log().isWarnEnabled()) {
    392             log().warn("    " + msg);
    393         }
    394         throw new ConversionException(msg);
    395
    396     }
    397
    398     /***
    399      * Default String to Number conversion.
    400      * <p>
    401      * This method handles conversion from a String to the following types:
    402      * <ul>
    403      *     <li><code>java.lang.Byte</code></li>
    404      *     <li><code>java.lang.Short</code></li>
    405      *     <li><code>java.lang.Integer</code></li>
    406      *     <li><code>java.lang.Long</code></li>
    407      *     <li><code>java.lang.Float</code></li>
    408      *     <li><code>java.lang.Double</code></li>
    409      *     <li><code>java.math.BigDecimal</code></li>
    410      *     <li><code>java.math.BigInteger</code></li>
    411      * </ul>
    412      * @param sourceType The type being converted from
    413      * @param targetType The Number type to convert to
    414      * @param value The String value to convert.
    415      *
    416      * @return The converted Number value.
    417      */
    418     private Number toNumber(Class sourceType, Class targetType, String value) {
    419
    420         // Byte
    421         if (targetType.equals(Byte.class)) {
    422             return new Byte(value);
    423         }
    424
    425         // Short
    426         if (targetType.equals(Short.class)) {
    427             return new Short(value);
    428         }
    429
    430         // Integer
    431         if (targetType.equals(Integer.class)) {
    432             return new Integer(value);
    433         }
    434
    435         // Long
    436         if (targetType.equals(Long.class)) {
    437             return new Long(value);
    438         }
    439
    440         // Float
    441         if (targetType.equals(Float.class)) {
    442             return new Float(value);
    443         }
    444
    445         // Double
    446         if (targetType.equals(Double.class)) {
    447             return new Double(value);
    448         }
    449
    450         // BigDecimal
    451         if (targetType.equals(BigDecimal.class)) {
    452             return new BigDecimal(value);
    453         }
    454
    455         // BigInteger
    456         if (targetType.equals(BigInteger.class)) {
    457             return new BigInteger(value);
    458         }
    459
    460         String msg = toString(getClass()) + " cannot handle conversion from '" +
    461                      toString(sourceType) + "' to '" + toString(targetType) + "'";
    462         if (log().isWarnEnabled()) {
    463             log().warn("    " + msg);
    464         }
    465         throw new ConversionException(msg);
    466     }
    467
    468     /***
    469      * Provide a String representation of this number converter.
    470      *
    471      * @return A String representation of this number converter
    472      */
    473     public String toString() {
    474         StringBuffer buffer = new StringBuffer();
    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 TODO
    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 = (Number)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 }