View Javadoc
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  
18  package org.apache.commons.beanutils2.locale;
19  
20  import java.lang.reflect.Array;
21  import java.math.BigDecimal;
22  import java.math.BigInteger;
23  import java.util.Locale;
24  import java.util.Map;
25  
26  import org.apache.commons.beanutils2.BeanUtils;
27  import org.apache.commons.beanutils2.ConversionException;
28  import org.apache.commons.beanutils2.locale.converters.BigDecimalLocaleConverter;
29  import org.apache.commons.beanutils2.locale.converters.BigIntegerLocaleConverter;
30  import org.apache.commons.beanutils2.locale.converters.ByteLocaleConverter;
31  import org.apache.commons.beanutils2.locale.converters.DoubleLocaleConverter;
32  import org.apache.commons.beanutils2.locale.converters.FloatLocaleConverter;
33  import org.apache.commons.beanutils2.locale.converters.IntegerLocaleConverter;
34  import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter;
35  import org.apache.commons.beanutils2.locale.converters.ShortLocaleConverter;
36  import org.apache.commons.beanutils2.locale.converters.StringLocaleConverter;
37  import org.apache.commons.beanutils2.sql.converters.locale.SqlDateLocaleConverter;
38  import org.apache.commons.beanutils2.sql.converters.locale.SqlTimeLocaleConverter;
39  import org.apache.commons.beanutils2.sql.converters.locale.SqlTimestampLocaleConverter;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  /**
44   * <p>
45   * Utility methods for converting locale-sensitive String scalar values to objects of the specified Class, String arrays to arrays of the specified Class and
46   * object to locale-sensitive String scalar value.
47   * </p>
48   *
49   * <p>
50   * This class provides the implementations used by the static utility methods in {@link LocaleConvertUtils}.
51   * </p>
52   *
53   * <p>
54   * The actual {@link LocaleConverter} instance to be used can be registered for each possible destination Class. Unless you override them, standard
55   * {@link LocaleConverter} instances are provided for all of the following destination Classes:
56   * </p>
57   * <ul>
58   * <li>java.lang.BigDecimal</li>
59   * <li>java.lang.BigInteger</li>
60   * <li>byte and java.lang.Byte</li>
61   * <li>double and java.lang.Double</li>
62   * <li>float and java.lang.Float</li>
63   * <li>int and java.lang.Integer</li>
64   * <li>long and java.lang.Long</li>
65   * <li>short and java.lang.Short</li>
66   * <li>java.lang.String</li>
67   * <li>java.sql.Date</li>
68   * <li>java.sql.Time</li>
69   * <li>java.sql.Timestamp</li>
70   * </ul>
71   *
72   * <p>
73   * For backwards compatibility, the standard locale converters for primitive types (and the corresponding wrapper classes).
74   *
75   * If you prefer to have another {@link LocaleConverter} thrown instead, replace the standard {@link LocaleConverter} instances with ones created with the one
76   * of the appropriate constructors.
77   *
78   * It's important that {@link LocaleConverter} should be registered for the specified locale and Class (or primitive type).
79   *
80   * @since 1.7
81   */
82  public class LocaleConvertUtilsBean {
83  
84      /** The {@code Log} instance for this class. */
85      private static final Log LOG = LogFactory.getLog(LocaleConvertUtilsBean.class);
86  
87      /**
88       * Gets singleton instance. This is the same as the instance used by the default {@link LocaleBeanUtilsBean} singleton.
89       *
90       * @return the singleton instance
91       */
92      public static LocaleConvertUtilsBean getInstance() {
93          return LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getLocaleConvertUtils();
94      }
95  
96      /** The locale - default for conversion. */
97      private Locale defaultLocale = Locale.getDefault();
98  
99      /** Indicate whether the pattern is localized or not */
100     private boolean applyLocalized;
101 
102     /**
103      * Every entry of the mapConverters is:
104      * <ul>
105      * <li>key = locale</li>
106      * <li>value = map of converters for the certain locale.</li>
107      * <ul>
108      */
109     private final Map<Locale, Map<Class<?>, LocaleConverter<?>>> mapConverters;
110 
111     /**
112      * Makes the state by default (deregisters all converters for all locales) and then registers default locale converters.
113      */
114     public LocaleConvertUtilsBean() {
115         mapConverters = BeanUtils.createCache();
116         deregister();
117     }
118 
119     /**
120      * Convert the specified locale-sensitive value into a String.
121      *
122      * @param value The Value to be converted
123      * @return the converted value
124      * @throws ConversionException if thrown by an underlying Converter
125      */
126     public String convert(final Object value) {
127         return convert(value, defaultLocale, null);
128     }
129 
130     /**
131      * Convert the specified locale-sensitive value into a String using the particular conversion pattern.
132      *
133      * @param value   The Value to be converted
134      * @param locale  The locale
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 Locale locale, final String pattern) {
140         final LocaleConverter<String> converter = lookup(String.class, locale);
141         return converter.convert(String.class, value, pattern);
142     }
143 
144     /**
145      * Convert the specified locale-sensitive value into a String using the conversion pattern.
146      *
147      * @param value   The Value to be converted
148      * @param pattern The conversion pattern
149      * @return the converted value
150      * @throws ConversionException if thrown by an underlying Converter
151      */
152     public String convert(final Object value, final String pattern) {
153         return convert(value, defaultLocale, pattern);
154     }
155 
156     /**
157      * Convert the specified value to an object of the specified class (if possible). Otherwise, return a String representation of the value.
158      *
159      * @param value The String scalar value to be converted
160      * @param clazz The Data type to which this value should be converted.
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) {
165         return convert(value, clazz, defaultLocale, null);
166     }
167 
168     /**
169      * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of
170      * the value.
171      *
172      * @param value   The String scalar value to be converted
173      * @param clazz   The Data type to which this value should be converted.
174      * @param locale  The locale
175      * @param pattern The conversion pattern
176      * @return the converted value
177      * @throws ConversionException if thrown by an underlying Converter
178      */
179     public Object convert(final String value, final Class<?> clazz, final Locale locale, final String pattern) {
180         if (LOG.isDebugEnabled()) {
181             LOG.debug("Convert string " + value + " to class " + clazz.getName() + " using " + locale + " locale and " + pattern + " pattern");
182         }
183 
184         Class<?> targetClass = clazz;
185         LocaleConverter converter = lookup(clazz, locale);
186 
187         if (converter == null) {
188             converter = lookup(String.class, locale);
189             targetClass = String.class;
190         }
191         if (LOG.isTraceEnabled()) {
192             LOG.trace("  Using converter " + converter);
193         }
194 
195         return converter.convert(targetClass, value, pattern);
196     }
197 
198     /**
199      * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of
200      * the value.
201      *
202      * @param value   The String scalar value to be converted
203      * @param clazz   The Data type to which this value should be converted.
204      * @param pattern The conversion pattern
205      * @return the converted value
206      * @throws ConversionException if thrown by an underlying Converter
207      */
208     public Object convert(final String value, final Class<?> clazz, final String pattern) {
209         return convert(value, clazz, defaultLocale, pattern);
210     }
211 
212     /**
213      * Convert an array of specified values to an array of objects of the specified class (if possible) .
214      *
215      * @param values Value to be converted (may be null)
216      * @param clazz  Java array or element class to be converted to
217      * @return the converted value
218      * @throws ConversionException if thrown by an underlying Converter
219      */
220     public Object convert(final String[] values, final Class<?> clazz) {
221         return convert(values, clazz, getDefaultLocale(), null);
222     }
223 
224     /**
225      * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern.
226      *
227      * @param <T>     The result component type
228      * @param values  Value to be converted (may be null)
229      * @param clazz   Java array or element class to be converted to
230      * @param locale  The locale
231      * @param pattern The conversion pattern
232      * @return the converted value
233      * @throws ConversionException if thrown by an underlying Converter
234      */
235     public <T> T[] convert(final String[] values, final Class<T> clazz, final Locale locale, final String pattern) {
236         Class<?> type = clazz;
237         if (clazz.isArray()) {
238             type = clazz.getComponentType();
239         }
240         if (LOG.isDebugEnabled()) {
241             LOG.debug("Convert String[" + values.length + "] to class " + type.getName() + "[] using " + locale + " locale and " + pattern + " pattern");
242         }
243 
244         final T[] array = (T[]) Array.newInstance(type, values.length);
245         for (int i = 0; i < values.length; i++) {
246             Array.set(array, i, convert(values[i], type, locale, pattern));
247         }
248         return array;
249     }
250 
251     /**
252      * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern.
253      *
254      * @param <T>     The array component type
255      * @param values  Value to be converted (may be null)
256      * @param clazz   Java array or element class to be converted to
257      * @param pattern The conversion pattern
258      * @return the converted value
259      * @throws ConversionException if thrown by an underlying Converter
260      */
261     public <T> T[] convert(final String[] values, final Class<T> clazz, final String pattern) {
262         return convert(values, clazz, getDefaultLocale(), pattern);
263     }
264 
265     /**
266      * Create all {@link LocaleConverter} types for specified locale.
267      *
268      * @param locale The Locale
269      * @return The map instance contains the all {@link LocaleConverter} types for the specified locale.
270      */
271     protected Map<Class<?>, LocaleConverter<?>> create(final Locale locale) {
272         final Map<Class<?>, LocaleConverter<?>> converter = BeanUtils.createCache();
273 
274         converter.put(BigDecimal.class, BigDecimalLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
275         converter.put(BigInteger.class, BigIntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
276 
277         converter.put(Byte.class, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
278         converter.put(Byte.TYPE, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
279 
280         converter.put(Double.class, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
281         converter.put(Double.TYPE, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
282 
283         converter.put(Float.class, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
284         converter.put(Float.TYPE, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
285 
286         converter.put(Integer.class, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
287         converter.put(Integer.TYPE, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
288 
289         converter.put(Long.class, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
290         converter.put(Long.TYPE, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
291 
292         converter.put(Short.class, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
293         converter.put(Short.TYPE, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
294 
295         converter.put(String.class, StringLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get());
296 
297         // conversion format patterns of java.sql.* types should correspond to default
298         // behavior of toString and valueOf methods of these classes
299         converter.put(java.sql.Date.class, SqlDateLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd").get());
300         converter.put(java.sql.Time.class, SqlTimeLocaleConverter.builder().setLocale(locale).setPattern("HH:mm:ss").get());
301         converter.put(java.sql.Timestamp.class, SqlTimestampLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd HH:mm:ss.S").get());
302 
303         return converter;
304     }
305 
306     /**
307      * Remove any registered {@link LocaleConverter}.
308      */
309     public void deregister() {
310         final Map<Class<?>, LocaleConverter<?>> defaultConverter = lookup(defaultLocale);
311         mapConverters.clear();
312         mapConverters.put(defaultLocale, defaultConverter);
313     }
314 
315     /**
316      * Remove any registered {@link LocaleConverter} for the specified locale and Class.
317      *
318      * @param clazz  Class for which to remove a registered Converter
319      * @param locale The locale
320      */
321     public void deregister(final Class<?> clazz, final Locale locale) {
322         lookup(locale).remove(clazz);
323     }
324 
325     /**
326      * Remove any registered {@link LocaleConverter} for the specified locale
327      *
328      * @param locale The locale
329      */
330     public void deregister(final Locale locale) {
331         mapConverters.remove(locale);
332     }
333 
334     /**
335      * getter for applyLocalized
336      *
337      * @return {@code true} if pattern is localized, otherwise {@code false}
338      */
339     public boolean getApplyLocalized() {
340         return applyLocalized;
341     }
342 
343     /**
344      * getter for defaultLocale.
345      *
346      * @return the default locale
347      */
348     public Locale getDefaultLocale() {
349         return defaultLocale;
350     }
351 
352     /**
353      * Look up and return any registered {@link LocaleConverter} for the specified destination class and locale; if there is no registered Converter, return
354      * {@code null}.
355      *
356      * @param <T>    The converter type.
357      * @param clazz  Class for which to return a registered Converter
358      * @param locale The Locale
359      * @return The registered locale Converter, if any
360      */
361     public <T> LocaleConverter<T> lookup(final Class<T> clazz, final Locale locale) {
362         final LocaleConverter<T> converter = (LocaleConverter<T>) lookup(locale).get(clazz);
363 
364         if (LOG.isTraceEnabled()) {
365             LOG.trace("LocaleConverter:" + converter);
366         }
367 
368         return converter;
369     }
370 
371     /**
372      * Look up and return any registered map instance for the specified locale; if there is no registered one, return {@code null}.
373      *
374      * @param locale The Locale
375      * @return The map instance contains the all {@link LocaleConverter} types for the specified locale.
376      */
377     protected Map<Class<?>, LocaleConverter<?>> lookup(final Locale locale) {
378         return mapConverters.computeIfAbsent(locale == null ? defaultLocale : locale, this::create);
379     }
380 
381     /**
382      * Register a custom {@link LocaleConverter} for the specified destination {@code Class}, replacing any previously registered converter.
383      *
384      * @param <T>       The converter type.
385      * @param converter The LocaleConverter to be registered
386      * @param clazz     The Destination class for conversions performed by this Converter
387      * @param locale    The locale
388      */
389     public <T> void register(final LocaleConverter<T> converter, final Class<T> clazz, final Locale locale) {
390         lookup(locale).put(clazz, converter);
391     }
392 
393     /**
394      * setter for applyLocalized
395      *
396      * @param newApplyLocalized {@code true} if pattern is localized, otherwise {@code false}
397      */
398     public void setApplyLocalized(final boolean newApplyLocalized) {
399         applyLocalized = newApplyLocalized;
400     }
401 
402     /**
403      * setter for defaultLocale.
404      *
405      * @param locale the default locale
406      */
407     public void setDefaultLocale(final Locale locale) {
408         if (locale == null) {
409             defaultLocale = Locale.getDefault();
410         } else {
411             defaultLocale = locale;
412         }
413     }
414 }