001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017     
018    package org.apache.commons.beanutils.locale;
019    
020    import org.apache.commons.beanutils.BeanUtils;
021    import org.apache.commons.beanutils.locale.converters.BigDecimalLocaleConverter;
022    import org.apache.commons.beanutils.locale.converters.BigIntegerLocaleConverter;
023    import org.apache.commons.beanutils.locale.converters.ByteLocaleConverter;
024    import org.apache.commons.beanutils.locale.converters.DoubleLocaleConverter;
025    import org.apache.commons.beanutils.locale.converters.FloatLocaleConverter;
026    import org.apache.commons.beanutils.locale.converters.IntegerLocaleConverter;
027    import org.apache.commons.beanutils.locale.converters.LongLocaleConverter;
028    import org.apache.commons.beanutils.locale.converters.ShortLocaleConverter;
029    import org.apache.commons.beanutils.locale.converters.StringLocaleConverter;
030    import org.apache.commons.beanutils.locale.converters.SqlDateLocaleConverter;
031    import org.apache.commons.beanutils.locale.converters.SqlTimeLocaleConverter;
032    import org.apache.commons.beanutils.locale.converters.SqlTimestampLocaleConverter;
033    
034    import org.apache.commons.collections.FastHashMap;
035    import org.apache.commons.logging.Log;
036    import org.apache.commons.logging.LogFactory;
037    
038    import java.lang.reflect.Array;
039    import java.math.BigDecimal;
040    import java.math.BigInteger;
041    import java.util.Collection;
042    import java.util.Locale;
043    import java.util.Map;
044    import java.util.Set;
045    
046    /**
047     * <p>Utility methods for converting locale-sensitive String scalar values to objects of the
048     * specified Class, String arrays to arrays of the specified Class and
049     * object to locale-sensitive String scalar value.</p>
050     *
051     * <p>This class provides the implementations used by the static utility methods in 
052     * {@link LocaleConvertUtils}.</p>
053     * 
054     * <p>The actual {@link LocaleConverter} instance to be used
055     * can be registered for each possible destination Class. Unless you override them, standard
056     * {@link LocaleConverter} instances are provided for all of the following
057     * destination Classes:</p>
058     * <ul>
059     * <li>java.lang.BigDecimal</li>
060     * <li>java.lang.BigInteger</li>
061     * <li>byte and java.lang.Byte</li>
062     * <li>double and java.lang.Double</li>
063     * <li>float and java.lang.Float</li>
064     * <li>int and java.lang.Integer</li>
065     * <li>long and java.lang.Long</li>
066     * <li>short and java.lang.Short</li>
067     * <li>java.lang.String</li>
068     * <li>java.sql.Date</li>
069     * <li>java.sql.Time</li>
070     * <li>java.sql.Timestamp</li>
071     * </ul>
072     *
073     * <p>For backwards compatibility, the standard locale converters
074     * for primitive types (and the corresponding wrapper classes).
075     *
076     * If you prefer to have another {@link LocaleConverter}
077     * thrown instead, replace the standard {@link LocaleConverter} instances
078     * with ones created with the one of the appropriate constructors.
079     *
080     * It's important that {@link LocaleConverter} should be registered for
081     * the specified locale and Class (or primitive type).
082     *
083     * @author Yauheny Mikulski
084     * @since 1.7
085     */
086    public class LocaleConvertUtilsBean {
087        
088        /** 
089         * Gets singleton instance.
090         * This is the same as the instance used by the default {@link LocaleBeanUtilsBean} singleton.
091         * @return the singleton instance
092         */
093        public static LocaleConvertUtilsBean getInstance() {
094            return LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getLocaleConvertUtils();
095        }
096    
097        // ----------------------------------------------------- Instance Variables
098    
099        /** The locale - default for convertion. */
100        private Locale defaultLocale = Locale.getDefault();
101    
102        /** Indicate whether the pattern is localized or not */
103        private boolean applyLocalized = false;
104    
105        /** The <code>Log</code> instance for this class. */
106        private Log log = LogFactory.getLog(LocaleConvertUtils.class);
107    
108        /** Every entry of the mapConverters is:
109         *  key = locale
110         *  value = FastHashMap of converters for the certain locale.
111         */
112        private FastHashMap mapConverters = new DelegateFastHashMap(BeanUtils.createCache());
113    
114        // --------------------------------------------------------- Constructors
115    
116        /**
117         *  Makes the state by default (deregisters all converters for all locales)
118         *  and then registers default locale converters.
119         */
120        public LocaleConvertUtilsBean() {
121            mapConverters.setFast(false);
122            deregister();
123            mapConverters.setFast(true);
124        }
125        
126        // --------------------------------------------------------- Properties
127         
128        /**
129         * getter for defaultLocale.
130         * @return the default locale
131         */
132        public Locale getDefaultLocale() {
133    
134            return defaultLocale;
135        }
136    
137        /**
138         * setter for defaultLocale.
139         * @param locale the default locale
140         */
141        public void setDefaultLocale(Locale locale) {
142    
143            if (locale == null) {
144                defaultLocale = Locale.getDefault();
145            }
146            else {
147                defaultLocale = locale;
148            }
149        }
150        
151        /**
152         * getter for applyLocalized
153         *
154         * @return <code>true</code> if pattern is localized,
155         * otherwise <code>false</code>
156         */
157        public boolean getApplyLocalized() {
158            return applyLocalized;
159        }
160    
161        /**
162         * setter for applyLocalized
163         *
164         * @param newApplyLocalized <code>true</code> if pattern is localized,
165         * otherwise <code>false</code>
166         */
167        public void setApplyLocalized(boolean newApplyLocalized) {
168            applyLocalized = newApplyLocalized;
169        }
170    
171        // --------------------------------------------------------- Methods
172    
173        /**
174         * Convert the specified locale-sensitive value into a String.
175         *
176         * @param value The Value to be converted
177         * @return the converted value
178         *
179         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
180         * underlying Converter
181         */
182        public String convert(Object value) {
183            return convert(value, defaultLocale, null);
184        }
185    
186        /**
187         * Convert the specified locale-sensitive value into a String
188         * using the conversion pattern.
189         *
190         * @param value The Value to be converted
191         * @param pattern       The convertion pattern
192         * @return the converted value
193         *
194         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
195         * underlying Converter
196         */
197        public String convert(Object value, String pattern) {
198            return convert(value, defaultLocale, pattern);
199        }
200    
201        /**
202         * Convert the specified locale-sensitive value into a String
203         * using the paticular convertion pattern.
204         *
205         * @param value The Value to be converted
206         * @param locale The locale
207         * @param pattern The convertion pattern
208         * @return the converted value
209         *
210         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
211         * underlying Converter
212         */
213        public String convert(Object value, Locale locale, String pattern) {
214    
215            LocaleConverter converter = lookup(String.class, locale);
216    
217            return (String) converter.convert(String.class, value, pattern);
218        }
219    
220        /**
221         * Convert the specified value to an object of the specified class (if
222         * possible).  Otherwise, return a String representation of the value.
223         *
224         * @param value The String scalar value to be converted
225         * @param clazz The Data type to which this value should be converted.
226         * @return the converted value
227         *
228         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
229         * underlying Converter
230         */
231        public Object convert(String value, Class clazz) {
232    
233            return convert(value, clazz, defaultLocale, null);
234        }
235    
236        /**
237         * Convert the specified value to an object of the specified class (if
238         * possible) using the convertion pattern. Otherwise, return a String
239         * representation of the value.
240         *
241         * @param value The String scalar value to be converted
242         * @param clazz The Data type to which this value should be converted.
243         * @param pattern The convertion pattern
244         * @return the converted value
245         *
246         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
247         * underlying Converter
248         */
249        public Object convert(String value, Class clazz, String pattern) {
250    
251            return convert(value, clazz, defaultLocale, pattern);
252        }
253    
254        /**
255         * Convert the specified value to an object of the specified class (if
256         * possible) using the convertion pattern. Otherwise, return a String
257         * representation of the value.
258         *
259         * @param value The String scalar value to be converted
260         * @param clazz The Data type to which this value should be converted.
261         * @param locale The locale
262         * @param pattern The convertion pattern
263         * @return the converted value
264         *
265         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
266         * underlying Converter
267         */
268        public Object convert(String value, Class clazz, Locale locale, String pattern) {
269    
270            if (log.isDebugEnabled()) {
271                log.debug("Convert string " + value + " to class " +
272                        clazz.getName() + " using " + locale +
273                        " locale and " + pattern + " pattern");
274            }
275    
276            LocaleConverter converter = lookup(clazz, locale);
277    
278            if (converter == null) {
279                converter = lookup(String.class, locale);
280            }
281            if (log.isTraceEnabled()) {
282                log.trace("  Using converter " + converter);
283            }
284    
285            return (converter.convert(clazz, value, pattern));
286        }
287    
288        /**
289         * Convert an array of specified values to an array of objects of the
290         * specified class (if possible) using the convertion pattern.
291         *
292         * @param values Value to be converted (may be null)
293         * @param clazz Java array or element class to be converted to
294         * @param pattern The convertion pattern
295         * @return the converted value
296         *
297         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
298         * underlying Converter
299         */
300        public Object convert(String[] values, Class clazz, String pattern) {
301    
302            return convert(values, clazz, getDefaultLocale(), pattern);
303        }
304    
305       /**
306        * Convert an array of specified values to an array of objects of the
307        * specified class (if possible) .
308        *
309        * @param values Value to be converted (may be null)
310        * @param clazz Java array or element class to be converted to
311        * @return the converted value
312        *
313         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
314         * underlying Converter
315        */
316       public Object convert(String[] values, Class clazz) {
317    
318           return convert(values, clazz, getDefaultLocale(), null);
319       }
320    
321        /**
322         * Convert an array of specified values to an array of objects of the
323         * specified class (if possible) using the convertion pattern.
324         *
325         * @param values Value to be converted (may be null)
326         * @param clazz Java array or element class to be converted to
327         * @param locale The locale
328         * @param pattern The convertion pattern
329         * @return the converted value
330         *
331         * @throws org.apache.commons.beanutils.ConversionException if thrown by an
332         * underlying Converter
333         */
334        public Object convert(String[] values, Class clazz, Locale locale, String pattern) {
335    
336            Class type = clazz;
337            if (clazz.isArray()) {
338                type = clazz.getComponentType();
339            }
340            if (log.isDebugEnabled()) {
341                log.debug("Convert String[" + values.length + "] to class " +
342                        type.getName() + "[] using " + locale +
343                        " locale and " + pattern + " pattern");
344            }
345    
346            Object array = Array.newInstance(type, values.length);
347            for (int i = 0; i < values.length; i++) {
348                Array.set(array, i, convert(values[i], type, locale, pattern));
349            }
350    
351            return (array);
352        }
353    
354        /**
355         * Register a custom {@link LocaleConverter} for the specified destination
356         * <code>Class</code>, replacing any previously registered converter.
357         *
358         * @param converter The LocaleConverter to be registered
359         * @param clazz The Destination class for conversions performed by this
360         *  Converter
361         * @param locale The locale
362         */
363        public void register(LocaleConverter converter, Class clazz, Locale locale) {
364    
365            lookup(locale).put(clazz, converter);
366        }
367    
368        /**
369         * Remove any registered {@link LocaleConverter}.
370         */
371        public void deregister() {
372    
373            FastHashMap defaultConverter = lookup(defaultLocale);
374    
375            mapConverters.setFast(false);
376    
377            mapConverters.clear();
378            mapConverters.put(defaultLocale, defaultConverter);
379    
380            mapConverters.setFast(true);
381        }
382    
383    
384        /**
385         * Remove any registered {@link LocaleConverter} for the specified locale
386         *
387         * @param locale The locale
388         */
389        public void deregister(Locale locale) {
390    
391            mapConverters.remove(locale);
392        }
393    
394    
395        /**
396         * Remove any registered {@link LocaleConverter} for the specified locale and Class.
397         *
398         * @param clazz Class for which to remove a registered Converter
399         * @param locale The locale
400         */
401        public void deregister(Class clazz, Locale locale) {
402    
403            lookup(locale).remove(clazz);
404        }
405    
406        /**
407         * Look up and return any registered {@link LocaleConverter} for the specified
408         * destination class and locale; if there is no registered Converter, return
409         * <code>null</code>.
410         *
411         * @param clazz Class for which to return a registered Converter
412         * @param locale The Locale
413         * @return The registered locale Converter, if any
414         */
415        public LocaleConverter lookup(Class clazz, Locale locale) {
416    
417            LocaleConverter converter = (LocaleConverter) lookup(locale).get(clazz);
418            
419            if (log.isTraceEnabled()) {
420                log.trace("LocaleConverter:" + converter);
421            }
422            
423            return converter;
424        }
425    
426        /**
427         * Look up and return any registered FastHashMap instance for the specified locale;
428         * if there is no registered one, return <code>null</code>.
429         *
430         * @param locale The Locale
431         * @return The FastHashMap instance contains the all {@link LocaleConverter} types for
432         *  the specified locale.
433         * @deprecated This method will be modified to return a Map in the next release.
434         */
435        protected FastHashMap lookup(Locale locale) {
436            FastHashMap localeConverters;
437    
438            if (locale == null) {
439                localeConverters = (FastHashMap) mapConverters.get(defaultLocale);
440            }
441            else {
442                localeConverters = (FastHashMap) mapConverters.get(locale);
443    
444                if (localeConverters == null) {
445                    localeConverters = create(locale);
446                    mapConverters.put(locale, localeConverters);
447                }
448            }
449    
450            return localeConverters;
451        }
452    
453        /**
454         *  Create all {@link LocaleConverter} types for specified locale.
455         *
456         * @param locale The Locale
457         * @return The FastHashMap instance contains the all {@link LocaleConverter} types
458         *  for the specified locale.
459         * @deprecated This method will be modified to return a Map in the next release.
460         */
461        protected FastHashMap create(Locale locale) {
462    
463            FastHashMap converter = new DelegateFastHashMap(BeanUtils.createCache());
464            converter.setFast(false);
465    
466            converter.put(BigDecimal.class, new BigDecimalLocaleConverter(locale, applyLocalized));
467            converter.put(BigInteger.class, new BigIntegerLocaleConverter(locale, applyLocalized));
468    
469            converter.put(Byte.class, new ByteLocaleConverter(locale, applyLocalized));
470            converter.put(Byte.TYPE, new ByteLocaleConverter(locale, applyLocalized));
471    
472            converter.put(Double.class, new DoubleLocaleConverter(locale, applyLocalized));
473            converter.put(Double.TYPE, new DoubleLocaleConverter(locale, applyLocalized));
474    
475            converter.put(Float.class, new FloatLocaleConverter(locale, applyLocalized));
476            converter.put(Float.TYPE, new FloatLocaleConverter(locale, applyLocalized));
477    
478            converter.put(Integer.class, new IntegerLocaleConverter(locale, applyLocalized));
479            converter.put(Integer.TYPE, new IntegerLocaleConverter(locale, applyLocalized));
480    
481            converter.put(Long.class, new LongLocaleConverter(locale, applyLocalized));
482            converter.put(Long.TYPE, new LongLocaleConverter(locale, applyLocalized));
483    
484            converter.put(Short.class, new ShortLocaleConverter(locale, applyLocalized));
485            converter.put(Short.TYPE, new ShortLocaleConverter(locale, applyLocalized));
486    
487            converter.put(String.class, new StringLocaleConverter(locale, applyLocalized));
488    
489            // conversion format patterns of java.sql.* types should correspond to default
490            // behaviour of toString and valueOf methods of these classes
491            converter.put(java.sql.Date.class, new SqlDateLocaleConverter(locale, "yyyy-MM-dd"));
492            converter.put(java.sql.Time.class, new SqlTimeLocaleConverter(locale, "HH:mm:ss"));
493            converter.put( java.sql.Timestamp.class,
494                           new SqlTimestampLocaleConverter(locale, "yyyy-MM-dd HH:mm:ss.S")
495                         );
496    
497            converter.setFast(true);
498    
499            return converter;
500        }
501    
502        /**
503         * FastHashMap implementation that uses WeakReferences to overcome
504         * memory leak problems.
505         *
506         * This is a hack to retain binary compatibility with previous
507         * releases (where FastHashMap is exposed in the API), but
508         * use WeakHashMap to resolve memory leaks.
509         */
510        private static class DelegateFastHashMap extends FastHashMap {
511    
512            private final Map map;
513    
514            private DelegateFastHashMap(Map map) {
515                this.map = map;
516            }
517            public void clear() {
518                map.clear();
519            }
520            public boolean containsKey(Object key) {
521                return map.containsKey(key);
522            }
523            public boolean containsValue(Object value) {
524                return map.containsValue(value);
525            }
526            public Set entrySet() {
527                return map.entrySet();
528            }
529            public boolean equals(Object o) {
530                return map.equals(o);
531            }
532            public Object get(Object key) {
533                return map.get(key);
534            }
535            public int hashCode() {
536                return map.hashCode();
537            }
538            public boolean isEmpty() {
539                return map.isEmpty();
540            }
541            public Set keySet() {
542                return map.keySet();
543            }
544            public Object put(Object key, Object value) {
545                return map.put(key, value);
546            }
547            public void putAll(Map m) {
548                map.putAll(m);
549            }
550            public Object remove(Object key) {
551                return map.remove(key);
552            }
553            public int size() {
554                return map.size();
555            }
556            public Collection values() {
557                return map.values();
558            }
559            public boolean getFast() {
560                return BeanUtils.getCacheFast(map);
561            }
562            public void setFast(boolean fast) {
563                BeanUtils.setCacheFast(map, fast);
564            }
565        }
566    }