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 */
017 package org.apache.commons.beanutils.converters;
018
019 import java.util.Calendar;
020 import java.util.Date;
021 import java.util.Locale;
022 import java.math.BigDecimal;
023 import java.math.BigInteger;
024 import java.text.NumberFormat;
025 import java.text.DecimalFormat;
026 import java.text.DecimalFormatSymbols;
027 import java.text.ParsePosition;
028
029 import 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 used the <i>standard</i>
078 * pattern characters and NOT in <i>localized</i> form (see <code>java.text.SimpleDateFormat</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 $Revision: 745081 $ $Date: 2009-02-17 14:05:20 +0000 (Tue, 17 Feb 2009) $
083 * @since 1.8.0
084 */
085 public 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 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.SimpleDateFormat</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.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 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 protected String convertToString(Object value) throws Throwable {
201
202 String result = null;
203 if (useLocaleFormat && value instanceof Number) {
204 NumberFormat format = getFormat();
205 format.setGroupingUsed(false);
206 result = format.format(value);
207 if (log().isDebugEnabled()) {
208 log().debug(" Converted to String using format '" + result + "'");
209 }
210
211 } else {
212 result = value.toString();
213 if (log().isDebugEnabled()) {
214 log().debug(" Converted to String using toString() '" + result + "'");
215 }
216 }
217 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 Class sourceType = value.getClass();
233 // Handle Number
234 if (value instanceof Number) {
235 return toNumber(sourceType, targetType, (Number)value);
236 }
237
238 // Handle Boolean
239 if (value instanceof Boolean) {
240 return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
241 }
242
243 // Handle Date --> Long
244 if (value instanceof Date && Long.class.equals(targetType)) {
245 return new Long(((Date)value).getTime());
246 }
247
248 // Handle Calendar --> Long
249 if (value instanceof Calendar && Long.class.equals(targetType)) {
250 return new Long(((Calendar)value).getTime().getTime());
251 }
252
253 // Convert all other types to String & handle
254 String stringValue = value.toString().trim();
255 if (stringValue.length() == 0) {
256 return handleMissing(targetType);
257 }
258
259 // Convert/Parse a String
260 Number number = null;
261 if (useLocaleFormat) {
262 NumberFormat format = getFormat();
263 number = parse(sourceType, targetType, stringValue, format);
264 } else {
265 if (log().isDebugEnabled()) {
266 log().debug(" No NumberFormat, using default conversion");
267 }
268 number = toNumber(sourceType, targetType, stringValue);
269 }
270
271 // Ensure the correct number type is returned
272 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 if (targetType.equals(value.getClass())) {
301 return value;
302 }
303
304 // Byte
305 if (targetType.equals(Byte.class)) {
306 long longValue = value.longValue();
307 if (longValue > Byte.MAX_VALUE) {
308 throw new ConversionException(toString(sourceType) + " value '" + value
309 + "' is too large for " + toString(targetType));
310 }
311 if (longValue < Byte.MIN_VALUE) {
312 throw new ConversionException(toString(sourceType) + " value '" + value
313 + "' is too small " + toString(targetType));
314 }
315 return new Byte(value.byteValue());
316 }
317
318 // Short
319 if (targetType.equals(Short.class)) {
320 long longValue = value.longValue();
321 if (longValue > Short.MAX_VALUE) {
322 throw new ConversionException(toString(sourceType) + " value '" + value
323 + "' is too large for " + toString(targetType));
324 }
325 if (longValue < Short.MIN_VALUE) {
326 throw new ConversionException(toString(sourceType) + " value '" + value
327 + "' is too small " + toString(targetType));
328 }
329 return new Short(value.shortValue());
330 }
331
332 // Integer
333 if (targetType.equals(Integer.class)) {
334 long longValue = value.longValue();
335 if (longValue > Integer.MAX_VALUE) {
336 throw new ConversionException(toString(sourceType) + " value '" + value
337 + "' is too large for " + toString(targetType));
338 }
339 if (longValue < Integer.MIN_VALUE) {
340 throw new ConversionException(toString(sourceType) + " value '" + value
341 + "' is too small " + toString(targetType));
342 }
343 return new Integer(value.intValue());
344 }
345
346 // Long
347 if (targetType.equals(Long.class)) {
348 return new Long(value.longValue());
349 }
350
351 // Float
352 if (targetType.equals(Float.class)) {
353 if (value.doubleValue() > Float.MAX_VALUE) {
354 throw new ConversionException(toString(sourceType) + " value '" + value
355 + "' is too large for " + toString(targetType));
356 }
357 return new Float(value.floatValue());
358 }
359
360 // Double
361 if (targetType.equals(Double.class)) {
362 return new Double(value.doubleValue());
363 }
364
365 // BigDecimal
366 if (targetType.equals(BigDecimal.class)) {
367 if (value instanceof Float || value instanceof Double) {
368 return new BigDecimal(value.toString());
369 } else if (value instanceof BigInteger) {
370 return new BigDecimal((BigInteger)value);
371 } else {
372 return BigDecimal.valueOf(value.longValue());
373 }
374 }
375
376 // BigInteger
377 if (targetType.equals(BigInteger.class)) {
378 if (value instanceof BigDecimal) {
379 return ((BigDecimal)value).toBigInteger();
380 } else {
381 return BigInteger.valueOf(value.longValue());
382 }
383 }
384
385 String msg = toString(getClass()) + " cannot handle conversion to '"
386 + toString(targetType) + "'";
387 if (log().isWarnEnabled()) {
388 log().warn(" " + msg);
389 }
390 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 if (targetType.equals(Byte.class)) {
418 return new Byte(value);
419 }
420
421 // Short
422 if (targetType.equals(Short.class)) {
423 return new Short(value);
424 }
425
426 // Integer
427 if (targetType.equals(Integer.class)) {
428 return new Integer(value);
429 }
430
431 // Long
432 if (targetType.equals(Long.class)) {
433 return new Long(value);
434 }
435
436 // Float
437 if (targetType.equals(Float.class)) {
438 return new Float(value);
439 }
440
441 // Double
442 if (targetType.equals(Double.class)) {
443 return new Double(value);
444 }
445
446 // BigDecimal
447 if (targetType.equals(BigDecimal.class)) {
448 return new BigDecimal(value);
449 }
450
451 // BigInteger
452 if (targetType.equals(BigInteger.class)) {
453 return new BigInteger(value);
454 }
455
456 String msg = toString(getClass()) + " cannot handle conversion from '" +
457 toString(sourceType) + "' to '" + toString(targetType) + "'";
458 if (log().isWarnEnabled()) {
459 log().warn(" " + msg);
460 }
461 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 StringBuffer buffer = new StringBuffer();
471 buffer.append(toString(getClass()));
472 buffer.append("[UseDefault=");
473 buffer.append(isUseDefault());
474 buffer.append(", UseLocaleFormat=");
475 buffer.append(useLocaleFormat);
476 if (pattern != null) {
477 buffer.append(", Pattern=");
478 buffer.append(pattern);
479 }
480 if (locale != null) {
481 buffer.append(", Locale=");
482 buffer.append(locale);
483 }
484 buffer.append(']');
485 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 NumberFormat format = null;
495 if (pattern != null) {
496 if (locale == null) {
497 if (log().isDebugEnabled()) {
498 log().debug(" Using pattern '" + pattern + "'");
499 }
500 format = new DecimalFormat(pattern);
501 } else {
502 if (log().isDebugEnabled()) {
503 log().debug(" Using pattern '" + pattern + "'" +
504 " with Locale[" + locale + "]");
505 }
506 DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
507 format = new DecimalFormat(pattern, symbols);
508 }
509 } else {
510 if (locale == null) {
511 if (log().isDebugEnabled()) {
512 log().debug(" Using default Locale format");
513 }
514 format = NumberFormat.getInstance();
515 } else {
516 if (log().isDebugEnabled()) {
517 log().debug(" Using Locale[" + locale + "] format");
518 }
519 format = NumberFormat.getInstance(locale);
520 }
521 }
522 if (!allowDecimals) {
523 format.setParseIntegerOnly(true);
524 }
525 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 ParsePosition pos = new ParsePosition(0);
540 Number parsedNumber = format.parse(value, pos);
541 if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) {
542 String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
543 if (format instanceof DecimalFormat) {
544 msg += " using pattern '" + ((DecimalFormat)format).toPattern() + "'";
545 }
546 if (locale != null) {
547 msg += " for locale=[" + locale + "]";
548 }
549 if (log().isDebugEnabled()) {
550 log().debug(" " + msg);
551 }
552 throw new ConversionException(msg);
553 }
554 return parsedNumber;
555 }
556
557 }