LocaleConvertUtilsBean.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.locale;

  18. import java.lang.reflect.Array;
  19. import java.math.BigDecimal;
  20. import java.math.BigInteger;
  21. import java.util.Locale;
  22. import java.util.Map;

  23. import org.apache.commons.beanutils2.BeanUtils;
  24. import org.apache.commons.beanutils2.ConversionException;
  25. import org.apache.commons.beanutils2.locale.converters.BigDecimalLocaleConverter;
  26. import org.apache.commons.beanutils2.locale.converters.BigIntegerLocaleConverter;
  27. import org.apache.commons.beanutils2.locale.converters.ByteLocaleConverter;
  28. import org.apache.commons.beanutils2.locale.converters.DoubleLocaleConverter;
  29. import org.apache.commons.beanutils2.locale.converters.FloatLocaleConverter;
  30. import org.apache.commons.beanutils2.locale.converters.IntegerLocaleConverter;
  31. import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter;
  32. import org.apache.commons.beanutils2.locale.converters.ShortLocaleConverter;
  33. import org.apache.commons.beanutils2.locale.converters.StringLocaleConverter;
  34. import org.apache.commons.beanutils2.sql.converters.locale.SqlDateLocaleConverter;
  35. import org.apache.commons.beanutils2.sql.converters.locale.SqlTimeLocaleConverter;
  36. import org.apache.commons.beanutils2.sql.converters.locale.SqlTimestampLocaleConverter;
  37. import org.apache.commons.logging.Log;
  38. import org.apache.commons.logging.LogFactory;

  39. /**
  40.  * <p>
  41.  * Utility methods for converting locale-sensitive String scalar values to objects of the specified Class, String arrays to arrays of the specified Class and
  42.  * object to locale-sensitive String scalar value.
  43.  * </p>
  44.  *
  45.  * <p>
  46.  * This class provides the implementations used by the static utility methods in {@link LocaleConvertUtils}.
  47.  * </p>
  48.  *
  49.  * <p>
  50.  * The actual {@link LocaleConverter} instance to be used can be registered for each possible destination Class. Unless you override them, standard
  51.  * {@link LocaleConverter} instances are provided for all of the following destination Classes:
  52.  * </p>
  53.  * <ul>
  54.  * <li>java.lang.BigDecimal</li>
  55.  * <li>java.lang.BigInteger</li>
  56.  * <li>byte and java.lang.Byte</li>
  57.  * <li>double and java.lang.Double</li>
  58.  * <li>float and java.lang.Float</li>
  59.  * <li>int and java.lang.Integer</li>
  60.  * <li>long and java.lang.Long</li>
  61.  * <li>short and java.lang.Short</li>
  62.  * <li>java.lang.String</li>
  63.  * <li>java.sql.Date</li>
  64.  * <li>java.sql.Time</li>
  65.  * <li>java.sql.Timestamp</li>
  66.  * </ul>
  67.  *
  68.  * <p>
  69.  * For backwards compatibility, the standard locale converters for primitive types (and the corresponding wrapper classes).
  70.  *
  71.  * If you prefer to have another {@link LocaleConverter} thrown instead, replace the standard {@link LocaleConverter} instances with ones created with the one
  72.  * of the appropriate constructors.
  73.  *
  74.  * It's important that {@link LocaleConverter} should be registered for the specified locale and Class (or primitive type).
  75.  *
  76.  * @since 1.7
  77.  */
  78. public class LocaleConvertUtilsBean {

  79.     /** The {@code Log} instance for this class. */
  80.     private static final Log LOG = LogFactory.getLog(LocaleConvertUtilsBean.class);

  81.     /**
  82.      * Gets singleton instance. This is the same as the instance used by the default {@link LocaleBeanUtilsBean} singleton.
  83.      *
  84.      * @return the singleton instance
  85.      */
  86.     public static LocaleConvertUtilsBean getInstance() {
  87.         return LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getLocaleConvertUtils();
  88.     }

  89.     /** The locale - default for conversion. */
  90.     private Locale defaultLocale = Locale.getDefault();

  91.     /** Indicate whether the pattern is localized or not */
  92.     private boolean applyLocalized;

  93.     /**
  94.      * Every entry of the mapConverters is:
  95.      * <ul>
  96.      * <li>key = locale</li>
  97.      * <li>value = map of converters for the certain locale.</li>
  98.      * <ul>
  99.      */
  100.     private final Map<Locale, Map<Class<?>, LocaleConverter<?>>> mapConverters;

  101.     /**
  102.      * Makes the state by default (deregisters all converters for all locales) and then registers default locale converters.
  103.      */
  104.     public LocaleConvertUtilsBean() {
  105.         mapConverters = BeanUtils.createCache();
  106.         deregister();
  107.     }

  108.     /**
  109.      * Convert the specified locale-sensitive value into a String.
  110.      *
  111.      * @param value The Value to be converted
  112.      * @return the converted value
  113.      * @throws ConversionException if thrown by an underlying Converter
  114.      */
  115.     public String convert(final Object value) {
  116.         return convert(value, defaultLocale, null);
  117.     }

  118.     /**
  119.      * Convert the specified locale-sensitive value into a String using the particular conversion pattern.
  120.      *
  121.      * @param value   The Value to be converted
  122.      * @param locale  The locale
  123.      * @param pattern The conversion pattern
  124.      * @return the converted value
  125.      * @throws ConversionException if thrown by an underlying Converter
  126.      */
  127.     public String convert(final Object value, final Locale locale, final String pattern) {
  128.         final LocaleConverter<String> converter = lookup(String.class, locale);
  129.         return converter.convert(String.class, value, pattern);
  130.     }

  131.     /**
  132.      * Convert the specified locale-sensitive value into a String using the conversion pattern.
  133.      *
  134.      * @param value   The Value to be converted
  135.      * @param pattern The conversion pattern
  136.      * @return the converted value
  137.      * @throws ConversionException if thrown by an underlying Converter
  138.      */
  139.     public String convert(final Object value, final String pattern) {
  140.         return convert(value, defaultLocale, pattern);
  141.     }

  142.     /**
  143.      * Convert the specified value to an object of the specified class (if possible). Otherwise, return a String representation of the value.
  144.      *
  145.      * @param value The String scalar value to be converted
  146.      * @param clazz The Data type to which this value should be converted.
  147.      * @return the converted value
  148.      * @throws ConversionException if thrown by an underlying Converter
  149.      */
  150.     public Object convert(final String value, final Class<?> clazz) {
  151.         return convert(value, clazz, defaultLocale, null);
  152.     }

  153.     /**
  154.      * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of
  155.      * the value.
  156.      *
  157.      * @param value   The String scalar value to be converted
  158.      * @param clazz   The Data type to which this value should be converted.
  159.      * @param locale  The locale
  160.      * @param pattern The conversion pattern
  161.      * @return the converted value
  162.      * @throws ConversionException if thrown by an underlying Converter
  163.      */
  164.     public Object convert(final String value, final Class<?> clazz, final Locale locale, final String pattern) {
  165.         if (LOG.isDebugEnabled()) {
  166.             LOG.debug("Convert string " + value + " to class " + clazz.getName() + " using " + locale + " locale and " + pattern + " pattern");
  167.         }

  168.         Class<?> targetClass = clazz;
  169.         LocaleConverter converter = lookup(clazz, locale);

  170.         if (converter == null) {
  171.             converter = lookup(String.class, locale);
  172.             targetClass = String.class;
  173.         }
  174.         if (LOG.isTraceEnabled()) {
  175.             LOG.trace("  Using converter " + converter);
  176.         }

  177.         return converter.convert(targetClass, value, pattern);
  178.     }

  179.     /**
  180.      * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of
  181.      * the value.
  182.      *
  183.      * @param value   The String scalar value to be converted
  184.      * @param clazz   The Data type to which this value should be converted.
  185.      * @param pattern The conversion pattern
  186.      * @return the converted value
  187.      * @throws ConversionException if thrown by an underlying Converter
  188.      */
  189.     public Object convert(final String value, final Class<?> clazz, final String pattern) {
  190.         return convert(value, clazz, defaultLocale, pattern);
  191.     }

  192.     /**
  193.      * Convert an array of specified values to an array of objects of the specified class (if possible) .
  194.      *
  195.      * @param values Value to be converted (may be null)
  196.      * @param clazz  Java array or element class to be converted to
  197.      * @return the converted value
  198.      * @throws ConversionException if thrown by an underlying Converter
  199.      */
  200.     public Object convert(final String[] values, final Class<?> clazz) {
  201.         return convert(values, clazz, getDefaultLocale(), null);
  202.     }

  203.     /**
  204.      * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern.
  205.      *
  206.      * @param <T>     The result component type
  207.      * @param values  Value to be converted (may be null)
  208.      * @param clazz   Java array or element class to be converted to
  209.      * @param locale  The locale
  210.      * @param pattern The conversion pattern
  211.      * @return the converted value
  212.      * @throws ConversionException if thrown by an underlying Converter
  213.      */
  214.     public <T> T[] convert(final String[] values, final Class<T> clazz, final Locale locale, final String pattern) {
  215.         Class<?> type = clazz;
  216.         if (clazz.isArray()) {
  217.             type = clazz.getComponentType();
  218.         }
  219.         if (LOG.isDebugEnabled()) {
  220.             LOG.debug("Convert String[" + values.length + "] to class " + type.getName() + "[] using " + locale + " locale and " + pattern + " pattern");
  221.         }

  222.         final T[] array = (T[]) Array.newInstance(type, values.length);
  223.         for (int i = 0; i < values.length; i++) {
  224.             Array.set(array, i, convert(values[i], type, locale, pattern));
  225.         }
  226.         return array;
  227.     }

  228.     /**
  229.      * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern.
  230.      *
  231.      * @param <T>     The array component type
  232.      * @param values  Value to be converted (may be null)
  233.      * @param clazz   Java array or element class to be converted to
  234.      * @param pattern The conversion pattern
  235.      * @return the converted value
  236.      * @throws ConversionException if thrown by an underlying Converter
  237.      */
  238.     public <T> T[] convert(final String[] values, final Class<T> clazz, final String pattern) {
  239.         return convert(values, clazz, getDefaultLocale(), pattern);
  240.     }

  241.     /**
  242.      * Create all {@link LocaleConverter} types for specified locale.
  243.      *
  244.      * @param locale The Locale
  245.      * @return The map instance contains the all {@link LocaleConverter} types for the specified locale.
  246.      */
  247.     protected Map<Class<?>, LocaleConverter<?>> create(final Locale locale) {
  248.         final Map<Class<?>, LocaleConverter<?>> converter = BeanUtils.createCache();

  249.         converter.put(BigDecimal.class, BigDecimalLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  250.         converter.put(BigInteger.class, BigIntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  251.         converter.put(Byte.class, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  252.         converter.put(Byte.TYPE, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  253.         converter.put(Double.class, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  254.         converter.put(Double.TYPE, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  255.         converter.put(Float.class, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  256.         converter.put(Float.TYPE, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  257.         converter.put(Integer.class, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  258.         converter.put(Integer.TYPE, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  259.         converter.put(Long.class, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  260.         converter.put(Long.TYPE, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  261.         converter.put(Short.class, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
  262.         converter.put(Short.TYPE, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  263.         converter.put(String.class, StringLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());

  264.         // conversion format patterns of java.sql.* types should correspond to default
  265.         // behavior of toString and valueOf methods of these classes
  266.         converter.put(java.sql.Date.class, SqlDateLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd").get());
  267.         converter.put(java.sql.Time.class, SqlTimeLocaleConverter.builder().setLocale(locale).setPattern("HH:mm:ss").get());
  268.         converter.put(java.sql.Timestamp.class, SqlTimestampLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd HH:mm:ss.S").get());

  269.         return converter;
  270.     }

  271.     /**
  272.      * Remove any registered {@link LocaleConverter}.
  273.      */
  274.     public void deregister() {
  275.         final Map<Class<?>, LocaleConverter<?>> defaultConverter = lookup(defaultLocale);
  276.         mapConverters.clear();
  277.         mapConverters.put(defaultLocale, defaultConverter);
  278.     }

  279.     /**
  280.      * Remove any registered {@link LocaleConverter} for the specified locale and Class.
  281.      *
  282.      * @param clazz  Class for which to remove a registered Converter
  283.      * @param locale The locale
  284.      */
  285.     public void deregister(final Class<?> clazz, final Locale locale) {
  286.         lookup(locale).remove(clazz);
  287.     }

  288.     /**
  289.      * Remove any registered {@link LocaleConverter} for the specified locale
  290.      *
  291.      * @param locale The locale
  292.      */
  293.     public void deregister(final Locale locale) {
  294.         mapConverters.remove(locale);
  295.     }

  296.     /**
  297.      * getter for applyLocalized
  298.      *
  299.      * @return {@code true} if pattern is localized, otherwise {@code false}
  300.      */
  301.     public boolean getApplyLocalized() {
  302.         return applyLocalized;
  303.     }

  304.     /**
  305.      * getter for defaultLocale.
  306.      *
  307.      * @return the default locale
  308.      */
  309.     public Locale getDefaultLocale() {
  310.         return defaultLocale;
  311.     }

  312.     /**
  313.      * Look up and return any registered {@link LocaleConverter} for the specified destination class and locale; if there is no registered Converter, return
  314.      * {@code null}.
  315.      *
  316.      * @param <T>    The converter type.
  317.      * @param clazz  Class for which to return a registered Converter
  318.      * @param locale The Locale
  319.      * @return The registered locale Converter, if any
  320.      */
  321.     public <T> LocaleConverter<T> lookup(final Class<T> clazz, final Locale locale) {
  322.         final LocaleConverter<T> converter = (LocaleConverter<T>) lookup(locale).get(clazz);

  323.         if (LOG.isTraceEnabled()) {
  324.             LOG.trace("LocaleConverter:" + converter);
  325.         }

  326.         return converter;
  327.     }

  328.     /**
  329.      * Look up and return any registered map instance for the specified locale; if there is no registered one, return {@code null}.
  330.      *
  331.      * @param locale The Locale
  332.      * @return The map instance contains the all {@link LocaleConverter} types for the specified locale.
  333.      */
  334.     protected Map<Class<?>, LocaleConverter<?>> lookup(final Locale locale) {
  335.         return mapConverters.computeIfAbsent(locale == null ? defaultLocale : locale, this::create);
  336.     }

  337.     /**
  338.      * Register a custom {@link LocaleConverter} for the specified destination {@code Class}, replacing any previously registered converter.
  339.      *
  340.      * @param <T>       The converter type.
  341.      * @param converter The LocaleConverter to be registered
  342.      * @param clazz     The Destination class for conversions performed by this Converter
  343.      * @param locale    The locale
  344.      */
  345.     public <T> void register(final LocaleConverter<T> converter, final Class<T> clazz, final Locale locale) {
  346.         lookup(locale).put(clazz, converter);
  347.     }

  348.     /**
  349.      * setter for applyLocalized
  350.      *
  351.      * @param newApplyLocalized {@code true} if pattern is localized, otherwise {@code false}
  352.      */
  353.     public void setApplyLocalized(final boolean newApplyLocalized) {
  354.         applyLocalized = newApplyLocalized;
  355.     }

  356.     /**
  357.      * setter for defaultLocale.
  358.      *
  359.      * @param locale the default locale
  360.      */
  361.     public void setDefaultLocale(final Locale locale) {
  362.         if (locale == null) {
  363.             defaultLocale = Locale.getDefault();
  364.         } else {
  365.             defaultLocale = locale;
  366.         }
  367.     }
  368. }