001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.beanutils.converters;
018
019import java.math.BigDecimal;
020import java.math.BigInteger;
021import java.text.DecimalFormat;
022import java.text.DecimalFormatSymbols;
023import java.text.NumberFormat;
024import java.text.ParsePosition;
025import java.util.Calendar;
026import java.util.Date;
027import java.util.Locale;
028
029import org.apache.commons.beanutils.ConversionException;
030
031/**
032 * {@link org.apache.commons.beanutils.Converter} implementation that handles conversion
033 * to and from <strong>java.lang.Number</strong> objects.
034 * <p>
035 * This implementation handles conversion for the following
036 * <code>java.lang.Number</code> types.
037 * <ul>
038 *     <li><code>java.lang.Byte</code></li>
039 *     <li><code>java.lang.Short</code></li>
040 *     <li><code>java.lang.Integer</code></li>
041 *     <li><code>java.lang.Long</code></li>
042 *     <li><code>java.lang.Float</code></li>
043 *     <li><code>java.lang.Double</code></li>
044 *     <li><code>java.math.BigDecimal</code></li>
045 *     <li><code>java.math.BigInteger</code></li>
046 * </ul>
047 *
048 * <h2>String Conversions (to and from)</h2>
049 * This class provides a number of ways in which number
050 * conversions to/from Strings can be achieved:
051 * <ul>
052 *    <li>Using the default format for the default Locale, configure using:
053 *        <ul>
054 *           <li><code>setUseLocaleFormat(true)</code></li>
055 *        </ul>
056 *    </li>
057 *    <li>Using the default format for a specified Locale, configure using:
058 *        <ul>
059 *           <li><code>setLocale(Locale)</code></li>
060 *        </ul>
061 *    </li>
062 *    <li>Using a specified pattern for the default Locale, configure using:
063 *        <ul>
064 *           <li><code>setPattern(String)</code></li>
065 *        </ul>
066 *    </li>
067 *    <li>Using a specified pattern for a specified Locale, configure using:
068 *        <ul>
069 *           <li><code>setPattern(String)</code></li>
070 *           <li><code>setLocale(Locale)</code></li>
071 *        </ul>
072 *    </li>
073 *    <li>If none of the above are configured the
074 *        <code>toNumber(String)</code> method is used to convert
075 *        from String to Number and the Number's
076 *        <code>toString()</code> method used to convert from
077 *        Number to String.</li>
078 * </ul>
079 *
080 * <p>
081 * <strong>N.B.</strong>Patterns can only be specified using the <em>standard</em>
082 * pattern characters and NOT in <em>localized</em> form (see <code>java.text.DecimalFormat</code>).
083 * For example to cater for number styles used in Germany such as <code>0.000,00</code> the pattern
084 * is specified in the normal form <code>0,000.00</code> and the locale set to <code>Locale.GERMANY</code>.
085 * </p>
086 *
087 * @since 1.8.0
088 */
089public abstract class NumberConverter extends AbstractConverter {
090
091    private static final Integer ZERO = Integer.valueOf(0);
092    private static final Integer ONE  = Integer.valueOf(1);
093
094    private String pattern;
095    private final boolean allowDecimals;
096    private boolean useLocaleFormat;
097    private Locale locale;
098
099    /**
100     * Construct a <strong>java.lang.Number</strong> <em>Converter</em>
101     * that throws a <code>ConversionException</code> if a error occurs.
102     *
103     * @param allowDecimals Indicates whether decimals are allowed
104     */
105    public NumberConverter(final boolean allowDecimals) {
106        this.allowDecimals = allowDecimals;
107    }
108
109    /**
110     * Construct a <code>java.lang.Number</code> <em>Converter</em> that returns
111     * a default value if an error occurs.
112     *
113     * @param allowDecimals Indicates whether decimals are allowed
114     * @param defaultValue The default value to be returned
115     */
116    public NumberConverter(final boolean allowDecimals, final Object defaultValue) {
117        this.allowDecimals = allowDecimals;
118        setDefaultValue(defaultValue);
119    }
120
121    /**
122     * Convert an input Number object into a String.
123     *
124     * @param value The input value to be converted
125     * @return the converted String value.
126     * @throws Throwable if an error occurs converting to a String
127     */
128    @Override
129    protected String convertToString(final Object value) throws Throwable {
130
131        String result = null;
132        if (useLocaleFormat && value instanceof Number) {
133            final NumberFormat format = getFormat();
134            format.setGroupingUsed(false);
135            result = format.format(value);
136            if (log().isDebugEnabled()) {
137                log().debug("    Converted  to String using format '" + result + "'");
138            }
139
140        } else {
141            result = value.toString();
142            if (log().isDebugEnabled()) {
143                log().debug("    Converted  to String using toString() '" + result + "'");
144            }
145        }
146        return result;
147
148    }
149
150    /**
151     * Convert the input object into a Number object of the
152     * specified type.
153     *
154     * @param <T> Target type of the conversion.
155     * @param targetType Data type to which this value should be converted.
156     * @param value The input value to be converted.
157     * @return The converted value.
158     * @throws Throwable if an error occurs converting to the specified type
159     */
160    @Override
161    protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable {
162
163        final Class<?> sourceType = value.getClass();
164        // Handle Number
165        if (value instanceof Number) {
166            return toNumber(sourceType, targetType, (Number)value);
167        }
168
169        // Handle Boolean
170        if (value instanceof Boolean) {
171            return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
172        }
173
174        // Handle Date --> Long
175        if (value instanceof Date && Long.class.equals(targetType)) {
176            return targetType.cast(Long.valueOf(((Date)value).getTime()));
177        }
178
179        // Handle Calendar --> Long
180        if (value instanceof Calendar  && Long.class.equals(targetType)) {
181            return targetType.cast(Long.valueOf(((Calendar)value).getTime().getTime()));
182        }
183
184        // Convert all other types to String & handle
185        final String stringValue = value.toString().trim();
186        if (stringValue.length() == 0) {
187            return handleMissing(targetType);
188        }
189
190        // Convert/Parse a String
191        Number number = null;
192        if (useLocaleFormat) {
193            final NumberFormat format = getFormat();
194            number = parse(sourceType, targetType, stringValue, format);
195        } else {
196            if (log().isDebugEnabled()) {
197                log().debug("    No NumberFormat, using default conversion");
198            }
199            number = toNumber(sourceType, targetType, stringValue);
200        }
201
202        // Ensure the correct number type is returned
203        return toNumber(sourceType, targetType, number);
204
205    }
206
207    /**
208     * Return a NumberFormat to use for Conversion.
209     *
210     * @return The NumberFormat.
211     */
212    private NumberFormat getFormat() {
213        NumberFormat format = null;
214        if (pattern != null) {
215            if (locale == null) {
216                if (log().isDebugEnabled()) {
217                    log().debug("    Using pattern '" + pattern + "'");
218                }
219                format = new DecimalFormat(pattern);
220            } else {
221                if (log().isDebugEnabled()) {
222                    log().debug("    Using pattern '" + pattern + "'" +
223                              " with Locale[" + locale + "]");
224                }
225                final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
226                format = new DecimalFormat(pattern, symbols);
227            }
228        } else if (locale == null) {
229            if (log().isDebugEnabled()) {
230                log().debug("    Using default Locale format");
231            }
232            format = NumberFormat.getInstance();
233        } else {
234            if (log().isDebugEnabled()) {
235                log().debug("    Using Locale[" + locale + "] format");
236            }
237            format = NumberFormat.getInstance(locale);
238        }
239        if (!allowDecimals) {
240            format.setParseIntegerOnly(true);
241        }
242        return format;
243    }
244
245    /**
246     * Return the Locale for the <em>Converter</em>
247     * (or <code>null</code> if none specified).
248     *
249     * @return The locale to use for conversion
250     */
251    public Locale getLocale() {
252        return locale;
253    }
254
255    /**
256     * Return the number format pattern used to convert
257     * Numbers to/from a <code>java.lang.String</code>
258     * (or <code>null</code> if none specified).
259     * <p>
260     * See <code>java.text.DecimalFormat</code> for details
261     * of how to specify the pattern.
262     *
263     * @return The format pattern.
264     */
265    public String getPattern() {
266        return pattern;
267    }
268
269    /**
270     * Tests whether decimals are allowed in the number.
271     *
272     * @return Whether decimals are allowed in the number
273     */
274    public boolean isAllowDecimals() {
275        return allowDecimals;
276    }
277
278    /**
279     * Convert a String into a <code>Number</code> object.
280     * @param sourceType the source type of the conversion
281     * @param targetType The type to convert the value to
282     * @param value The String date value.
283     * @param format The NumberFormat to parse the String value.
284     * @return The converted Number object.
285     * @throws ConversionException if the String cannot be converted.
286     */
287    private Number parse(final Class<?> sourceType, final Class<?> targetType, final String value, final NumberFormat format) {
288        final ParsePosition pos = new ParsePosition(0);
289        final Number parsedNumber = format.parse(value, pos);
290        if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) {
291            String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
292            if (format instanceof DecimalFormat) {
293                msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'";
294            }
295            if (locale != null) {
296                msg += " for locale=[" + locale + "]";
297            }
298            if (log().isDebugEnabled()) {
299                log().debug("    " + msg);
300            }
301            throw new ConversionException(msg);
302        }
303        return parsedNumber;
304    }
305
306    /**
307     * Set the Locale for the <em>Converter</em>.
308     *
309     * @param locale The locale to use for conversion
310     */
311    public void setLocale(final Locale locale) {
312        this.locale = locale;
313        setUseLocaleFormat(true);
314    }
315
316    /**
317     * Set a number format pattern to use to convert
318     * Numbers to/from a <code>java.lang.String</code>.
319     * <p>
320     * See <code>java.text.DecimalFormat</code> for details
321     * of how to specify the pattern.
322     *
323     * @param pattern The format pattern.
324     */
325    public void setPattern(final String pattern) {
326        this.pattern = pattern;
327        setUseLocaleFormat(true);
328    }
329
330    /**
331     * Set whether a format should be used to convert
332     * the Number.
333     *
334     * @param useLocaleFormat <code>true</code> if a number format
335     * should be used.
336     */
337    public void setUseLocaleFormat(final boolean useLocaleFormat) {
338        this.useLocaleFormat = useLocaleFormat;
339    }
340
341    /**
342     * Default String to Number conversion.
343     * <p>
344     * This method handles conversion from a String to the following types:
345     * <ul>
346     *     <li><code>java.lang.Byte</code></li>
347     *     <li><code>java.lang.Short</code></li>
348     *     <li><code>java.lang.Integer</code></li>
349     *     <li><code>java.lang.Long</code></li>
350     *     <li><code>java.lang.Float</code></li>
351     *     <li><code>java.lang.Double</code></li>
352     *     <li><code>java.math.BigDecimal</code></li>
353     *     <li><code>java.math.BigInteger</code></li>
354     * </ul>
355     * @param sourceType The type being converted from
356     * @param targetType The Number type to convert to
357     * @param value The String value to convert.
358     * @return The converted Number value.
359     */
360    private Number toNumber(final Class<?> sourceType, final Class<?> targetType, final String value) {
361
362        // Byte
363        if (targetType.equals(Byte.class)) {
364            return Byte.valueOf(value);
365        }
366
367        // Short
368        if (targetType.equals(Short.class)) {
369            return Short.valueOf(value);
370        }
371
372        // Integer
373        if (targetType.equals(Integer.class)) {
374            return Integer.valueOf(value);
375        }
376
377        // Long
378        if (targetType.equals(Long.class)) {
379            return Long.valueOf(value);
380        }
381
382        // Float
383        if (targetType.equals(Float.class)) {
384            return Float.valueOf(value);
385        }
386
387        // Double
388        if (targetType.equals(Double.class)) {
389            return Double.valueOf(value);
390        }
391
392        // BigDecimal
393        if (targetType.equals(BigDecimal.class)) {
394            return new BigDecimal(value);
395        }
396
397        // BigInteger
398        if (targetType.equals(BigInteger.class)) {
399            return new BigInteger(value);
400        }
401
402        final String msg = toString(getClass()) + " cannot handle conversion from '" +
403                     toString(sourceType) + "' to '" + toString(targetType) + "'";
404        if (log().isWarnEnabled()) {
405            log().warn("    " + msg);
406        }
407        throw new ConversionException(msg);
408    }
409
410    /**
411     * Convert any Number object to the specified type for this
412     * <em>Converter</em>.
413     * <p>
414     * This method handles conversion to the following types:
415     * <ul>
416     *     <li><code>java.lang.Byte</code></li>
417     *     <li><code>java.lang.Short</code></li>
418     *     <li><code>java.lang.Integer</code></li>
419     *     <li><code>java.lang.Long</code></li>
420     *     <li><code>java.lang.Float</code></li>
421     *     <li><code>java.lang.Double</code></li>
422     *     <li><code>java.math.BigDecimal</code></li>
423     *     <li><code>java.math.BigInteger</code></li>
424     * </ul>
425     * @param sourceType The type being converted from
426     * @param targetType The Number type to convert to
427     * @param value The Number to convert.
428     * @return The converted value.
429     */
430    private <T> T toNumber(final Class<?> sourceType, final Class<T> targetType, final Number value) {
431
432        // Correct Number type already
433        if (targetType.equals(value.getClass())) {
434            return targetType.cast(value);
435        }
436
437        // Byte
438        if (targetType.equals(Byte.class)) {
439            final long longValue = value.longValue();
440            if (longValue > Byte.MAX_VALUE) {
441                throw new ConversionException(toString(sourceType) + " value '" + value
442                        + "' is too large for " + toString(targetType));
443            }
444            if (longValue < Byte.MIN_VALUE) {
445                throw new ConversionException(toString(sourceType) + " value '" + value
446                        + "' is too small " + toString(targetType));
447            }
448            return targetType.cast(Byte.valueOf(value.byteValue()));
449        }
450
451        // Short
452        if (targetType.equals(Short.class)) {
453            final long longValue = value.longValue();
454            if (longValue > Short.MAX_VALUE) {
455                throw new ConversionException(toString(sourceType) + " value '" + value
456                        + "' is too large for " + toString(targetType));
457            }
458            if (longValue < Short.MIN_VALUE) {
459                throw new ConversionException(toString(sourceType) + " value '" + value
460                        + "' is too small " + toString(targetType));
461            }
462            return targetType.cast(Short.valueOf(value.shortValue()));
463        }
464
465        // Integer
466        if (targetType.equals(Integer.class)) {
467            final long longValue = value.longValue();
468            if (longValue > Integer.MAX_VALUE) {
469                throw new ConversionException(toString(sourceType) + " value '" + value
470                        + "' is too large for " + toString(targetType));
471            }
472            if (longValue < Integer.MIN_VALUE) {
473                throw new ConversionException(toString(sourceType) + " value '" + value
474                        + "' is too small " + toString(targetType));
475            }
476            return targetType.cast(Integer.valueOf(value.intValue()));
477        }
478
479        // Long
480        if (targetType.equals(Long.class)) {
481            return targetType.cast(Long.valueOf(value.longValue()));
482        }
483
484        // Float
485        if (targetType.equals(Float.class)) {
486            if (value.doubleValue() > Float.MAX_VALUE) {
487                throw new ConversionException(toString(sourceType) + " value '" + value
488                        + "' is too large for " + toString(targetType));
489            }
490            return targetType.cast(Float.valueOf(value.floatValue()));
491        }
492
493        // Double
494        if (targetType.equals(Double.class)) {
495            return targetType.cast(Double.valueOf(value.doubleValue()));
496        }
497
498        // BigDecimal
499        if (targetType.equals(BigDecimal.class)) {
500            if (value instanceof Float || value instanceof Double) {
501                return targetType.cast(new BigDecimal(value.toString()));
502            }
503            if (value instanceof BigInteger) {
504                return targetType.cast(new BigDecimal((BigInteger)value));
505            }
506            if (value instanceof BigDecimal) {
507                return targetType.cast(new BigDecimal(value.toString()));
508            }
509            return targetType.cast(BigDecimal.valueOf(value.longValue()));
510        }
511
512        // BigInteger
513        if (targetType.equals(BigInteger.class)) {
514            if (value instanceof BigDecimal) {
515                return targetType.cast(((BigDecimal)value).toBigInteger());
516            }
517            return targetType.cast(BigInteger.valueOf(value.longValue()));
518        }
519
520        final String msg = toString(getClass()) + " cannot handle conversion to '"
521                   + toString(targetType) + "'";
522        if (log().isWarnEnabled()) {
523            log().warn("    " + msg);
524        }
525        throw new ConversionException(msg);
526
527    }
528
529    /**
530     * Provide a String representation of this number converter.
531     *
532     * @return A String representation of this number converter
533     */
534    @Override
535    public String toString() {
536        final StringBuilder buffer = new StringBuilder();
537        buffer.append(toString(getClass()));
538        buffer.append("[UseDefault=");
539        buffer.append(isUseDefault());
540        buffer.append(", UseLocaleFormat=");
541        buffer.append(useLocaleFormat);
542        if (pattern != null) {
543            buffer.append(", Pattern=");
544            buffer.append(pattern);
545        }
546        if (locale != null) {
547            buffer.append(", Locale=");
548            buffer.append(locale);
549        }
550        buffer.append(']');
551        return buffer.toString();
552    }
553
554}