    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    }