    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} 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     * Return 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}