1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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
238 if (value instanceof Number) {
239 return toNumber(sourceType, targetType, (Number)value);
240 }
241
242
243 if (value instanceof Boolean) {
244 return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
245 }
246
247
248 if (value instanceof Date && Long.class.equals(targetType)) {
249 return new Long(((Date)value).getTime());
250 }
251
252
253 if (value instanceof Calendar && Long.class.equals(targetType)) {
254 return new Long(((Calendar)value).getTime().getTime());
255 }
256
257
258 String stringValue = value.toString().trim();
259 if (stringValue.length() == 0) {
260 return handleMissing(targetType);
261 }
262
263
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
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
304 if (targetType.equals(value.getClass())) {
305 return value;
306 }
307
308
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
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
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
351 if (targetType.equals(Long.class)) {
352 return new Long(value.longValue());
353 }
354
355
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
365 if (targetType.equals(Double.class)) {
366 return new Double(value.doubleValue());
367 }
368
369
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
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
421 if (targetType.equals(Byte.class)) {
422 return new Byte(value);
423 }
424
425
426 if (targetType.equals(Short.class)) {
427 return new Short(value);
428 }
429
430
431 if (targetType.equals(Integer.class)) {
432 return new Integer(value);
433 }
434
435
436 if (targetType.equals(Long.class)) {
437 return new Long(value);
438 }
439
440
441 if (targetType.equals(Float.class)) {
442 return new Float(value);
443 }
444
445
446 if (targetType.equals(Double.class)) {
447 return new Double(value);
448 }
449
450
451 if (targetType.equals(BigDecimal.class)) {
452 return new BigDecimal(value);
453 }
454
455
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 }