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:
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 <i>standard</i>
082 * pattern characters and NOT in <i>localized</i> 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 *
086 * @version $Id$
087 * @since 1.8.0
088 */
089public abstract class NumberConverter extends AbstractConverter {
090
091    private static final Integer ZERO = new Integer(0);
092    private static final Integer ONE  = new Integer(1);
093
094    private String pattern;
095    private final boolean allowDecimals;
096    private boolean useLocaleFormat;
097    private Locale locale;
098
099    // ----------------------------------------------------------- Constructors
100
101    /**
102     * Construct a <b>java.lang.Number</b> <i>Converter</i>
103     * that throws a <code>ConversionException</code> if a error occurs.
104     *
105     * @param allowDecimals Indicates whether decimals are allowed
106     */
107    public NumberConverter(final boolean allowDecimals) {
108        super();
109        this.allowDecimals = allowDecimals;
110    }
111
112    /**
113     * Construct a <code>java.lang.Number</code> <i>Converter</i> that returns
114     * a default value if an error occurs.
115     *
116     * @param allowDecimals Indicates whether decimals are allowed
117     * @param defaultValue The default value to be returned
118     */
119    public NumberConverter(final boolean allowDecimals, final Object defaultValue) {
120        super();
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(final 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.DecimalFormat</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.DecimalFormat</code> for details
166     * of how to specify the pattern.
167     *
168     * @param pattern The format pattern.
169     */
170    public void setPattern(final 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(final 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    @Override
205    protected String convertToString(final Object value) throws Throwable {
206
207        String result = null;
208        if (useLocaleFormat && value instanceof Number) {
209            final NumberFormat format = getFormat();
210            format.setGroupingUsed(false);
211            result = format.format(value);
212            if (log().isDebugEnabled()) {
213                log().debug("    Converted  to String using format '" + result + "'");
214            }
215
216        } else {
217            result = value.toString();
218            if (log().isDebugEnabled()) {
219                log().debug("    Converted  to String using toString() '" + result + "'");
220            }
221        }
222        return result;
223
224    }
225
226    /**
227     * Convert the input object into a Number object of the
228     * specified type.
229     *
230     * @param <T> Target type of the conversion.
231     * @param targetType Data type to which this value should be converted.
232     * @param value The input value to be converted.
233     * @return The converted value.
234     * @throws Throwable if an error occurs converting to the specified type
235     */
236    @Override
237    protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable {
238
239        final Class<?> sourceType = value.getClass();
240        // Handle Number
241        if (value instanceof Number) {
242            return toNumber(sourceType, targetType, (Number)value);
243        }
244
245        // Handle Boolean
246        if (value instanceof Boolean) {
247            return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
248        }
249
250        // Handle Date --> Long
251        if (value instanceof Date && Long.class.equals(targetType)) {
252            return targetType.cast(new Long(((Date)value).getTime()));
253        }
254
255        // Handle Calendar --> Long
256        if (value instanceof Calendar  && Long.class.equals(targetType)) {
257            return targetType.cast(new Long(((Calendar)value).getTime().getTime()));
258        }
259
260        // Convert all other types to String & handle
261        final String stringValue = value.toString().trim();
262        if (stringValue.length() == 0) {
263            return handleMissing(targetType);
264        }
265
266        // Convert/Parse a String
267        Number number = null;
268        if (useLocaleFormat) {
269            final NumberFormat format = getFormat();
270            number = parse(sourceType, targetType, stringValue, format);
271        } else {
272            if (log().isDebugEnabled()) {
273                log().debug("    No NumberFormat, using default conversion");
274            }
275            number = toNumber(sourceType, targetType, stringValue);
276        }
277
278        // Ensure the correct number type is returned
279        return toNumber(sourceType, targetType, number);
280
281    }
282
283    /**
284     * Convert any Number object to the specified type for this
285     * <i>Converter</i>.
286     * <p>
287     * This method handles conversion to the following types:
288     * <ul>
289     *     <li><code>java.lang.Byte</code></li>
290     *     <li><code>java.lang.Short</code></li>
291     *     <li><code>java.lang.Integer</code></li>
292     *     <li><code>java.lang.Long</code></li>
293     *     <li><code>java.lang.Float</code></li>
294     *     <li><code>java.lang.Double</code></li>
295     *     <li><code>java.math.BigDecimal</code></li>
296     *     <li><code>java.math.BigInteger</code></li>
297     * </ul>
298     * @param sourceType The type being converted from
299     * @param targetType The Number type to convert to
300     * @param value The Number to convert.
301     *
302     * @return The converted value.
303     */
304    private <T> T toNumber(final Class<?> sourceType, final Class<T> targetType, final Number value) {
305
306        // Correct Number type already
307        if (targetType.equals(value.getClass())) {
308            return targetType.cast(value);
309        }
310
311        // Byte
312        if (targetType.equals(Byte.class)) {
313            final long longValue = value.longValue();
314            if (longValue > Byte.MAX_VALUE) {
315                throw new ConversionException(toString(sourceType) + " value '" + value
316                        + "' is too large for " + toString(targetType));
317            }
318            if (longValue < Byte.MIN_VALUE) {
319                throw new ConversionException(toString(sourceType) + " value '" + value
320                        + "' is too small " + toString(targetType));
321            }
322            return targetType.cast(new Byte(value.byteValue()));
323        }
324
325        // Short
326        if (targetType.equals(Short.class)) {
327            final long longValue = value.longValue();
328            if (longValue > Short.MAX_VALUE) {
329                throw new ConversionException(toString(sourceType) + " value '" + value
330                        + "' is too large for " + toString(targetType));
331            }
332            if (longValue < Short.MIN_VALUE) {
333                throw new ConversionException(toString(sourceType) + " value '" + value
334                        + "' is too small " + toString(targetType));
335            }
336            return targetType.cast(new Short(value.shortValue()));
337        }
338
339        // Integer
340        if (targetType.equals(Integer.class)) {
341            final long longValue = value.longValue();
342            if (longValue > Integer.MAX_VALUE) {
343                throw new ConversionException(toString(sourceType) + " value '" + value
344                        + "' is too large for " + toString(targetType));
345            }
346            if (longValue < Integer.MIN_VALUE) {
347                throw new ConversionException(toString(sourceType) + " value '" + value
348                        + "' is too small " + toString(targetType));
349            }
350            return targetType.cast(new Integer(value.intValue()));
351        }
352
353        // Long
354        if (targetType.equals(Long.class)) {
355            return targetType.cast(new Long(value.longValue()));
356        }
357
358        // Float
359        if (targetType.equals(Float.class)) {
360            if (value.doubleValue() > Float.MAX_VALUE) {
361                throw new ConversionException(toString(sourceType) + " value '" + value
362                        + "' is too large for " + toString(targetType));
363            }
364            return targetType.cast(new Float(value.floatValue()));
365        }
366
367        // Double
368        if (targetType.equals(Double.class)) {
369            return targetType.cast(new Double(value.doubleValue()));
370        }
371
372        // BigDecimal
373        if (targetType.equals(BigDecimal.class)) {
374            if (value instanceof Float || value instanceof Double) {
375                return targetType.cast(new BigDecimal(value.toString()));
376            } else if (value instanceof BigInteger) {
377                return targetType.cast(new BigDecimal((BigInteger)value));
378            } else if (value instanceof BigDecimal) {
379                return targetType.cast(new BigDecimal(value.toString()));
380            } else {
381                return targetType.cast(BigDecimal.valueOf(value.longValue()));
382            }
383        }
384
385        // BigInteger
386        if (targetType.equals(BigInteger.class)) {
387            if (value instanceof BigDecimal) {
388                return targetType.cast(((BigDecimal)value).toBigInteger());
389            } else {
390                return targetType.cast(BigInteger.valueOf(value.longValue()));
391            }
392        }
393
394        final String msg = toString(getClass()) + " cannot handle conversion to '"
395                   + toString(targetType) + "'";
396        if (log().isWarnEnabled()) {
397            log().warn("    " + msg);
398        }
399        throw new ConversionException(msg);
400
401    }
402
403    /**
404     * Default String to Number conversion.
405     * <p>
406     * This method handles conversion from a String to the following types:
407     * <ul>
408     *     <li><code>java.lang.Byte</code></li>
409     *     <li><code>java.lang.Short</code></li>
410     *     <li><code>java.lang.Integer</code></li>
411     *     <li><code>java.lang.Long</code></li>
412     *     <li><code>java.lang.Float</code></li>
413     *     <li><code>java.lang.Double</code></li>
414     *     <li><code>java.math.BigDecimal</code></li>
415     *     <li><code>java.math.BigInteger</code></li>
416     * </ul>
417     * @param sourceType The type being converted from
418     * @param targetType The Number type to convert to
419     * @param value The String value to convert.
420     *
421     * @return The converted Number value.
422     */
423    private Number toNumber(final Class<?> sourceType, final Class<?> targetType, final String value) {
424
425        // Byte
426        if (targetType.equals(Byte.class)) {
427            return new Byte(value);
428        }
429
430        // Short
431        if (targetType.equals(Short.class)) {
432            return new Short(value);
433        }
434
435        // Integer
436        if (targetType.equals(Integer.class)) {
437            return new Integer(value);
438        }
439
440        // Long
441        if (targetType.equals(Long.class)) {
442            return new Long(value);
443        }
444
445        // Float
446        if (targetType.equals(Float.class)) {
447            return new Float(value);
448        }
449
450        // Double
451        if (targetType.equals(Double.class)) {
452            return new Double(value);
453        }
454
455        // BigDecimal
456        if (targetType.equals(BigDecimal.class)) {
457            return new BigDecimal(value);
458        }
459
460        // BigInteger
461        if (targetType.equals(BigInteger.class)) {
462            return new BigInteger(value);
463        }
464
465        final String msg = toString(getClass()) + " cannot handle conversion from '" +
466                     toString(sourceType) + "' to '" + toString(targetType) + "'";
467        if (log().isWarnEnabled()) {
468            log().warn("    " + msg);
469        }
470        throw new ConversionException(msg);
471    }
472
473    /**
474     * Provide a String representation of this number converter.
475     *
476     * @return A String representation of this number converter
477     */
478    @Override
479    public String toString() {
480        final StringBuilder buffer = new StringBuilder();
481        buffer.append(toString(getClass()));
482        buffer.append("[UseDefault=");
483        buffer.append(isUseDefault());
484        buffer.append(", UseLocaleFormat=");
485        buffer.append(useLocaleFormat);
486        if (pattern != null) {
487            buffer.append(", Pattern=");
488            buffer.append(pattern);
489        }
490        if (locale != null) {
491            buffer.append(", Locale=");
492            buffer.append(locale);
493        }
494        buffer.append(']');
495        return buffer.toString();
496    }
497
498    /**
499     * Return a NumberFormat to use for Conversion.
500     *
501     * @return The NumberFormat.
502     */
503    private NumberFormat getFormat() {
504        NumberFormat format = null;
505        if (pattern != null) {
506            if (locale == null) {
507                if (log().isDebugEnabled()) {
508                    log().debug("    Using pattern '" + pattern + "'");
509                }
510                format = new DecimalFormat(pattern);
511            } else {
512                if (log().isDebugEnabled()) {
513                    log().debug("    Using pattern '" + pattern + "'" +
514                              " with Locale[" + locale + "]");
515                }
516                final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
517                format = new DecimalFormat(pattern, symbols);
518            }
519        } else {
520            if (locale == null) {
521                if (log().isDebugEnabled()) {
522                    log().debug("    Using default Locale format");
523                }
524                format = NumberFormat.getInstance();
525            } else {
526                if (log().isDebugEnabled()) {
527                    log().debug("    Using Locale[" + locale + "] format");
528                }
529                format = NumberFormat.getInstance(locale);
530            }
531        }
532        if (!allowDecimals) {
533            format.setParseIntegerOnly(true);
534        }
535        return format;
536    }
537
538    /**
539     * Convert a String into a <code>Number</code> object.
540     * @param sourceType the source type of the conversion
541     * @param targetType The type to convert the value to
542     * @param value The String date value.
543     * @param format The NumberFormat to parse the String value.
544     *
545     * @return The converted Number object.
546     * @throws ConversionException if the String cannot be converted.
547     */
548    private Number parse(final Class<?> sourceType, final Class<?> targetType, final String value, final NumberFormat format) {
549        final ParsePosition pos = new ParsePosition(0);
550        final Number parsedNumber = format.parse(value, pos);
551        if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) {
552            String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
553            if (format instanceof DecimalFormat) {
554                msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'";
555            }
556            if (locale != null) {
557                msg += " for locale=[" + locale + "]";
558            }
559            if (log().isDebugEnabled()) {
560                log().debug("    " + msg);
561            }
562            throw new ConversionException(msg);
563        }
564        return parsedNumber;
565    }
566
567}