NumberConverter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.beanutils2.converters;

  18. import java.math.BigDecimal;
  19. import java.math.BigInteger;
  20. import java.text.DecimalFormat;
  21. import java.text.DecimalFormatSymbols;
  22. import java.text.NumberFormat;
  23. import java.text.ParsePosition;
  24. import java.util.Calendar;
  25. import java.util.Date;
  26. import java.util.Locale;

  27. import org.apache.commons.beanutils2.ConversionException;

  28. /**
  29.  * {@link org.apache.commons.beanutils2.Converter} implementation that handles conversion to and from <strong>java.lang.Number</strong> objects.
  30.  * <p>
  31.  * This implementation handles conversion for the following {@link Number} types.
  32.  * <ul>
  33.  * <li>{@link Byte}</li>
  34.  * <li>{@link Short}</li>
  35.  * <li>{@link Integer}</li>
  36.  * <li>{@link Long}</li>
  37.  * <li>{@link Float}</li>
  38.  * <li>{@link Double}</li>
  39.  * <li>{@link java.math.BigDecimal}</li>
  40.  * <li>{@link java.math.BigInteger}</li>
  41.  * </ul>
  42.  *
  43.  * <h2>String Conversions (to and from)</h2> This class provides a number of ways in which number conversions to/from Strings can be achieved:
  44.  * <ul>
  45.  * <li>Using the default format for the default Locale, configure using:
  46.  * <ul>
  47.  * <li>{@code setUseLocaleFormat(true)}</li>
  48.  * </ul>
  49.  * </li>
  50.  * <li>Using the default format for a specified Locale, configure using:
  51.  * <ul>
  52.  * <li>{@code setLocale(Locale)}</li>
  53.  * </ul>
  54.  * </li>
  55.  * <li>Using a specified pattern for the default Locale, configure using:
  56.  * <ul>
  57.  * <li>{@code setPattern(String)}</li>
  58.  * </ul>
  59.  * </li>
  60.  * <li>Using a specified pattern for a specified Locale, configure using:
  61.  * <ul>
  62.  * <li>{@code setPattern(String)}</li>
  63.  * <li>{@code setLocale(Locale)}</li>
  64.  * </ul>
  65.  * </li>
  66.  * <li>If none of the above are configured the {@code toNumber(String)} method is used to convert from String to Number and the Number's {@code toString()}
  67.  * method used to convert from Number to String.</li>
  68.  * </ul>
  69.  *
  70.  * <p>
  71.  * <strong>N.B.</strong>Patterns can only be specified using the <em>standard</em> pattern characters and NOT in <em>localized</em> form (see
  72.  * {@link java.text.DecimalFormat}). For example to cater for number styles used in Germany such as {@code 0.000,00} the pattern is specified in the normal form
  73.  * {@code 0,000.00</code> and the locale set to <code>Locale.GERMANY}
  74.  *
  75.  * @param <N> The default value type.
  76.  * @since 1.8.0
  77.  */
  78. public abstract class NumberConverter<N extends Number> extends AbstractConverter<N> {

  79.     private static final Integer ZERO = Integer.valueOf(0);
  80.     private static final Integer ONE = Integer.valueOf(1);

  81.     private String pattern;
  82.     private final boolean allowDecimals;
  83.     private boolean useLocaleFormat;
  84.     private Locale locale;

  85.     /**
  86.      * Constructs a <strong>java.lang.Number</strong> <em>Converter</em> that throws a {@code ConversionException} if a error occurs.
  87.      *
  88.      * @param allowDecimals Indicates whether decimals are allowed
  89.      */
  90.     public NumberConverter(final boolean allowDecimals) {
  91.         this.allowDecimals = allowDecimals;
  92.     }

  93.     /**
  94.      * Constructs a {@link Number} <em>Converter</em> that returns a default value if an error occurs.
  95.      *
  96.      * @param allowDecimals Indicates whether decimals are allowed
  97.      * @param defaultValue  The default value to be returned
  98.      */
  99.     public NumberConverter(final boolean allowDecimals, final N defaultValue) {
  100.         this.allowDecimals = allowDecimals;
  101.         setDefaultValue(defaultValue);
  102.     }

  103.     /**
  104.      * Convert an input Number object into a String.
  105.      *
  106.      * @param value The input value to be converted
  107.      * @return the converted String value.
  108.      * @throws IllegalArgumentException if an error occurs converting to a String
  109.      */
  110.     @Override
  111.     protected String convertToString(final Object value) {

  112.         String result = null;
  113.         if (useLocaleFormat && value instanceof Number) {
  114.             final NumberFormat format = getFormat();
  115.             format.setGroupingUsed(false);
  116.             result = format.format(value);
  117.             if (log().isDebugEnabled()) {
  118.                 log().debug("    Converted  to String using format '" + result + "'");
  119.             }

  120.         } else {
  121.             result = value.toString();
  122.             if (log().isDebugEnabled()) {
  123.                 log().debug("    Converted  to String using toString() '" + result + "'");
  124.             }
  125.         }
  126.         return result;

  127.     }

  128.     /**
  129.      * Convert the input object into a Number object of the specified type.
  130.      *
  131.      * @param <T>        Target type of the conversion.
  132.      * @param targetType Data type to which this value should be converted.
  133.      * @param value      The input value to be converted.
  134.      * @return The converted value.
  135.      * @throws Throwable if an error occurs converting to the specified type
  136.      */
  137.     @Override
  138.     protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable {
  139.         final Class<?> sourceType = value.getClass();
  140.         // Handle Number
  141.         if (value instanceof Number) {
  142.             return toNumber(sourceType, targetType, (Number) value);
  143.         }

  144.         // Handle Boolean
  145.         if (value instanceof Boolean) {
  146.             return toNumber(sourceType, targetType, ((Boolean) value).booleanValue() ? ONE : ZERO);
  147.         }

  148.         // Handle Date --> Long
  149.         if (value instanceof Date && Long.class.equals(targetType)) {
  150.             return targetType.cast(Long.valueOf(((Date) value).getTime()));
  151.         }

  152.         // Handle Calendar --> Long
  153.         if (value instanceof Calendar && Long.class.equals(targetType)) {
  154.             return targetType.cast(Long.valueOf(((Calendar) value).getTime().getTime()));
  155.         }

  156.         // Convert all other types to String & handle
  157.         final String stringValue = toTrim(value);
  158.         if (stringValue.isEmpty()) {
  159.             return handleMissing(targetType);
  160.         }

  161.         // Convert/Parse a String
  162.         Number number = null;
  163.         if (useLocaleFormat) {
  164.             final NumberFormat format = getFormat();
  165.             number = parse(sourceType, targetType, stringValue, format);
  166.         } else {
  167.             if (log().isDebugEnabled()) {
  168.                 log().debug("    No NumberFormat, using default conversion");
  169.             }
  170.             number = toNumber(sourceType, targetType, stringValue);
  171.         }

  172.         // Ensure the correct number type is returned
  173.         return toNumber(sourceType, targetType, number);
  174.     }

  175.     /**
  176.      * Gets a NumberFormat to use for Conversion.
  177.      *
  178.      * @return The NumberFormat.
  179.      */
  180.     private NumberFormat getFormat() {
  181.         NumberFormat format = null;
  182.         if (pattern != null) {
  183.             if (locale == null) {
  184.                 if (log().isDebugEnabled()) {
  185.                     log().debug("    Using pattern '" + pattern + "'");
  186.                 }
  187.                 format = new DecimalFormat(pattern);
  188.             } else {
  189.                 if (log().isDebugEnabled()) {
  190.                     log().debug("    Using pattern '" + pattern + "'" + " with Locale[" + locale + "]");
  191.                 }
  192.                 final DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
  193.                 format = new DecimalFormat(pattern, symbols);
  194.             }
  195.         } else if (locale == null) {
  196.             if (log().isDebugEnabled()) {
  197.                 log().debug("    Using default Locale format");
  198.             }
  199.             format = NumberFormat.getInstance();
  200.         } else {
  201.             if (log().isDebugEnabled()) {
  202.                 log().debug("    Using Locale[" + locale + "] format");
  203.             }
  204.             format = NumberFormat.getInstance(locale);
  205.         }
  206.         if (!allowDecimals) {
  207.             format.setParseIntegerOnly(true);
  208.         }
  209.         return format;
  210.     }

  211.     /**
  212.      * Gets the Locale for the <em>Converter</em> (or {@code null} if none specified).
  213.      *
  214.      * @return The locale to use for conversion
  215.      */
  216.     public Locale getLocale() {
  217.         return locale;
  218.     }

  219.     /**
  220.      * Gets the number format pattern used to convert Numbers to/from a {@link String} (or {@code null} if none specified).
  221.      * <p>
  222.      * See {@link java.text.DecimalFormat} for details of how to specify the pattern.
  223.      *
  224.      * @return The format pattern.
  225.      */
  226.     public String getPattern() {
  227.         return pattern;
  228.     }

  229.     /**
  230.      * Gets whether decimals are allowed in the number.
  231.      *
  232.      * @return Whether decimals are allowed in the number
  233.      */
  234.     public boolean isAllowDecimals() {
  235.         return allowDecimals;
  236.     }

  237.     /**
  238.      * Convert a String into a {@code Number} object.
  239.      *
  240.      * @param sourceType the source type of the conversion
  241.      * @param targetType The type to convert the value to
  242.      * @param value      The String date value.
  243.      * @param format     The NumberFormat to parse the String value.
  244.      * @return The converted Number object.
  245.      * @throws ConversionException if the String cannot be converted.
  246.      */
  247.     private Number parse(final Class<?> sourceType, final Class<?> targetType, final String value, final NumberFormat format) {
  248.         final ParsePosition pos = new ParsePosition(0);
  249.         final Number parsedNumber = format.parse(value, pos);
  250.         if (pos.getErrorIndex() >= 0 || pos.getIndex() != value.length() || parsedNumber == null) {
  251.             String msg = "Error converting from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
  252.             if (format instanceof DecimalFormat) {
  253.                 msg += " using pattern '" + ((DecimalFormat) format).toPattern() + "'";
  254.             }
  255.             if (locale != null) {
  256.                 msg += " for locale=[" + locale + "]";
  257.             }
  258.             if (log().isDebugEnabled()) {
  259.                 log().debug("    " + msg);
  260.             }
  261.             throw new ConversionException(msg);
  262.         }
  263.         return parsedNumber;
  264.     }

  265.     /**
  266.      * Sets the Locale for the <em>Converter</em>.
  267.      *
  268.      * @param locale The locale to use for conversion
  269.      */
  270.     public void setLocale(final Locale locale) {
  271.         this.locale = locale;
  272.         setUseLocaleFormat(true);
  273.     }

  274.     /**
  275.      * Sets a number format pattern to use to convert Numbers to/from a {@link String}.
  276.      * <p>
  277.      * See {@link java.text.DecimalFormat} for details of how to specify the pattern.
  278.      *
  279.      * @param pattern The format pattern.
  280.      */
  281.     public void setPattern(final String pattern) {
  282.         this.pattern = pattern;
  283.         setUseLocaleFormat(true);
  284.     }

  285.     /**
  286.      * Sets whether a format should be used to convert the Number.
  287.      *
  288.      * @param useLocaleFormat {@code true} if a number format should be used.
  289.      */
  290.     public void setUseLocaleFormat(final boolean useLocaleFormat) {
  291.         this.useLocaleFormat = useLocaleFormat;
  292.     }

  293.     /**
  294.      * Default String to Number conversion.
  295.      * <p>
  296.      * This method handles conversion from a String to the following types:
  297.      * <ul>
  298.      * <li>{@link Byte}</li>
  299.      * <li>{@link Short}</li>
  300.      * <li>{@link Integer}</li>
  301.      * <li>{@link Long}</li>
  302.      * <li>{@link Float}</li>
  303.      * <li>{@link Double}</li>
  304.      * <li>{@link java.math.BigDecimal}</li>
  305.      * <li>{@link java.math.BigInteger}</li>
  306.      * </ul>
  307.      *
  308.      * @param sourceType The type being converted from
  309.      * @param targetType The Number type to convert to
  310.      * @param value      The String value to convert.
  311.      * @return The converted Number value.
  312.      */
  313.     private Number toNumber(final Class<?> sourceType, final Class<?> targetType, final String value) {
  314.         // Byte
  315.         if (targetType.equals(Byte.class)) {
  316.             return Byte.valueOf(value);
  317.         }

  318.         // Short
  319.         if (targetType.equals(Short.class)) {
  320.             return Short.valueOf(value);
  321.         }

  322.         // Integer
  323.         if (targetType.equals(Integer.class)) {
  324.             return Integer.valueOf(value);
  325.         }

  326.         // Long
  327.         if (targetType.equals(Long.class)) {
  328.             return Long.valueOf(value);
  329.         }

  330.         // Float
  331.         if (targetType.equals(Float.class)) {
  332.             return Float.valueOf(value);
  333.         }

  334.         // Double
  335.         if (targetType.equals(Double.class)) {
  336.             return Double.valueOf(value);
  337.         }

  338.         // BigDecimal
  339.         if (targetType.equals(BigDecimal.class)) {
  340.             return new BigDecimal(value);
  341.         }

  342.         // BigInteger
  343.         if (targetType.equals(BigInteger.class)) {
  344.             return new BigInteger(value);
  345.         }

  346.         final String msg = toString(getClass()) + " cannot handle conversion from '" + toString(sourceType) + "' to '" + toString(targetType) + "'";
  347.         if (log().isWarnEnabled()) {
  348.             log().warn("    " + msg);
  349.         }
  350.         throw new ConversionException(msg);
  351.     }

  352.     /**
  353.      * Convert any Number object to the specified type for this <em>Converter</em>.
  354.      * <p>
  355.      * This method handles conversion to the following types:
  356.      * <ul>
  357.      * <li>{@link Byte}</li>
  358.      * <li>{@link Short}</li>
  359.      * <li>{@link Integer}</li>
  360.      * <li>{@link Long}</li>
  361.      * <li>{@link Float}</li>
  362.      * <li>{@link Double}</li>
  363.      * <li>{@link java.math.BigDecimal}</li>
  364.      * <li>{@link java.math.BigInteger}</li>
  365.      * </ul>
  366.      *
  367.      * @param sourceType The type being converted from
  368.      * @param targetType The Number type to convert to
  369.      * @param value      The Number to convert.
  370.      * @return The converted value.
  371.      */
  372.     private <T> T toNumber(final Class<?> sourceType, final Class<T> targetType, final Number value) {
  373.         // Correct Number type already
  374.         if (targetType.equals(value.getClass())) {
  375.             return targetType.cast(value);
  376.         }

  377.         // Byte
  378.         if (targetType.equals(Byte.class)) {
  379.             final long longValue = value.longValue();
  380.             if (longValue > Byte.MAX_VALUE) {
  381.                 throw ConversionException.format("%s value '%s' is too large for %s", toString(sourceType), value, toString(targetType));
  382.             }
  383.             if (longValue < Byte.MIN_VALUE) {
  384.                 throw ConversionException.format("%s value '%s' is too small %s", toString(sourceType), value, toString(targetType));
  385.             }
  386.             return targetType.cast(Byte.valueOf(value.byteValue()));
  387.         }

  388.         // Short
  389.         if (targetType.equals(Short.class)) {
  390.             final long longValue = value.longValue();
  391.             if (longValue > Short.MAX_VALUE) {
  392.                 throw ConversionException.format("%s value '%s' is too large for %s", toString(sourceType), value, toString(targetType));
  393.             }
  394.             if (longValue < Short.MIN_VALUE) {
  395.                 throw ConversionException.format("%s value '%s' is too small %s", toString(sourceType), value, toString(targetType));
  396.             }
  397.             return targetType.cast(Short.valueOf(value.shortValue()));
  398.         }

  399.         // Integer
  400.         if (targetType.equals(Integer.class)) {
  401.             final long longValue = value.longValue();
  402.             if (longValue > Integer.MAX_VALUE) {
  403.                 throw ConversionException.format("%s value '%s' is too large for %s", toString(sourceType), value, toString(targetType));
  404.             }
  405.             if (longValue < Integer.MIN_VALUE) {
  406.                 throw ConversionException.format("%s value '%s' is too small %s", toString(sourceType), value, toString(targetType));
  407.             }
  408.             return targetType.cast(Integer.valueOf(value.intValue()));
  409.         }

  410.         // Long
  411.         if (targetType.equals(Long.class)) {
  412.             return targetType.cast(Long.valueOf(value.longValue()));
  413.         }

  414.         // Float
  415.         if (targetType.equals(Float.class)) {
  416.             if (value.doubleValue() > Float.MAX_VALUE) {
  417.                 throw ConversionException.format("%s value '%s' is too large for %s", toString(sourceType), value, toString(targetType));
  418.             }
  419.             return targetType.cast(Float.valueOf(value.floatValue()));
  420.         }

  421.         // Double
  422.         if (targetType.equals(Double.class)) {
  423.             return targetType.cast(Double.valueOf(value.doubleValue()));
  424.         }

  425.         // BigDecimal
  426.         if (targetType.equals(BigDecimal.class)) {
  427.             if (value instanceof Float || value instanceof Double) {
  428.                 return targetType.cast(new BigDecimal(value.toString()));
  429.             }
  430.             if (value instanceof BigInteger) {
  431.                 return targetType.cast(new BigDecimal((BigInteger) value));
  432.             }
  433.             if (value instanceof BigDecimal) {
  434.                 return targetType.cast(new BigDecimal(value.toString()));
  435.             }
  436.             return targetType.cast(BigDecimal.valueOf(value.longValue()));
  437.         }

  438.         // BigInteger
  439.         if (targetType.equals(BigInteger.class)) {
  440.             if (value instanceof BigDecimal) {
  441.                 return targetType.cast(((BigDecimal) value).toBigInteger());
  442.             }
  443.             return targetType.cast(BigInteger.valueOf(value.longValue()));
  444.         }

  445.         final String msg = toString(getClass()) + " cannot handle conversion to '" + toString(targetType) + "'";
  446.         if (log().isWarnEnabled()) {
  447.             log().warn("    " + msg);
  448.         }
  449.         throw new ConversionException(msg);
  450.     }

  451.     /**
  452.      * Provide a String representation of this number converter.
  453.      *
  454.      * @return A String representation of this number converter
  455.      */
  456.     @Override
  457.     public String toString() {
  458.         final StringBuilder buffer = new StringBuilder();
  459.         buffer.append(toString(getClass()));
  460.         buffer.append("[UseDefault=");
  461.         buffer.append(isUseDefault());
  462.         buffer.append(", UseLocaleFormat=");
  463.         buffer.append(useLocaleFormat);
  464.         if (pattern != null) {
  465.             buffer.append(", Pattern=");
  466.             buffer.append(pattern);
  467.         }
  468.         if (locale != null) {
  469.             buffer.append(", Locale=");
  470.             buffer.append(locale);
  471.         }
  472.         buffer.append(']');
  473.         return buffer.toString();
  474.     }

  475. }