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: 555845 $ $Date: 2007-07-13 03:52:05 +0100 (Fri, 13 Jul 2007) $
083: * @since 1.8.0
084: */
085: public 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 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,
120: Object defaultValue) {
121: super (defaultType);
122: this .allowDecimals = allowDecimals;
123: setDefaultValue(defaultValue);
124: }
125:
126: // --------------------------------------------------------- Public Methods
127:
128: /**
129: * Return whether decimals are allowed in the number.
130: *
131: * @return Whether decimals are allowed in the number
132: */
133: public boolean isAllowDecimals() {
134: return allowDecimals;
135: }
136:
137: /**
138: * Set whether a format should be used to convert
139: * the Number.
140: *
141: * @param useLocaleFormat <code>true</code> if a number format
142: * should be used.
143: */
144: public void setUseLocaleFormat(boolean useLocaleFormat) {
145: this .useLocaleFormat = useLocaleFormat;
146: }
147:
148: /**
149: * Return the number format pattern used to convert
150: * Numbers to/from a <code>java.lang.String</code>
151: * (or <code>null</code> if none specified).
152: * <p>
153: * See <code>java.text.SimpleDateFormat</code> for details
154: * of how to specify the pattern.
155: *
156: * @return The format pattern.
157: */
158: public String getPattern() {
159: return pattern;
160: }
161:
162: /**
163: * Set a number format pattern to use to convert
164: * Numbers to/from a <code>java.lang.String</code>.
165: * <p>
166: * See <code>java.text.SimpleDateFormat</code> for details
167: * of how to specify the pattern.
168: *
169: * @param pattern The format pattern.
170: */
171: public void setPattern(String pattern) {
172: this .pattern = pattern;
173: setUseLocaleFormat(true);
174: }
175:
176: /**
177: * Return the Locale for the <i>Converter</i>
178: * (or <code>null</code> if none specified).
179: *
180: * @return The locale to use for conversion
181: */
182: public Locale getLocale() {
183: return locale;
184: }
185:
186: /**
187: * Set the Locale for the <i>Converter</i>.
188: *
189: * @param locale The locale to use for conversion
190: */
191: public void setLocale(Locale locale) {
192: this .locale = locale;
193: setUseLocaleFormat(true);
194: }
195:
196: // ------------------------------------------------------ Protected Methods
197:
198: /**
199: * Convert an input Number object into a String.
200: *
201: * @param value The input value to be converted
202: * @return the converted String value.
203: * @throws Throwable if an error occurs converting to a String
204: */
205: protected String convertToString(Object value) throws Throwable {
206:
207: String result = null;
208: if (useLocaleFormat && value instanceof Number) {
209: NumberFormat format = getFormat();
210: format.setGroupingUsed(false);
211: result = format.format(value);
212: if (log().isDebugEnabled()) {
213: log().debug(
214: " Converted to String using format '"
215: + result + "'");
216: }
217:
218: } else {
219: result = value.toString();
220: if (log().isDebugEnabled()) {
221: log().debug(
222: " Converted to String using toString() '"
223: + result + "'");
224: }
225: }
226: return result;
227:
228: }
229:
230: /**
231: * Convert the input object into a Number object of the
232: * specified type.
233: *
234: * @param targetType Data type to which this value should be converted.
235: * @param value The input value to be converted.
236: * @return The converted value.
237: * @throws Throwable if an error occurs converting to the specified type
238: */
239: protected Object convertToType(Class targetType, Object value)
240: throws Throwable {
241:
242: Class sourceType = value.getClass();
243: // Handle Number
244: if (value instanceof Number) {
245: return toNumber(sourceType, targetType, (Number) value);
246: }
247:
248: // Handle Boolean
249: if (value instanceof Boolean) {
250: return toNumber(sourceType, targetType, ((Boolean) value)
251: .booleanValue() ? ONE : ZERO);
252: }
253:
254: // Handle Date --> Long
255: if (value instanceof Date && Long.class.equals(targetType)) {
256: return new Long(((Date) value).getTime());
257: }
258:
259: // Handle Calendar --> Long
260: if (value instanceof Calendar && Long.class.equals(targetType)) {
261: return new Long(((Calendar) value).getTime().getTime());
262: }
263:
264: // Convert all other types to String & handle
265: String stringValue = value.toString().trim();
266: if (stringValue.length() == 0) {
267: return handleMissing(targetType);
268: }
269:
270: // Convert/Parse a String
271: Number number = null;
272: if (useLocaleFormat) {
273: NumberFormat format = getFormat();
274: number = parse(sourceType, targetType, stringValue, format);
275: } else {
276: if (log().isDebugEnabled()) {
277: log()
278: .debug(
279: " No NumberFormat, using default conversion");
280: }
281: number = toNumber(sourceType, targetType, stringValue);
282: }
283:
284: // Ensure the correct number type is returned
285: return toNumber(sourceType, targetType, number);
286:
287: }
288:
289: /**
290: * Convert any Number object to the specified type for this
291: * <i>Converter</i>.
292: * <p>
293: * This method handles conversion to the following types:
294: * <ul>
295: * <li><code>java.lang.Byte</code></li>
296: * <li><code>java.lang.Short</code></li>
297: * <li><code>java.lang.Integer</code></li>
298: * <li><code>java.lang.Long</code></li>
299: * <li><code>java.lang.Float</code></li>
300: * <li><code>java.lang.Double</code></li>
301: * <li><code>java.math.BigDecimal</code></li>
302: * <li><code>java.math.BigInteger</code></li>
303: * </ul>
304: * @param sourceType The type being converted from
305: * @param targetType The Number type to convert to
306: * @param value The Number to convert.
307: *
308: * @return The converted value.
309: */
310: private Number toNumber(Class sourceType, Class targetType,
311: Number value) {
312:
313: // Correct Number type already
314: if (targetType.equals(value.getClass())) {
315: return value;
316: }
317:
318: // Byte
319: if (targetType.equals(Byte.class)) {
320: long longValue = value.longValue();
321: if (longValue > Byte.MAX_VALUE) {
322: throw new ConversionException(toString(sourceType)
323: + " value '" + value + "' is too large for "
324: + toString(targetType));
325: }
326: if (longValue < Byte.MIN_VALUE) {
327: throw new ConversionException(toString(sourceType)
328: + " value '" + value + "' is too small "
329: + toString(targetType));
330: }
331: return new Byte(value.byteValue());
332: }
333:
334: // Short
335: if (targetType.equals(Short.class)) {
336: long longValue = value.longValue();
337: if (longValue > Short.MAX_VALUE) {
338: throw new ConversionException(toString(sourceType)
339: + " value '" + value + "' is too large for "
340: + toString(targetType));
341: }
342: if (longValue < Short.MIN_VALUE) {
343: throw new ConversionException(toString(sourceType)
344: + " value '" + value + "' is too small "
345: + toString(targetType));
346: }
347: return new Short(value.shortValue());
348: }
349:
350: // Integer
351: if (targetType.equals(Integer.class)) {
352: long longValue = value.longValue();
353: if (longValue > Integer.MAX_VALUE) {
354: throw new ConversionException(toString(sourceType)
355: + " value '" + value + "' is too large for "
356: + toString(targetType));
357: }
358: if (longValue < Integer.MIN_VALUE) {
359: throw new ConversionException(toString(sourceType)
360: + " value '" + value + "' is too small "
361: + toString(targetType));
362: }
363: return new Integer(value.intValue());
364: }
365:
366: // Long
367: if (targetType.equals(Long.class)) {
368: return new Long(value.longValue());
369: }
370:
371: // Float
372: if (targetType.equals(Float.class)) {
373: if (value.doubleValue() > Float.MAX_VALUE) {
374: throw new ConversionException(toString(sourceType)
375: + " value '" + value + "' is too large for "
376: + toString(targetType));
377: }
378: return new Float(value.floatValue());
379: }
380:
381: // Double
382: if (targetType.equals(Double.class)) {
383: return new Double(value.doubleValue());
384: }
385:
386: // BigDecimal
387: if (targetType.equals(BigDecimal.class)) {
388: if (value instanceof Float || value instanceof Double) {
389: return new BigDecimal(value.toString());
390: } else if (value instanceof BigInteger) {
391: return new BigDecimal((BigInteger) value);
392: } else {
393: return BigDecimal.valueOf(value.longValue());
394: }
395: }
396:
397: // BigInteger
398: if (targetType.equals(BigInteger.class)) {
399: if (value instanceof BigDecimal) {
400: return ((BigDecimal) value).toBigInteger();
401: } else {
402: return BigInteger.valueOf(value.longValue());
403: }
404: }
405:
406: String msg = toString(getClass())
407: + " cannot handle conversion to '"
408: + toString(targetType) + "'";
409: if (log().isWarnEnabled()) {
410: log().warn(" " + msg);
411: }
412: throw new ConversionException(msg);
413:
414: }
415:
416: /**
417: * Default String to Number conversion.
418: * <p>
419: * This method handles conversion from a String to the following types:
420: * <ul>
421: * <li><code>java.lang.Byte</code></li>
422: * <li><code>java.lang.Short</code></li>
423: * <li><code>java.lang.Integer</code></li>
424: * <li><code>java.lang.Long</code></li>
425: * <li><code>java.lang.Float</code></li>
426: * <li><code>java.lang.Double</code></li>
427: * <li><code>java.math.BigDecimal</code></li>
428: * <li><code>java.math.BigInteger</code></li>
429: * </ul>
430: * @param sourceType The type being converted from
431: * @param targetType The Number type to convert to
432: * @param value The String value to convert.
433: *
434: * @return The converted Number value.
435: */
436: private Number toNumber(Class sourceType, Class targetType,
437: String value) {
438:
439: // Byte
440: if (targetType.equals(Byte.class)) {
441: return new Byte(value);
442: }
443:
444: // Short
445: if (targetType.equals(Short.class)) {
446: return new Short(value);
447: }
448:
449: // Integer
450: if (targetType.equals(Integer.class)) {
451: return new Integer(value);
452: }
453:
454: // Long
455: if (targetType.equals(Long.class)) {
456: return new Long(value);
457: }
458:
459: // Float
460: if (targetType.equals(Float.class)) {
461: return new Float(value);
462: }
463:
464: // Double
465: if (targetType.equals(Double.class)) {
466: return new Double(value);
467: }
468:
469: // BigDecimal
470: if (targetType.equals(BigDecimal.class)) {
471: return new BigDecimal(value);
472: }
473:
474: // BigInteger
475: if (targetType.equals(BigInteger.class)) {
476: return new BigInteger(value);
477: }
478:
479: String msg = toString(getClass())
480: + " cannot handle conversion from '"
481: + toString(sourceType) + "' to '"
482: + toString(targetType) + "'";
483: if (log().isWarnEnabled()) {
484: log().warn(" " + msg);
485: }
486: throw new ConversionException(msg);
487: }
488:
489: /**
490: * Provide a String representation of this number converter.
491: *
492: * @return A String representation of this number converter
493: */
494: public String toString() {
495: StringBuffer buffer = new StringBuffer();
496: buffer.append(toString(getClass()));
497: buffer.append("[UseDefault=");
498: buffer.append(isUseDefault());
499: buffer.append(", UseLocaleFormat=");
500: buffer.append(useLocaleFormat);
501: if (pattern != null) {
502: buffer.append(", Pattern=");
503: buffer.append(pattern);
504: }
505: if (locale != null) {
506: buffer.append(", Locale=");
507: buffer.append(locale);
508: }
509: buffer.append(']');
510: return buffer.toString();
511: }
512:
513: /**
514: * Return a NumberFormat to use for Conversion.
515: *
516: * @return The NumberFormat.
517: */
518: private NumberFormat getFormat() {
519: NumberFormat format = null;
520: if (pattern != null) {
521: if (locale == null) {
522: if (log().isDebugEnabled()) {
523: log().debug(" Using pattern '" + pattern + "'");
524: }
525: format = new DecimalFormat(pattern);
526: } else {
527: if (log().isDebugEnabled()) {
528: log().debug(
529: " Using pattern '" + pattern + "'"
530: + " with Locale[" + locale + "]");
531: }
532: DecimalFormatSymbols symbols = new DecimalFormatSymbols(
533: locale);
534: format = new DecimalFormat(pattern, symbols);
535: }
536: } else {
537: if (locale == null) {
538: if (log().isDebugEnabled()) {
539: log().debug(" Using default Locale format");
540: }
541: format = NumberFormat.getInstance();
542: } else {
543: if (log().isDebugEnabled()) {
544: log().debug(
545: " Using Locale[" + locale + "] format");
546: }
547: format = NumberFormat.getInstance(locale);
548: }
549: }
550: if (!allowDecimals) {
551: format.setParseIntegerOnly(true);
552: }
553: return format;
554: }
555:
556: /**
557: * Convert a String into a <code>Number</code> object.
558: * @param sourceType TODO
559: * @param targetType The type to convert the value to
560: * @param value The String date value.
561: * @param format The NumberFormat to parse the String value.
562: *
563: * @return The converted Number object.
564: * @throws ConversionException if the String cannot be converted.
565: */
566: private Number parse(Class sourceType, Class targetType,
567: String value, NumberFormat format) {
568: ParsePosition pos = new ParsePosition(0);
569: Number parsedNumber = (Number) format.parse(value, pos);
570: if (pos.getErrorIndex() >= 0
571: || pos.getIndex() != value.length()
572: || parsedNumber == null) {
573: String msg = "Error converting from '"
574: + toString(sourceType) + "' to '"
575: + toString(targetType) + "'";
576: if (format instanceof DecimalFormat) {
577: msg += " using pattern '"
578: + ((DecimalFormat) format).toPattern() + "'";
579: }
580: if (locale != null) {
581: msg += " for locale=[" + locale + "]";
582: }
583: if (log().isDebugEnabled()) {
584: log().debug(" " + msg);
585: }
586: throw new ConversionException(msg);
587: }
588: return parsedNumber;
589: }
590:
591: }
|