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: NumberConverter.java 1546738 2013-11-30 16:24:19Z oheger $
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(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(boolean allowDecimals, 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(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(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(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(Object value) throws Throwable {
202
203        String result = null;
204        if (useLocaleFormat && value instanceof Number) {
205            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(Class<T> targetType, Object value) throws Throwable {
234
235        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        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            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(Class<?> sourceType, Class<T> targetType, 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            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            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            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 {
375                return targetType.cast(BigDecimal.valueOf(value.longValue()));
376            }
377        }
378
379        // BigInteger
380        if (targetType.equals(BigInteger.class)) {
381            if (value instanceof BigDecimal) {
382                return targetType.cast(((BigDecimal)value).toBigInteger());
383            } else {
384                return targetType.cast(BigInteger.valueOf(value.longValue()));
385            }
386        }
387
388        String msg = toString(getClass()) + " cannot handle conversion to '"
389                   + toString(targetType) + "'";
390        if (log().isWarnEnabled()) {
391            log().warn("    " + msg);
392        }
393        throw new ConversionException(msg);
394
395    }
396
397    /**
398     * Default String to Number conversion.
399     * <p>
400     * This method handles conversion from a String to the following types:
401     * <ul>
402     *     <li><code>java.lang.Byte</code></li>
403     *     <li><code>java.lang.Short</code></li>
404     *     <li><code>java.lang.Integer</code></li>
405     *     <li><code>java.lang.Long</code></li>
406     *     <li><code>java.lang.Float</code></li>
407     *     <li><code>java.lang.Double</code></li>
408     *     <li><code>java.math.BigDecimal</code></li>
409     *     <li><code>java.math.BigInteger</code></li>
410     * </ul>
411     * @param sourceType The type being converted from
412     * @param targetType The Number type to convert to
413     * @param value The String value to convert.
414     *
415     * @return The converted Number value.
416     */
417    private Number toNumber(Class<?> sourceType, Class<?> targetType, String value) {
418
419        // Byte
420        if (targetType.equals(Byte.class)) {
421            return new Byte(value);
422        }
423
424        // Short
425        if (targetType.equals(Short.class)) {
426            return new Short(value);
427        }
428
429        // Integer
430        if (targetType.equals(Integer.class)) {
431            return new Integer(value);
432        }
433
434        // Long
435        if (targetType.equals(Long.class)) {
436            return new Long(value);
437        }
438
439        // Float
440        if (targetType.equals(Float.class)) {
441            return new Float(value);
442        }
443
444        // Double
445        if (targetType.equals(Double.class)) {
446            return new Double(value);
447        }
448
449        // BigDecimal
450        if (targetType.equals(BigDecimal.class)) {
451            return new BigDecimal(value);
452        }
453
454        // BigInteger
455        if (targetType.equals(BigInteger.class)) {
456            return new BigInteger(value);
457        }
458
459        String msg = toString(getClass()) + " cannot handle conversion from '" +
460                     toString(sourceType) + "' to '" + toString(targetType) + "'";
461        if (log().isWarnEnabled()) {
462            log().warn("    " + msg);
463        }
464        throw new ConversionException(msg);
465    }
466
467    /**
468     * Provide a String representation of this number converter.
469     *
470     * @return A String representation of this number converter
471     */
472    @Override
473    public String toString() {
474        StringBuilder buffer = new StringBuilder();
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 the source type of the conversion
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 = 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}