View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.beanutils.converters;
18  
19  import java.util.Calendar;
20  import java.util.Date;
21  import java.util.Locale;
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.text.NumberFormat;
25  import java.text.DecimalFormat;
26  import java.text.DecimalFormatSymbols;
27  import java.text.ParsePosition;
28  
29  import org.apache.commons.beanutils.ConversionException;
30  
31  /***
32   * {@link org.apache.commons.beanutils.Converter} implementaion that handles conversion
33   * to and from <b>java.lang.Number</b> objects.
34   * <p>
35   * This implementation handles conversion for the following
36   * <code>java.lang.Number</code> types.
37   * <ul>
38   *     <li><code>java.lang.Byte</code></li>
39   *     <li><code>java.lang.Short</code></li>
40   *     <li><code>java.lang.Integer</code></li>
41   *     <li><code>java.lang.Long</code></li>
42   *     <li><code>java.lang.Float</code></li>
43   *     <li><code>java.lang.Double</code></li>
44   *     <li><code>java.math.BigDecimal</code></li>
45   *     <li><code>java.math.BigInteger</code></li>
46   * </ul>
47   *
48   * <h3>String Conversions (to and from)</h3>
49   * This class provides a number of ways in which number
50   * conversions to/from Strings can be achieved:
51   * <ul>
52   *    <li>Using the default format for the default Locale, configure using:</li>
53   *        <ul>
54   *           <li><code>setUseLocaleFormat(true)</code></li>
55   *        </ul>
56   *    <li>Using the default format for a specified Locale, configure using:</li>
57   *        <ul>
58   *           <li><code>setLocale(Locale)</code></li>
59   *        </ul>
60   *    <li>Using a specified pattern for the default Locale, configure using:</li>
61   *        <ul>
62   *           <li><code>setPattern(String)</code></li>
63   *        </ul>
64   *    <li>Using a specified pattern for a specified Locale, configure using:</li>
65   *        <ul>
66   *           <li><code>setPattern(String)</code></li>
67   *           <li><code>setLocale(Locale)</code></li>
68   *        </ul>
69   *    <li>If none of the above are configured the
70   *        <code>toNumber(String)</code> method is used to convert
71   *        from String to Number and the Number's
72   *        <code>toString()</code> method used to convert from
73   *        Number to String.</li>
74   * </ul>
75   *
76   * <p>
77   * <strong>N.B.</strong>Patterns can only be specified used the <i>standard</i>
78   * pattern characters and NOT in <i>localized</i> form (see <code>java.text.SimpleDateFormat</code>).
79   * For example to cater for number styles used in Germany such as <code>0.000,00</code> the pattern
80   * is specified in the normal form <code>0,000.00</code> and the locale set to <code>Locale.GERMANY</code>.
81   *
82   * @version $Revision: 555845 $ $Date: 2007-07-13 03:52:05 +0100 (Fri, 13 Jul 2007) $
83   * @since 1.8.0
84   */
85  public class NumberConverter extends AbstractConverter {
86  
87      private static final Integer ZERO = new Integer(0);
88      private static final Integer ONE  = new Integer(1);
89  
90      private String pattern;
91      private boolean allowDecimals;
92      private boolean useLocaleFormat;
93      private Locale locale;
94  
95      // ----------------------------------------------------------- Constructors
96  
97      /***
98       * Construct a <b>java.lang.Number</b> <i>Converter</i>
99       * that throws a <code>ConversionException</code> if a error occurs.
100      *
101      * @param defaultType The default type this <code>Converter</code>
102      * handles
103      * @param allowDecimals Indicates whether decimals are allowed
104      */
105     public NumberConverter(Class defaultType, boolean allowDecimals) {
106         super(defaultType);
107         this.allowDecimals = allowDecimals;
108     }
109 
110     /***
111      * Construct a <code>java.lang.Number</code> <i>Converter</i> that returns
112      * a default value if an error occurs.
113      *
114      * @param defaultType The default type this <code>Converter</code>
115      * handles
116      * @param allowDecimals Indicates whether decimals are allowed
117      * @param defaultValue The default value to be returned
118      */
119     public NumberConverter(Class defaultType, boolean allowDecimals, Object defaultValue) {
120         super(defaultType);
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(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.SimpleDateFormat</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.SimpleDateFormat</code> for details
166      * of how to specify the pattern.
167      *
168      * @param pattern The format pattern.
169      */
170     public void setPattern(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(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     protected String convertToString(Object value) throws Throwable {
205 
206         String result = null;
207         if (useLocaleFormat && value instanceof Number) {
208             NumberFormat format = getFormat();
209             format.setGroupingUsed(false);
210             result = format.format(value);
211             if (log().isDebugEnabled()) {
212                 log().debug("    Converted  to String using format '" + result + "'");
213             }
214 
215         } else {
216             result = value.toString();
217             if (log().isDebugEnabled()) {
218                 log().debug("    Converted  to String using toString() '" + result + "'");
219             }
220         }
221         return result;
222 
223     }
224 
225     /***
226      * Convert the input object into a Number object of the
227      * specified type.
228      *
229      * @param targetType Data type to which this value should be converted.
230      * @param value The input value to be converted.
231      * @return The converted value.
232      * @throws Throwable if an error occurs converting to the specified type
233      */
234     protected Object convertToType(Class targetType, Object value) throws Throwable {
235 
236         Class sourceType = value.getClass();
237         // Handle Number
238         if (value instanceof Number) {
239             return toNumber(sourceType, targetType, (Number)value);
240         }
241 
242         // Handle Boolean
243         if (value instanceof Boolean) {
244             return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
245         }
246 
247         // Handle Date --> Long
248         if (value instanceof Date && Long.class.equals(targetType)) {
249             return new Long(((Date)value).getTime());
250         }
251 
252         // Handle Calendar --> Long
253         if (value instanceof Calendar  && Long.class.equals(targetType)) {
254             return new Long(((Calendar)value).getTime().getTime());
255         }
256 
257         // Convert all other types to String & handle
258         String stringValue = value.toString().trim();
259         if (stringValue.length() == 0) {
260             return handleMissing(targetType);
261         }
262 
263         // Convert/Parse a String
264         Number number = null;
265         if (useLocaleFormat) {
266             NumberFormat format = getFormat();
267             number = parse(sourceType, targetType, stringValue, format);
268         } else {
269             if (log().isDebugEnabled()) {
270                 log().debug("    No NumberFormat, using default conversion");
271             }
272             number = toNumber(sourceType, targetType, stringValue);
273         }
274 
275         // Ensure the correct number type is returned
276         return toNumber(sourceType, targetType, number);
277 
278     }
279 
280     /***
281      * Convert any Number object to the specified type for this
282      * <i>Converter</i>.
283      * <p>
284      * This method handles conversion to the following types:
285      * <ul>
286      *     <li><code>java.lang.Byte</code></li>
287      *     <li><code>java.lang.Short</code></li>
288      *     <li><code>java.lang.Integer</code></li>
289      *     <li><code>java.lang.Long</code></li>
290      *     <li><code>java.lang.Float</code></li>
291      *     <li><code>java.lang.Double</code></li>
292      *     <li><code>java.math.BigDecimal</code></li>
293      *     <li><code>java.math.BigInteger</code></li>
294      * </ul>
295      * @param sourceType The type being converted from
296      * @param targetType The Number type to convert to
297      * @param value The Number to convert.
298      *
299      * @return The converted value.
300      */
301     private Number toNumber(Class sourceType, Class targetType, Number value) {
302 
303         // Correct Number type already
304         if (targetType.equals(value.getClass())) {
305             return value;
306         }
307 
308         // Byte
309         if (targetType.equals(Byte.class)) {
310             long longValue = value.longValue();
311             if (longValue > Byte.MAX_VALUE) {
312                 throw new ConversionException(toString(sourceType) + " value '" + value
313                         + "' is too large for " + toString(targetType));
314             }
315             if (longValue < Byte.MIN_VALUE) {
316                 throw new ConversionException(toString(sourceType) + " value '" + value
317                         + "' is too small " + toString(targetType));
318             }
319             return new Byte(value.byteValue());
320         }
321 
322         // Short
323         if (targetType.equals(Short.class)) {
324             long longValue = value.longValue();
325             if (longValue > Short.MAX_VALUE) {
326                 throw new ConversionException(toString(sourceType) + " value '" + value
327                         + "' is too large for " + toString(targetType));
328             }
329             if (longValue < Short.MIN_VALUE) {
330                 throw new ConversionException(toString(sourceType) + " value '" + value
331                         + "' is too small " + toString(targetType));
332             }
333             return new Short(value.shortValue());
334         }
335 
336         // Integer
337         if (targetType.equals(Integer.class)) {
338             long longValue = value.longValue();
339             if (longValue > Integer.MAX_VALUE) {
340                 throw new ConversionException(toString(sourceType) + " value '" + value
341                         + "' is too large for " + toString(targetType));
342             }
343             if (longValue < Integer.MIN_VALUE) {
344                 throw new ConversionException(toString(sourceType) + " value '" + value
345                         + "' is too small " + toString(targetType));
346             }
347             return new Integer(value.intValue());
348         }
349 
350         // Long
351         if (targetType.equals(Long.class)) {
352             return new Long(value.longValue());
353         }
354 
355         // Float
356         if (targetType.equals(Float.class)) {
357             if (value.doubleValue() > Float.MAX_VALUE) {
358                 throw new ConversionException(toString(sourceType) + " value '" + value
359                         + "' is too large for " + toString(targetType));
360             }
361             return new Float(value.floatValue());
362         }
363 
364         // Double
365         if (targetType.equals(Double.class)) {
366             return new Double(value.doubleValue());
367         }
368 
369         // BigDecimal
370         if (targetType.equals(BigDecimal.class)) {
371             if (value instanceof Float || value instanceof Double) {
372                 return new BigDecimal(value.toString());
373             } else if (value instanceof BigInteger) {
374                 return new BigDecimal((BigInteger)value);
375             } else {
376                 return BigDecimal.valueOf(value.longValue());
377             }
378         }
379 
380         // BigInteger
381         if (targetType.equals(BigInteger.class)) {
382             if (value instanceof BigDecimal) {
383                 return ((BigDecimal)value).toBigInteger();
384             } else {
385                 return BigInteger.valueOf(value.longValue());
386             }
387         }
388 
389         String msg = toString(getClass()) + " cannot handle conversion to '"
390                    + toString(targetType) + "'";
391         if (log().isWarnEnabled()) {
392             log().warn("    " + msg);
393         }
394         throw new ConversionException(msg);
395 
396     }
397 
398     /***
399      * Default String to Number conversion.
400      * <p>
401      * This method handles conversion from a String to the following types:
402      * <ul>
403      *     <li><code>java.lang.Byte</code></li>
404      *     <li><code>java.lang.Short</code></li>
405      *     <li><code>java.lang.Integer</code></li>
406      *     <li><code>java.lang.Long</code></li>
407      *     <li><code>java.lang.Float</code></li>
408      *     <li><code>java.lang.Double</code></li>
409      *     <li><code>java.math.BigDecimal</code></li>
410      *     <li><code>java.math.BigInteger</code></li>
411      * </ul>
412      * @param sourceType The type being converted from
413      * @param targetType The Number type to convert to
414      * @param value The String value to convert.
415      *
416      * @return The converted Number value.
417      */
418     private Number toNumber(Class sourceType, Class targetType, String value) {
419 
420         // Byte
421         if (targetType.equals(Byte.class)) {
422             return new Byte(value);
423         }
424 
425         // Short
426         if (targetType.equals(Short.class)) {
427             return new Short(value);
428         }
429 
430         // Integer
431         if (targetType.equals(Integer.class)) {
432             return new Integer(value);
433         }
434 
435         // Long
436         if (targetType.equals(Long.class)) {
437             return new Long(value);
438         }
439 
440         // Float
441         if (targetType.equals(Float.class)) {
442             return new Float(value);
443         }
444 
445         // Double
446         if (targetType.equals(Double.class)) {
447             return new Double(value);
448         }
449 
450         // BigDecimal
451         if (targetType.equals(BigDecimal.class)) {
452             return new BigDecimal(value);
453         }
454 
455         // BigInteger
456         if (targetType.equals(BigInteger.class)) {
457             return new BigInteger(value);
458         }
459 
460         String msg = toString(getClass()) + " cannot handle conversion from '" +
461                      toString(sourceType) + "' to '" + toString(targetType) + "'";
462         if (log().isWarnEnabled()) {
463             log().warn("    " + msg);
464         }
465         throw new ConversionException(msg);
466     }
467 
468     /***
469      * Provide a String representation of this number converter.
470      *
471      * @return A String representation of this number converter
472      */
473     public String toString() {
474         StringBuffer buffer = new StringBuffer();
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 TODO
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 = (Number)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 }