Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
NumberConverter |
|
| 7.333333333333333;7.333 |
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: 745081 $ $Date: 2009-02-17 09:05:20 -0500 (Tue, 17 Feb 2009) $ | |
83 | * @since 1.8.0 | |
84 | */ | |
85 | public abstract class NumberConverter extends AbstractConverter { | |
86 | ||
87 | 1 | private static final Integer ZERO = new Integer(0); |
88 | 1 | 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 allowDecimals Indicates whether decimals are allowed | |
102 | */ | |
103 | public NumberConverter(boolean allowDecimals) { | |
104 | 6021 | super(); |
105 | 6021 | this.allowDecimals = allowDecimals; |
106 | 6021 | } |
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 | 5001 | super(); |
117 | 5001 | this.allowDecimals = allowDecimals; |
118 | 5001 | setDefaultValue(defaultValue); |
119 | 5001 | } |
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 | 0 | 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 | 74 | this.useLocaleFormat = useLocaleFormat; |
141 | 74 | } |
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.SimpleDateFormat</code> for details | |
149 | * of how to specify the pattern. | |
150 | * | |
151 | * @return The format pattern. | |
152 | */ | |
153 | public String getPattern() { | |
154 | 0 | 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.SimpleDateFormat</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 | 17 | this.pattern = pattern; |
168 | 17 | setUseLocaleFormat(true); |
169 | 17 | } |
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 | 0 | 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 | 33 | this.locale = locale; |
188 | 33 | setUseLocaleFormat(true); |
189 | 33 | } |
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 | protected String convertToString(Object value) throws Throwable { | |
201 | ||
202 | 123 | String result = null; |
203 | 123 | if (useLocaleFormat && value instanceof Number) { |
204 | 78 | NumberFormat format = getFormat(); |
205 | 78 | format.setGroupingUsed(false); |
206 | 78 | result = format.format(value); |
207 | 78 | if (log().isDebugEnabled()) { |
208 | 0 | log().debug(" Converted to String using format '" + result + "'"); |
209 | } | |
210 | ||
211 | 78 | } else { |
212 | 45 | result = value.toString(); |
213 | 45 | if (log().isDebugEnabled()) { |
214 | 0 | log().debug(" Converted to String using toString() '" + result + "'"); |
215 | } | |
216 | } | |
217 | 123 | return result; |
218 | ||
219 | } | |
220 | ||
221 | /** | |
222 | * Convert the input object into a Number object of the | |
223 | * specified type. | |
224 | * | |
225 | * @param targetType Data type to which this value should be converted. | |
226 | * @param value The input value to be converted. | |
227 | * @return The converted value. | |
228 | * @throws Throwable if an error occurs converting to the specified type | |
229 | */ | |
230 | protected Object convertToType(Class targetType, Object value) throws Throwable { | |
231 | ||
232 | 4461 | Class sourceType = value.getClass(); |
233 | // Handle Number | |
234 | 4461 | if (value instanceof Number) { |
235 | 3939 | return toNumber(sourceType, targetType, (Number)value); |
236 | } | |
237 | ||
238 | // Handle Boolean | |
239 | 522 | if (value instanceof Boolean) { |
240 | 16 | return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO); |
241 | } | |
242 | ||
243 | // Handle Date --> Long | |
244 | 506 | if (value instanceof Date && Long.class.equals(targetType)) { |
245 | 8 | return new Long(((Date)value).getTime()); |
246 | } | |
247 | ||
248 | // Handle Calendar --> Long | |
249 | 498 | if (value instanceof Calendar && Long.class.equals(targetType)) { |
250 | 8 | return new Long(((Calendar)value).getTime().getTime()); |
251 | } | |
252 | ||
253 | // Convert all other types to String & handle | |
254 | 490 | String stringValue = value.toString().trim(); |
255 | 490 | if (stringValue.length() == 0) { |
256 | 2 | return handleMissing(targetType); |
257 | } | |
258 | ||
259 | // Convert/Parse a String | |
260 | 488 | Number number = null; |
261 | 488 | if (useLocaleFormat) { |
262 | 116 | NumberFormat format = getFormat(); |
263 | 116 | number = parse(sourceType, targetType, stringValue, format); |
264 | 84 | } else { |
265 | 372 | if (log().isDebugEnabled()) { |
266 | 0 | log().debug(" No NumberFormat, using default conversion"); |
267 | } | |
268 | 372 | number = toNumber(sourceType, targetType, stringValue); |
269 | } | |
270 | ||
271 | // Ensure the correct number type is returned | |
272 | 389 | return toNumber(sourceType, targetType, number); |
273 | ||
274 | } | |
275 | ||
276 | /** | |
277 | * Convert any Number object to the specified type for this | |
278 | * <i>Converter</i>. | |
279 | * <p> | |
280 | * This method handles conversion to the following types: | |
281 | * <ul> | |
282 | * <li><code>java.lang.Byte</code></li> | |
283 | * <li><code>java.lang.Short</code></li> | |
284 | * <li><code>java.lang.Integer</code></li> | |
285 | * <li><code>java.lang.Long</code></li> | |
286 | * <li><code>java.lang.Float</code></li> | |
287 | * <li><code>java.lang.Double</code></li> | |
288 | * <li><code>java.math.BigDecimal</code></li> | |
289 | * <li><code>java.math.BigInteger</code></li> | |
290 | * </ul> | |
291 | * @param sourceType The type being converted from | |
292 | * @param targetType The Number type to convert to | |
293 | * @param value The Number to convert. | |
294 | * | |
295 | * @return The converted value. | |
296 | */ | |
297 | private Number toNumber(Class sourceType, Class targetType, Number value) { | |
298 | ||
299 | // Correct Number type already | |
300 | 4344 | if (targetType.equals(value.getClass())) { |
301 | 315 | return value; |
302 | } | |
303 | ||
304 | // Byte | |
305 | 4029 | if (targetType.equals(Byte.class)) { |
306 | 769 | long longValue = value.longValue(); |
307 | 769 | if (longValue > Byte.MAX_VALUE) { |
308 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
309 | + "' is too large for " + toString(targetType)); | |
310 | } | |
311 | 768 | if (longValue < Byte.MIN_VALUE) { |
312 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
313 | + "' is too small " + toString(targetType)); | |
314 | } | |
315 | 767 | return new Byte(value.byteValue()); |
316 | } | |
317 | ||
318 | // Short | |
319 | 3260 | if (targetType.equals(Short.class)) { |
320 | 769 | long longValue = value.longValue(); |
321 | 769 | if (longValue > Short.MAX_VALUE) { |
322 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
323 | + "' is too large for " + toString(targetType)); | |
324 | } | |
325 | 768 | if (longValue < Short.MIN_VALUE) { |
326 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
327 | + "' is too small " + toString(targetType)); | |
328 | } | |
329 | 767 | return new Short(value.shortValue()); |
330 | } | |
331 | ||
332 | // Integer | |
333 | 2491 | if (targetType.equals(Integer.class)) { |
334 | 101 | long longValue = value.longValue(); |
335 | 101 | if (longValue > Integer.MAX_VALUE) { |
336 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
337 | + "' is too large for " + toString(targetType)); | |
338 | } | |
339 | 100 | if (longValue < Integer.MIN_VALUE) { |
340 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
341 | + "' is too small " + toString(targetType)); | |
342 | } | |
343 | 99 | return new Integer(value.intValue()); |
344 | } | |
345 | ||
346 | // Long | |
347 | 2390 | if (targetType.equals(Long.class)) { |
348 | 776 | return new Long(value.longValue()); |
349 | } | |
350 | ||
351 | // Float | |
352 | 1614 | if (targetType.equals(Float.class)) { |
353 | 773 | if (value.doubleValue() > Float.MAX_VALUE) { |
354 | 1 | throw new ConversionException(toString(sourceType) + " value '" + value |
355 | + "' is too large for " + toString(targetType)); | |
356 | } | |
357 | 772 | return new Float(value.floatValue()); |
358 | } | |
359 | ||
360 | // Double | |
361 | 841 | if (targetType.equals(Double.class)) { |
362 | 771 | return new Double(value.doubleValue()); |
363 | } | |
364 | ||
365 | // BigDecimal | |
366 | 70 | if (targetType.equals(BigDecimal.class)) { |
367 | 31 | if (value instanceof Float || value instanceof Double) { |
368 | 6 | return new BigDecimal(value.toString()); |
369 | 25 | } else if (value instanceof BigInteger) { |
370 | 1 | return new BigDecimal((BigInteger)value); |
371 | } else { | |
372 | 24 | return BigDecimal.valueOf(value.longValue()); |
373 | } | |
374 | } | |
375 | ||
376 | // BigInteger | |
377 | 39 | if (targetType.equals(BigInteger.class)) { |
378 | 31 | if (value instanceof BigDecimal) { |
379 | 1 | return ((BigDecimal)value).toBigInteger(); |
380 | } else { | |
381 | 30 | return BigInteger.valueOf(value.longValue()); |
382 | } | |
383 | } | |
384 | ||
385 | 8 | String msg = toString(getClass()) + " cannot handle conversion to '" |
386 | + toString(targetType) + "'"; | |
387 | 8 | if (log().isWarnEnabled()) { |
388 | 8 | log().warn(" " + msg); |
389 | } | |
390 | 8 | throw new ConversionException(msg); |
391 | ||
392 | } | |
393 | ||
394 | /** | |
395 | * Default String to Number conversion. | |
396 | * <p> | |
397 | * This method handles conversion from a String to the following types: | |
398 | * <ul> | |
399 | * <li><code>java.lang.Byte</code></li> | |
400 | * <li><code>java.lang.Short</code></li> | |
401 | * <li><code>java.lang.Integer</code></li> | |
402 | * <li><code>java.lang.Long</code></li> | |
403 | * <li><code>java.lang.Float</code></li> | |
404 | * <li><code>java.lang.Double</code></li> | |
405 | * <li><code>java.math.BigDecimal</code></li> | |
406 | * <li><code>java.math.BigInteger</code></li> | |
407 | * </ul> | |
408 | * @param sourceType The type being converted from | |
409 | * @param targetType The Number type to convert to | |
410 | * @param value The String value to convert. | |
411 | * | |
412 | * @return The converted Number value. | |
413 | */ | |
414 | private Number toNumber(Class sourceType, Class targetType, String value) { | |
415 | ||
416 | // Byte | |
417 | 372 | if (targetType.equals(Byte.class)) { |
418 | 35 | return new Byte(value); |
419 | } | |
420 | ||
421 | // Short | |
422 | 337 | if (targetType.equals(Short.class)) { |
423 | 38 | return new Short(value); |
424 | } | |
425 | ||
426 | // Integer | |
427 | 299 | if (targetType.equals(Integer.class)) { |
428 | 164 | return new Integer(value); |
429 | } | |
430 | ||
431 | // Long | |
432 | 135 | if (targetType.equals(Long.class)) { |
433 | 33 | return new Long(value); |
434 | } | |
435 | ||
436 | // Float | |
437 | 102 | if (targetType.equals(Float.class)) { |
438 | 32 | return new Float(value); |
439 | } | |
440 | ||
441 | // Double | |
442 | 70 | if (targetType.equals(Double.class)) { |
443 | 38 | return new Double(value); |
444 | } | |
445 | ||
446 | // BigDecimal | |
447 | 32 | if (targetType.equals(BigDecimal.class)) { |
448 | 14 | return new BigDecimal(value); |
449 | } | |
450 | ||
451 | // BigInteger | |
452 | 18 | if (targetType.equals(BigInteger.class)) { |
453 | 18 | return new BigInteger(value); |
454 | } | |
455 | ||
456 | 0 | String msg = toString(getClass()) + " cannot handle conversion from '" + |
457 | toString(sourceType) + "' to '" + toString(targetType) + "'"; | |
458 | 0 | if (log().isWarnEnabled()) { |
459 | 0 | log().warn(" " + msg); |
460 | } | |
461 | 0 | throw new ConversionException(msg); |
462 | } | |
463 | ||
464 | /** | |
465 | * Provide a String representation of this number converter. | |
466 | * | |
467 | * @return A String representation of this number converter | |
468 | */ | |
469 | public String toString() { | |
470 | 155 | StringBuffer buffer = new StringBuffer(); |
471 | 155 | buffer.append(toString(getClass())); |
472 | 155 | buffer.append("[UseDefault="); |
473 | 155 | buffer.append(isUseDefault()); |
474 | 155 | buffer.append(", UseLocaleFormat="); |
475 | 155 | buffer.append(useLocaleFormat); |
476 | 155 | if (pattern != null) { |
477 | 0 | buffer.append(", Pattern="); |
478 | 0 | buffer.append(pattern); |
479 | } | |
480 | 155 | if (locale != null) { |
481 | 0 | buffer.append(", Locale="); |
482 | 0 | buffer.append(locale); |
483 | } | |
484 | 155 | buffer.append(']'); |
485 | 155 | return buffer.toString(); |
486 | } | |
487 | ||
488 | /** | |
489 | * Return a NumberFormat to use for Conversion. | |
490 | * | |
491 | * @return The NumberFormat. | |
492 | */ | |
493 | private NumberFormat getFormat() { | |
494 | 194 | NumberFormat format = null; |
495 | 194 | if (pattern != null) { |
496 | 114 | if (locale == null) { |
497 | 32 | if (log().isDebugEnabled()) { |
498 | 0 | log().debug(" Using pattern '" + pattern + "'"); |
499 | } | |
500 | 32 | format = new DecimalFormat(pattern); |
501 | } else { | |
502 | 82 | if (log().isDebugEnabled()) { |
503 | 0 | log().debug(" Using pattern '" + pattern + "'" + |
504 | " with Locale[" + locale + "]"); | |
505 | } | |
506 | 82 | DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale); |
507 | 82 | format = new DecimalFormat(pattern, symbols); |
508 | 82 | } |
509 | } else { | |
510 | 80 | if (locale == null) { |
511 | 40 | if (log().isDebugEnabled()) { |
512 | 0 | log().debug(" Using default Locale format"); |
513 | } | |
514 | 40 | format = NumberFormat.getInstance(); |
515 | } else { | |
516 | 40 | if (log().isDebugEnabled()) { |
517 | 0 | log().debug(" Using Locale[" + locale + "] format"); |
518 | } | |
519 | 40 | format = NumberFormat.getInstance(locale); |
520 | } | |
521 | } | |
522 | 194 | if (!allowDecimals) { |
523 | 134 | format.setParseIntegerOnly(true); |
524 | } | |
525 | 194 | return format; |
526 | } | |
527 | ||
528 | /** | |
529 | * Convert a String into a <code>Number</code> object. | |
530 | * @param sourceType TODO | |
531 | * @param targetType The type to convert the value to | |
532 | * @param value The String date value. | |
533 | * @param format The NumberFormat to parse the String value. | |
534 | * | |
535 | * @return The converted Number object. | |
536 | * @throws ConversionException if the String cannot be converted. | |
537 | */ | |
538 | private Number parse(Class sourceType, Class targetType, String value, NumberFormat format) { | |
539 | 116 | ParsePosition pos = new ParsePosition(0); |
540 | 116 | Number parsedNumber = format.parse(value, pos); |
541 | 116 | if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) { |
542 | 32 | String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'"; |
543 | 32 | if (format instanceof DecimalFormat) { |
544 | 32 | msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'"; |
545 | } | |
546 | 32 | if (locale != null) { |
547 | 24 | msg += " for locale=[" + locale + "]"; |
548 | } | |
549 | 32 | if (log().isDebugEnabled()) { |
550 | 0 | log().debug(" " + msg); |
551 | } | |
552 | 32 | throw new ConversionException(msg); |
553 | } | |
554 | 84 | return parsedNumber; |
555 | } | |
556 | ||
557 | } |