com.feilong.lib.beanutils.converters.NumberConverter Maven / Gradle / Ivy
Show all versions of feilong Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.feilong.lib.beanutils.converters;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.feilong.lib.beanutils.ConversionException;
/**
* {@link com.feilong.lib.beanutils.Converter} implementaion that handles conversion
* to and from java.lang.Number objects.
*
* This implementation handles conversion for the following
* java.lang.Number
types.
*
* java.lang.Byte
* java.lang.Short
* java.lang.Integer
* java.lang.Long
* java.lang.Float
* java.lang.Double
* java.math.BigDecimal
* java.math.BigInteger
*
*
* String Conversions (to and from)
* This class provides a number of ways in which number
* conversions to/from Strings can be achieved:
*
* - Using the default format for the default Locale, configure using:
*
* setUseLocaleFormat(true)
*
* - Using the default format for a specified Locale, configure using:
*
* setLocale(Locale)
*
* - Using a specified pattern for the default Locale, configure using:
*
* setPattern(String)
*
* - Using a specified pattern for a specified Locale, configure using:
*
* setPattern(String)
* setLocale(Locale)
*
* - If none of the above are configured the
*
toNumber(String)
method is used to convert
* from String to Number and the Number's
* toString()
method used to convert from
* Number to String.
*
*
*
* N.B.Patterns can only be specified using the standard
* pattern characters and NOT in localized form (see java.text.DecimalFormat
).
* For example to cater for number styles used in Germany such as 0.000,00
the pattern
* is specified in the normal form 0,000.00
and the locale set to Locale.GERMANY
.
*
* @version $Id$
* @since 1.8.0
*/
public abstract class NumberConverter extends AbstractConverter{
/** The Constant log. */
private static final Logger LOGGER = LoggerFactory.getLogger(NumberConverter.class);
//---------------------------------------------------------------
private static final Integer ZERO = new Integer(0);
private static final Integer ONE = new Integer(1);
private String pattern;
private final boolean allowDecimals;
private boolean useLocaleFormat;
private Locale locale;
// ----------------------------------------------------------- Constructors
/**
* Construct a java.lang.Number Converter
* that throws a ConversionException
if a error occurs.
*
* @param allowDecimals
* Indicates whether decimals are allowed
*/
public NumberConverter(final boolean allowDecimals){
super();
this.allowDecimals = allowDecimals;
}
/**
* Construct a java.lang.Number
Converter that returns
* a default value if an error occurs.
*
* @param allowDecimals
* Indicates whether decimals are allowed
* @param defaultValue
* The default value to be returned
*/
public NumberConverter(final boolean allowDecimals, final Object defaultValue){
super();
this.allowDecimals = allowDecimals;
setDefaultValue(defaultValue);
}
// --------------------------------------------------------- Public Methods
/**
* Return whether decimals are allowed in the number.
*
* @return Whether decimals are allowed in the number
*/
public boolean isAllowDecimals(){
return allowDecimals;
}
/**
* Set whether a format should be used to convert
* the Number.
*
* @param useLocaleFormat
* true
if a number format
* should be used.
*/
public void setUseLocaleFormat(final boolean useLocaleFormat){
this.useLocaleFormat = useLocaleFormat;
}
/**
* Return the number format pattern used to convert
* Numbers to/from a java.lang.String
* (or null
if none specified).
*
* See java.text.DecimalFormat
for details
* of how to specify the pattern.
*
* @return The format pattern.
*/
public String getPattern(){
return pattern;
}
/**
* Set a number format pattern to use to convert
* Numbers to/from a java.lang.String
.
*
* See java.text.DecimalFormat
for details
* of how to specify the pattern.
*
* @param pattern
* The format pattern.
*/
public void setPattern(final String pattern){
this.pattern = pattern;
setUseLocaleFormat(true);
}
/**
* Return the Locale for the Converter
* (or null
if none specified).
*
* @return The locale to use for conversion
*/
public Locale getLocale(){
return locale;
}
/**
* Set the Locale for the Converter.
*
* @param locale
* The locale to use for conversion
*/
public void setLocale(final Locale locale){
this.locale = locale;
setUseLocaleFormat(true);
}
// ------------------------------------------------------ Protected Methods
/**
* Convert an input Number object into a String.
*
* @param value
* The input value to be converted
* @return the converted String value.
* @throws Throwable
* if an error occurs converting to a String
*/
@Override
protected String convertToString(final Object value) throws Throwable{
String result = null;
if (useLocaleFormat && value instanceof Number){
final NumberFormat format = getFormat();
format.setGroupingUsed(false);
result = format.format(value);
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Converted to String using format '" + result + "'");
}
}else{
result = value.toString();
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Converted to String using toString() '" + result + "'");
}
}
return result;
}
/**
* Convert the input object into a Number object of the
* specified type.
*
* @param
* Target type of the conversion.
* @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
*/
@Override
protected T convertToType(final Class targetType,final Object value) throws Throwable{
final Class> sourceType = value.getClass();
// Handle Number
if (value instanceof Number){
return toNumber(sourceType, targetType, (Number) value);
}
// Handle Boolean
if (value instanceof Boolean){
return toNumber(sourceType, targetType, ((Boolean) value).booleanValue() ? ONE : ZERO);
}
// Handle Date --> Long
if (value instanceof Date && Long.class.equals(targetType)){
return targetType.cast(new Long(((Date) value).getTime()));
}
// Handle Calendar --> Long
if (value instanceof Calendar && Long.class.equals(targetType)){
return targetType.cast(new Long(((Calendar) value).getTime().getTime()));
}
// Convert all other types to String & handle
final String stringValue = value.toString().trim();
if (stringValue.length() == 0){
return handleMissing(targetType);
}
// Convert/Parse a String
Number number = null;
if (useLocaleFormat){
final NumberFormat format = getFormat();
number = parse(sourceType, targetType, stringValue, format);
}else{
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" No NumberFormat, using default conversion");
}
number = toNumber(sourceType, targetType, stringValue);
}
// Ensure the correct number type is returned
return toNumber(sourceType, targetType, number);
}
/**
* Convert any Number object to the specified type for this
* Converter.
*
* This method handles conversion to the following types:
*
* java.lang.Byte
* java.lang.Short
* java.lang.Integer
* java.lang.Long
* java.lang.Float
* java.lang.Double
* java.math.BigDecimal
* java.math.BigInteger
*
*
* @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 T toNumber(final Class> sourceType,final Class targetType,final Number value){
// Correct Number type already
if (targetType.equals(value.getClass())){
return targetType.cast(value);
}
// Byte
if (targetType.equals(Byte.class)){
final long longValue = value.longValue();
if (longValue > Byte.MAX_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too large for " + toString(targetType));
}
if (longValue < Byte.MIN_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too small " + toString(targetType));
}
return targetType.cast(new Byte(value.byteValue()));
}
// Short
if (targetType.equals(Short.class)){
final long longValue = value.longValue();
if (longValue > Short.MAX_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too large for " + toString(targetType));
}
if (longValue < Short.MIN_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too small " + toString(targetType));
}
return targetType.cast(new Short(value.shortValue()));
}
// Integer
if (targetType.equals(Integer.class)){
final long longValue = value.longValue();
if (longValue > Integer.MAX_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too large for " + toString(targetType));
}
if (longValue < Integer.MIN_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too small " + toString(targetType));
}
return targetType.cast(new Integer(value.intValue()));
}
// Long
if (targetType.equals(Long.class)){
return targetType.cast(new Long(value.longValue()));
}
// Float
if (targetType.equals(Float.class)){
if (value.doubleValue() > Float.MAX_VALUE){
throw new ConversionException(toString(sourceType) + " value '" + value + "' is too large for " + toString(targetType));
}
return targetType.cast(new Float(value.floatValue()));
}
// Double
if (targetType.equals(Double.class)){
return targetType.cast(new Double(value.doubleValue()));
}
// BigDecimal
if (targetType.equals(BigDecimal.class)){
if (value instanceof Float || value instanceof Double){
return targetType.cast(new BigDecimal(value.toString()));
}else if (value instanceof BigInteger){
return targetType.cast(new BigDecimal((BigInteger) value));
}else if (value instanceof BigDecimal){
return targetType.cast(new BigDecimal(value.toString()));
}else{
return targetType.cast(BigDecimal.valueOf(value.longValue()));
}
}
// BigInteger
if (targetType.equals(BigInteger.class)){
if (value instanceof BigDecimal){
return targetType.cast(((BigDecimal) value).toBigInteger());
}
return targetType.cast(BigInteger.valueOf(value.longValue()));
}
final String msg = toString(getClass()) + " cannot handle conversion to '" + toString(targetType) + "'";
if (LOGGER.isWarnEnabled()){
LOGGER.warn(" " + msg);
}
throw new ConversionException(msg);
}
/**
* Default String to Number conversion.
*
* This method handles conversion from a String to the following types:
*
* java.lang.Byte
* java.lang.Short
* java.lang.Integer
* java.lang.Long
* java.lang.Float
* java.lang.Double
* java.math.BigDecimal
* java.math.BigInteger
*
*
* @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 Number toNumber(final Class> sourceType,final Class> targetType,final String value){
// Byte
if (targetType.equals(Byte.class)){
return new Byte(value);
}
// Short
if (targetType.equals(Short.class)){
return new Short(value);
}
// Integer
if (targetType.equals(Integer.class)){
return new Integer(value);
}
// Long
if (targetType.equals(Long.class)){
return new Long(value);
}
// Float
if (targetType.equals(Float.class)){
return new Float(value);
}
// Double
if (targetType.equals(Double.class)){
return new Double(value);
}
// BigDecimal
if (targetType.equals(BigDecimal.class)){
return new BigDecimal(value);
}
// BigInteger
if (targetType.equals(BigInteger.class)){
return new BigInteger(value);
}
final String msg = toString(getClass()) + " cannot handle conversion from '" + toString(sourceType) + "' to '"
+ toString(targetType) + "'";
if (LOGGER.isWarnEnabled()){
LOGGER.warn(" " + msg);
}
throw new ConversionException(msg);
}
/**
* Provide a String representation of this number converter.
*
* @return A String representation of this number converter
*/
@Override
public String toString(){
final StringBuilder buffer = new StringBuilder();
buffer.append(toString(getClass()));
buffer.append("[UseDefault=");
buffer.append(isUseDefault());
buffer.append(", UseLocaleFormat=");
buffer.append(useLocaleFormat);
if (pattern != null){
buffer.append(", Pattern=");
buffer.append(pattern);
}
if (locale != null){
buffer.append(", Locale=");
buffer.append(locale);
}
buffer.append(']');
return buffer.toString();
}
/**
* Return a NumberFormat to use for Conversion.
*
* @return The NumberFormat.
*/
private NumberFormat getFormat(){
NumberFormat format = null;
if (pattern != null){
if (locale == null){
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Using pattern '" + pattern + "'");
}
format = new DecimalFormat(pattern);
}else{
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Using pattern '" + pattern + "'" + " with Locale[" + locale + "]");
}
final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
format = new DecimalFormat(pattern, symbols);
}
}else{
if (locale == null){
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Using default Locale format");
}
format = NumberFormat.getInstance();
}else{
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" Using Locale[" + locale + "] format");
}
format = NumberFormat.getInstance(locale);
}
}
if (!allowDecimals){
format.setParseIntegerOnly(true);
}
return format;
}
/**
* Convert a String into a Number
object.
*
* @param sourceType
* the source type of the conversion
* @param targetType
* The type to convert the value to
* @param value
* The String date value.
* @param format
* The NumberFormat to parse the String value.
*
* @return The converted Number object.
* @throws ConversionException
* if the String cannot be converted.
*/
private Number parse(final Class> sourceType,final Class> targetType,final String value,final NumberFormat format){
final ParsePosition pos = new ParsePosition(0);
final Number parsedNumber = format.parse(value, pos);
if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null){
String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
if (format instanceof DecimalFormat){
msg += " using pattern '" + ((DecimalFormat) format).toPattern() + "'";
}
if (locale != null){
msg += " for locale=[" + locale + "]";
}
if (LOGGER.isDebugEnabled()){
LOGGER.debug(" " + msg);
}
throw new ConversionException(msg);
}
return parsedNumber;
}
}