    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}