/**
* $Id: NumberConverter.java 2 2008-10-01 10:04:26Z azeckoski $
* $URL: http://reflectutils.googlecode.com/svn/trunk/src/main/java/org/azeckoski/reflectutils/converters/NumberConverter.java $
* NumberConverter.java - genericdao - Sep 8, 2008 10:48:42 AM - azeckoski
**************************************************************************
* Copyright (c) 2008 Aaron Zeckoski
* Licensed under the Apache License, Version 2.0
*
* A copy of the Apache License has been included in this
* distribution and is available at: http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Aaron Zeckoski (azeckoski @ gmail.com) (aaronz @ vt.edu) (aaron @ caret.cam.ac.uk)
*/
package org.azeckoski.reflectutils.converters;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;
import java.util.Date;
import org.azeckoski.reflectutils.ConstructorUtils;
import org.azeckoski.reflectutils.converters.api.Converter;
/**
* Handles all the number conversions in a single converter,
* the other number converters are simply going to call through to this one<br/>
* Special handling for {@link Boolean}, {@link Date}, {@link Calendar}, and {@link String}
* <ul>
* <li><code>java.math.BigDecimal</code></li>
* <li><code>java.math.BigInteger</code></li>
* <li><code>java.lang.Byte</code></li>
* <li><code>java.lang.Double</code></li>
* <li><code>java.lang.Float</code></li>
* <li><code>java.lang.Integer</code></li>
* <li><code>java.lang.Long</code></li>
* <li><code>java.lang.Number</code></li>
* <li><code>java.lang.Short</code></li>
* </ul>
* <br/>
* Based on code from commons bean utils but updated for generics and removed complexity and (mis)usage of Throwable<br/>
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public class NumberConverter implements Converter<Number> {
public Number convert(Object value) {
return NumberConverter.convertToType(Number.class, value);
}
// Code derived from commons beanutils converters
/**
* Convert the input object into a Number object of the
* specified type.
*
* @param targetType Data type to which this value should be converted.
* @param value The input value to be converted.
* @return The converted value.
* @throws Throwable if an error occurs converting to the specified type
*/
@SuppressWarnings("unchecked")
public static <T> T convertToType(Class<T> targetType, Object value) {
if (value == null) {
throw new IllegalArgumentException("value to convert cannot be null");
}
Class<?> sourceType = value.getClass();
// Handle Number
if (value instanceof Number) {
return (T) toNumber(sourceType, targetType, (Number)value);
}
// Handle Boolean
if (value instanceof Boolean) {
return (T) toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? 1 : 0);
}
// Handle Date
if (value instanceof Date) {
long time = ((Date)value).getTime();
return (T) toNumber(Long.class, targetType, time);
}
// Handle Calendar
if (value instanceof Calendar) {
long time = ((Calendar)value).getTime().getTime();
return (T) toNumber(Long.class, targetType, time);
}
// Convert all other types to String & handle
String stringValue = value.toString().trim();
if (stringValue.length() == 0) {
Object defaultValue = ConstructorUtils.getDefaultValue(targetType);
if (defaultValue == null) {
throw new UnsupportedOperationException("Number convert failure: Could not convert empty string to target ("+targetType+") or get a default value for it");
}
return (T) defaultValue;
}
// Convert/Parse a String
Number number = toNumber(sourceType, targetType, stringValue);
return (T) number;
}
/**
* Convert any Number object to the specified type for this <i>Converter</i>.
* <p>
* This method handles conversion to the following types:
* <ul>
* <li><code>java.math.BigDecimal</code></li>
* <li><code>java.math.BigInteger</code></li>
* <li><code>java.lang.Byte</code></li>
* <li><code>java.lang.Double</code></li>
* <li><code>java.lang.Float</code></li>
* <li><code>java.lang.Integer</code></li>
* <li><code>java.lang.Long</code></li>
* <li><code>java.lang.Number</code></li>
* <li><code>java.lang.Short</code></li>
* </ul>
* @param sourceType The type being converted from
* @param targetType The Number type to convert to
* @param value The Number to convert.
* @return The converted value.
*/
private static Number toNumber(Class<?> sourceType, Class<?> targetType, Number value) {
// Correct Number type already
if ( ConstructorUtils.classEquals(targetType, value.getClass()) ) {
return value;
}
// Byte
if (targetType.equals(Byte.class)) {
long longValue = value.longValue();
if (longValue > Byte.MAX_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too large for target (" + targetType + ")");
}
if (longValue < Byte.MIN_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too small for target (" + targetType + ")");
}
return new Byte(value.byteValue());
}
// Short
if (targetType.equals(Short.class)) {
long longValue = value.longValue();
if (longValue > Short.MAX_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too large for target (" + targetType + ")");
}
if (longValue < Short.MIN_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too small for target (" + targetType + ")");
}
return new Short(value.shortValue());
}
// Integer
if (targetType.equals(Integer.class)) {
long longValue = value.longValue();
if (longValue > Integer.MAX_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too large for target (" + targetType + ")");
}
if (longValue < Integer.MIN_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too small for target (" + targetType + ")");
}
return new Integer(value.intValue());
}
// Long
if (targetType.equals(Long.class)) {
return new Long(value.longValue());
}
// Float
if (targetType.equals(Float.class)) {
if (value.doubleValue() > Float.MAX_VALUE) {
throw new UnsupportedOperationException("source (" + sourceType + ") value ("
+ value + ") is too large for target (" + targetType + ")");
}
return new Float(value.floatValue());
}
// Double
if (targetType.equals(Double.class)) {
return new Double(value.doubleValue());
}
// BigDecimal
if (targetType.equals(BigDecimal.class)) {
if (value instanceof Float || value instanceof Double) {
return new BigDecimal(value.toString());
} else if (value instanceof BigInteger) {
return new BigDecimal((BigInteger)value);
} else {
return BigDecimal.valueOf(value.longValue());
}
}
// BigInteger
if (targetType.equals(BigInteger.class)) {
if (value instanceof BigDecimal) {
return ((BigDecimal)value).toBigInteger();
} else {
return BigInteger.valueOf(value.longValue());
}
}
throw new UnsupportedOperationException("Number convert failure: cannot convert source ("+sourceType+") to target ("+targetType+")");
}
/**
* Default String to Number conversion.
* <p>
* This method handles conversion from a String to the following types:
* <ul>
* <li><code>java.lang.Byte</code></li>
* <li><code>java.lang.Short</code></li>
* <li><code>java.lang.Integer</code></li>
* <li><code>java.lang.Long</code></li>
* <li><code>java.lang.Float</code></li>
* <li><code>java.lang.Double</code></li>
* <li><code>java.math.BigDecimal</code></li>
* <li><code>java.math.BigInteger</code></li>
* </ul>
* @param sourceType The type being converted from
* @param targetType The Number type to convert to
* @param value The String value to convert.
*
* @return The converted Number value.
*/
private static Number toNumber(Class<?> sourceType, Class<?> targetType, String value) {
// Byte
if (targetType.equals(Byte.class)) {
return Byte.valueOf(value);
}
// Short
if (targetType.equals(Short.class)) {
return Short.valueOf(value);
}
// Integer
if (targetType.equals(Integer.class)) {
return Integer.valueOf(value);
}
// Long
if (targetType.equals(Long.class)) {
return Long.valueOf(value);
}
// Float
if (targetType.equals(Float.class)) {
return Float.valueOf(value);
}
// Double
if (targetType.equals(Double.class)) {
return Double.valueOf(value);
}
// BigDecimal
if (targetType.equals(BigDecimal.class)) {
return new BigDecimal(value);
}
// BigInteger
if (targetType.equals(BigInteger.class)) {
return new BigInteger(value);
}
throw new UnsupportedOperationException("Number convert failure: cannot convert string source ("+sourceType+") to target ("+targetType+")");
}
}