/*
* 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 org.apache.commons.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.apache.commons.beanutils.ConversionException;
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
/**
* {@link org.apache.commons.beanutils.Converter} implementaion that handles conversion
* to and from <b>java.lang.Number</b> objects.
* <p>
* This implementation handles conversion for the following
* <code>java.lang.Number</code> 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>
*
* <h3>String Conversions (to and from)</h3>
* This class provides a number of ways in which number
* conversions to/from Strings can be achieved:
* <ul>
* <li>Using the default format for the default Locale, configure using:</li>
* <ul>
* <li><code>setUseLocaleFormat(true)</code></li>
* </ul>
* <li>Using the default format for a specified Locale, configure using:</li>
* <ul>
* <li><code>setLocale(Locale)</code></li>
* </ul>
* <li>Using a specified pattern for the default Locale, configure using:</li>
* <ul>
* <li><code>setPattern(String)</code></li>
* </ul>
* <li>Using a specified pattern for a specified Locale, configure using:</li>
* <ul>
* <li><code>setPattern(String)</code></li>
* <li><code>setLocale(Locale)</code></li>
* </ul>
* <li>If none of the above are configured the
* <code>toNumber(String)</code> method is used to convert
* from String to Number and the Number's
* <code>toString()</code> method used to convert from
* Number to String.</li>
* </ul>
*
* <p>
* <strong>N.B.</strong>Patterns can only be specified using the <i>standard</i>
* pattern characters and NOT in <i>localized</i> form (see <code>java.text.DecimalFormat</code>).
* For example to cater for number styles used in Germany such as <code>0.000,00</code> the pattern
* is specified in the normal form <code>0,000.00</code> and the locale set to <code>Locale.GERMANY</code>.
*
* @version $Id$
* @since 1.8.0
*/
public abstract class NumberConverter extends AbstractConverter {
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.
Params: - allowDecimals – Indicates whether decimals are allowed
/**
* Construct a <b>java.lang.Number</b> <i>Converter</i>
* that throws a <code>ConversionException</code> 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.
Params: - allowDecimals – Indicates whether decimals are allowed
- defaultValue – The default value to be returned
/**
* Construct a <code>java.lang.Number</code> <i>Converter</i> 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.
Returns: Whether decimals are allowed in the number
/**
* 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.
Params: - useLocaleFormat –
true
if a number format
should be used.
/**
* Set whether a format should be used to convert
* the Number.
*
* @param useLocaleFormat <code>true</code> 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.
Returns: The format pattern.
/**
* Return the number format pattern used to convert
* Numbers to/from a <code>java.lang.String</code>
* (or <code>null</code> if none specified).
* <p>
* See <code>java.text.DecimalFormat</code> 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.
Params: - pattern – The format pattern.
/**
* Set a number format pattern to use to convert
* Numbers to/from a <code>java.lang.String</code>.
* <p>
* See <code>java.text.DecimalFormat</code> 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).
Returns: The locale to use for conversion
/**
* Return the Locale for the <i>Converter</i>
* (or <code>null</code> if none specified).
*
* @return The locale to use for conversion
*/
public Locale getLocale() {
return locale;
}
Set the Locale for the Converter.
Params: - locale – The locale to use for conversion
/**
* Set the Locale for the <i>Converter</i>.
*
* @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.
Params: - value – The input value to be converted
Throws: - Throwable – if an error occurs converting to a String
Returns: the converted String value.
/**
* 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 (log().isDebugEnabled()) {
log().debug(" Converted to String using format '" + result + "'");
}
} else {
result = value.toString();
if (log().isDebugEnabled()) {
log().debug(" Converted to String using toString() '" + result + "'");
}
}
return result;
}
Convert the input object into a Number object of the
specified type.
Params: - targetType – Data type to which this value should be converted.
- value – The input value to be converted.
Type parameters: - <T> – Target type of the conversion.
Throws: - Throwable – if an error occurs converting to the specified type
Returns: The converted value.
/**
* Convert the input object into a Number object of the
* specified type.
*
* @param <T> 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> T convertToType(final Class<T> 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 (log().isDebugEnabled()) {
log().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
Params: - sourceType – The type being converted from
- targetType – The Number type to convert to
- value – The Number to convert.
Returns: The converted value.
/**
* 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.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 Number to convert.
*
* @return The converted value.
*/
private <T> T toNumber(final Class<?> sourceType, final Class<T> 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());
} else {
return targetType.cast(BigInteger.valueOf(value.longValue()));
}
}
final String msg = toString(getClass()) + " cannot handle conversion to '"
+ toString(targetType) + "'";
if (log().isWarnEnabled()) {
log().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
Params: - sourceType – The type being converted from
- targetType – The Number type to convert to
- value – The String value to convert.
Returns: The converted Number value.
/**
* 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 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 (log().isWarnEnabled()) {
log().warn(" " + msg);
}
throw new ConversionException(msg);
}
Provide a String representation of this number converter.
Returns: A String representation of this number converter
/**
* 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.
Returns: The NumberFormat.
/**
* Return a NumberFormat to use for Conversion.
*
* @return The NumberFormat.
*/
private NumberFormat getFormat() {
NumberFormat format = null;
if (pattern != null) {
if (locale == null) {
if (log().isDebugEnabled()) {
log().debug(" Using pattern '" + pattern + "'");
}
format = new DecimalFormat(pattern);
} else {
if (log().isDebugEnabled()) {
log().debug(" Using pattern '" + pattern + "'" +
" with Locale[" + locale + "]");
}
final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
format = new DecimalFormat(pattern, symbols);
}
} else {
if (locale == null) {
if (log().isDebugEnabled()) {
log().debug(" Using default Locale format");
}
format = NumberFormat.getInstance();
} else {
if (log().isDebugEnabled()) {
log().debug(" Using Locale[" + locale + "] format");
}
format = NumberFormat.getInstance(locale);
}
}
if (!allowDecimals) {
format.setParseIntegerOnly(true);
}
return format;
}
Convert a String into a Number
object.
Params: - sourceType – the source type of the conversion
- targetType – The type to convert the value to
- value – The String date value.
- format – The NumberFormat to parse the String value.
Throws: - ConversionException – if the String cannot be converted.
Returns: The converted Number object.
/**
* Convert a String into a <code>Number</code> 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 (log().isDebugEnabled()) {
log().debug(" " + msg);
}
throw new ConversionException(msg);
}
return parsedNumber;
}
}