    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.converters;
    019
    020    import org.apache.commons.beanutils.ConversionException;
    021    import org.apache.commons.beanutils.locale.BaseLocaleConverter;
    022    import org.apache.commons.logging.LogFactory;
    023    import org.apache.commons.logging.Log;
    024
    025    import java.text.ParseException;
    026    import java.text.ParsePosition;
    027    import java.text.SimpleDateFormat;
    028    import java.text.DateFormat;
    029    import java.text.DateFormatSymbols;
    030    import java.util.Locale;
    031
    032
    033    /**
    034     * <p>Standard {@link org.apache.commons.beanutils.locale.LocaleConverter}
    035     * implementation that converts an incoming
    036     * locale-sensitive String into a <code>java.util.Date</code> object,
    037     * optionally using a default value or throwing a
    038     * {@link org.apache.commons.beanutils.ConversionException}
    039     * if a conversion error occurs.</p>
    040     *
    041     * @author Yauheny Mikulski
    042     * @author Michael Szlapa
    043     */
    044
    045    public class DateLocaleConverter extends BaseLocaleConverter {
    046
    047        // ----------------------------------------------------- Instance Variables
    048
    049        /** All logging goes through this logger */
    050        private Log log = LogFactory.getLog(DateLocaleConverter.class);
    051
    052        /** Should the date conversion be lenient? */
    053        boolean isLenient = false;
    054
    055        /**
    056         * Default Pattern Characters
    057         *
    058         */
    059        private static final String DEFAULT_PATTERN_CHARS = DateLocaleConverter.initDefaultChars();
    060
    061        // ----------------------------------------------------------- Constructors
    062
    063        /**
    064         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    065         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    066         * if a conversion error occurs. The locale is the default locale for
    067         * this instance of the Java Virtual Machine and an unlocalized pattern is used
    068         * for the convertion.
    069         *
    070         */
    071        public DateLocaleConverter() {
    072
    073            this(false);
    074        }
    075
    076        /**
    077         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    078         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    079         * if a conversion error occurs. The locale is the default locale for
    080         * this instance of the Java Virtual Machine.
    081         *
    082         * @param locPattern    Indicate whether the pattern is localized or not
    083         */
    084        public DateLocaleConverter(boolean locPattern) {
    085
    086            this(Locale.getDefault(), locPattern);
    087        }
    088
    089        /**
    090         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    091         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    092         * if a conversion error occurs. An unlocalized pattern is used for the convertion.
    093         *
    094         * @param locale        The locale
    095         */
    096        public DateLocaleConverter(Locale locale) {
    097
    098            this(locale, false);
    099        }
    100
    101        /**
    102         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    103         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    104         * if a conversion error occurs.
    105         *
    106         * @param locale        The locale
    107         * @param locPattern    Indicate whether the pattern is localized or not
    108         */
    109        public DateLocaleConverter(Locale locale, boolean locPattern) {
    110
    111            this(locale, (String) null, locPattern);
    112        }
    113
    114        /**
    115         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    116         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    117         * if a conversion error occurs. An unlocalized pattern is used for the convertion.
    118         *
    119         * @param locale        The locale
    120         * @param pattern       The convertion pattern
    121         */
    122        public DateLocaleConverter(Locale locale, String pattern) {
    123
    124            this(locale, pattern, false);
    125        }
    126
    127        /**
    128         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    129         * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    130         * if a conversion error occurs.
    131         *
    132         * @param locale        The locale
    133         * @param pattern       The convertion pattern
    134         * @param locPattern    Indicate whether the pattern is localized or not
    135         */
    136        public DateLocaleConverter(Locale locale, String pattern, boolean locPattern) {
    137
    138            super(locale, pattern, locPattern);
    139        }
    140
    141        /**
    142         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    143         * that will return the specified default value
    144         * if a conversion error occurs. The locale is the default locale for
    145         * this instance of the Java Virtual Machine and an unlocalized pattern is used
    146         * for the convertion.
    147         *
    148         * @param defaultValue  The default value to be returned
    149         */
    150        public DateLocaleConverter(Object defaultValue) {
    151
    152            this(defaultValue, false);
    153        }
    154
    155        /**
    156         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    157         * that will return the specified default value
    158         * if a conversion error occurs. The locale is the default locale for
    159         * this instance of the Java Virtual Machine.
    160         *
    161         * @param defaultValue  The default value to be returned
    162         * @param locPattern    Indicate whether the pattern is localized or not
    163         */
    164        public DateLocaleConverter(Object defaultValue, boolean locPattern) {
    165
    166            this(defaultValue, Locale.getDefault(), locPattern);
    167        }
    168
    169        /**
    170         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    171         * that will return the specified default value
    172         * if a conversion error occurs. An unlocalized pattern is used for the convertion.
    173         *
    174         * @param defaultValue  The default value to be returned
    175         * @param locale        The locale
    176         */
    177        public DateLocaleConverter(Object defaultValue, Locale locale) {
    178
    179            this(defaultValue, locale, false);
    180        }
    181
    182        /**
    183         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    184         * that will return the specified default value
    185         * if a conversion error occurs.
    186         *
    187         * @param defaultValue  The default value to be returned
    188         * @param locale        The locale
    189         * @param locPattern    Indicate whether the pattern is localized or not
    190         */
    191        public DateLocaleConverter(Object defaultValue, Locale locale, boolean locPattern) {
    192
    193            this(defaultValue, locale, null, locPattern);
    194        }
    195
    196
    197        /**
    198         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    199         * that will return the specified default value
    200         * if a conversion error occurs. An unlocalized pattern is used for the convertion.
    201         *
    202         * @param defaultValue  The default value to be returned
    203         * @param locale        The locale
    204         * @param pattern       The convertion pattern
    205         */
    206        public DateLocaleConverter(Object defaultValue, Locale locale, String pattern) {
    207
    208            this(defaultValue, locale, pattern, false);
    209        }
    210
    211        /**
    212         * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    213         * that will return the specified default value
    214         * if a conversion error occurs.
    215         *
    216         * @param defaultValue  The default value to be returned
    217         * @param locale        The locale
    218         * @param pattern       The convertion pattern
    219         * @param locPattern    Indicate whether the pattern is localized or not
    220         */
    221        public DateLocaleConverter(Object defaultValue, Locale locale, String pattern, boolean locPattern) {
    222
    223            super(defaultValue, locale, pattern, locPattern);
    224        }
    225
    226        // --------------------------------------------------------- Methods
    227
    228        /**
    229         * Returns whether date formatting is lenient.
    230         *
    231         * @return true if the <code>DateFormat</code> used for formatting is lenient
    232         * @see java.text.DateFormat#isLenient
    233         */
    234        public boolean isLenient() {
    235            return isLenient;
    236        }
    237
    238        /**
    239         * Specify whether or not date-time parsing should be lenient.
    240         *
    241         * @param lenient true if the <code>DateFormat</code> used for formatting should be lenient
    242         * @see java.text.DateFormat#setLenient
    243         */
    244        public void setLenient(boolean lenient) {
    245            isLenient = lenient;
    246        }
    247
    248        // --------------------------------------------------------- Methods
    249
    250        /**
    251         * Convert the specified locale-sensitive input object into an output object of the
    252         * specified type.
    253         *
    254         * @param value The input object to be converted
    255         * @param pattern The pattern is used for the convertion
    256         * @return the converted Date value
    257         *
    258         * @exception org.apache.commons.beanutils.ConversionException
    259         * if conversion cannot be performed successfully
    260         * @throws ParseException if an error occurs parsing
    261         */
    262        protected Object parse(Object value, String pattern) throws ParseException {
    263
    264            // Handle Date
    265            if (value instanceof java.util.Date) {
    266                return value;
    267            }
    268
    269            // Handle Calendar
    270            if (value instanceof java.util.Calendar) {
    271                return ((java.util.Calendar)value).getTime();
    272            }
    273
    274             if (locPattern) {
    275                 pattern = convertLocalizedPattern(pattern, locale);
    276             }
    277
    278             // Create Formatter - use default if pattern is null
    279             DateFormat formatter = pattern == null ? DateFormat.getDateInstance(DateFormat.SHORT, locale)
    280                                                    : new SimpleDateFormat(pattern, locale);
    281             formatter.setLenient(isLenient);
    282
    283
    284             // Parse the Date
    285            ParsePosition pos = new ParsePosition(0);
    286            String strValue = value.toString();
    287            Object parsedValue = formatter.parseObject(strValue, pos);
    288            if (pos.getErrorIndex() > -1) {
    289                throw new ConversionException("Error parsing date '" + value +
    290                        "' at position="+ pos.getErrorIndex());
    291            }
    292            if (pos.getIndex() < strValue.length()) {
    293                throw new ConversionException("Date '" + value +
    294                        "' contains unparsed characters from position=" + pos.getIndex());
    295            }
    296
    297            return parsedValue;
    298         }
    299
    300         /**
    301          * Convert a pattern from a localized format to the default format.
    302          *
    303          * @param locale   The locale
    304          * @param localizedPattern The pattern in 'local' symbol format
    305          * @return pattern in 'default' symbol format
    306          */
    307         private String convertLocalizedPattern(String localizedPattern, Locale locale) {
    308
    309             if (localizedPattern == null) {
    310                return null;
    311             }
    312
    313             // Note that this is a little obtuse.
    314             // However, it is the best way that anyone can come up with
    315             // that works with some 1.4 series JVM.
    316
    317             // Get the symbols for the localized pattern
    318             DateFormatSymbols localizedSymbols = new DateFormatSymbols(locale);
    319             String localChars = localizedSymbols.getLocalPatternChars();
    320
    321             if (DEFAULT_PATTERN_CHARS.equals(localChars)) {
    322                 return localizedPattern;
    323             }
    324
    325             // Convert the localized pattern to default
    326             String convertedPattern = null;
    327             try {
    328                 convertedPattern = convertPattern(localizedPattern,
    329                                                    localChars,
    330                                                    DEFAULT_PATTERN_CHARS);
    331             } catch (Exception ex) {
    332                 log.debug("Converting pattern '" + localizedPattern + "' for " + locale, ex);
    333             }
    334             return convertedPattern;
    335        }
    336
    337        /**
    338         * <p>Converts a Pattern from one character set to another.</p>
    339         */
    340        private String convertPattern(String pattern, String fromChars, String toChars) {
    341
    342            StringBuffer converted = new StringBuffer();
    343            boolean quoted = false;
    344
    345            for (int i = 0; i < pattern.length(); ++i) {
    346                char thisChar = pattern.charAt(i);
    347                if (quoted) {
    348                    if (thisChar == '\'') {
    349                        quoted = false;
    350                    }
    351                } else {
    352                    if (thisChar == '\'') {
    353                       quoted = true;
    354                    } else if ((thisChar >= 'a' && thisChar <= 'z') ||
    355                               (thisChar >= 'A' && thisChar <= 'Z')) {
    356                        int index = fromChars.indexOf(thisChar );
    357                        if (index == -1) {
    358                            throw new IllegalArgumentException(
    359                                "Illegal pattern character '" + thisChar + "'");
    360                        }
    361                        thisChar = toChars.charAt(index);
    362                    }
    363                }
    364                converted.append(thisChar);
    365            }
    366
    367            if (quoted) {
    368                throw new IllegalArgumentException("Unfinished quote in pattern");
    369            }
    370
    371            return converted.toString();
    372        }
    373
    374        /**
    375         * This method is called at class initialization time to define the
    376         * value for constant member DEFAULT_PATTERN_CHARS. All other methods needing
    377         * this data should just read that constant.
    378         */
    379        private static String initDefaultChars() {
    380            DateFormatSymbols defaultSymbols = new DateFormatSymbols(Locale.US);
    381            return defaultSymbols.getLocalPatternChars();
    382        }
    383
    384    }