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.beanutils.locale.converters;
    19
    20  import org.apache.commons.beanutils.ConversionException;
    21  import org.apache.commons.beanutils.locale.BaseLocaleConverter;
    22  import org.apache.commons.logging.LogFactory;
    23  import org.apache.commons.logging.Log;
    24
    25  import java.text.ParseException;
    26  import java.text.ParsePosition;
    27  import java.text.SimpleDateFormat;
    28  import java.text.DateFormat;
    29  import java.text.DateFormatSymbols;
    30  import java.util.Locale;
    31
    32
    33  /***
    34   * <p>Standard {@link org.apache.commons.beanutils.locale.LocaleConverter}
    35   * implementation that converts an incoming
    36   * locale-sensitive String into a <code>java.util.Date</code> object,
    37   * optionally using a default value or throwing a
    38   * {@link org.apache.commons.beanutils.ConversionException}
    39   * if a conversion error occurs.</p>
    40   *
    41   * @author Yauheny Mikulski
    42   * @author Michael Szlapa
    43   */
    44
    45  public class DateLocaleConverter extends BaseLocaleConverter {
    46
    47      // ----------------------------------------------------- Instance Variables
    48
    49      /*** All logging goes through this logger */
    50      private Log log = LogFactory.getLog(DateLocaleConverter.class);
    51
    52      /*** Should the date conversion be lenient? */
    53      boolean isLenient = false;
    54
    55      /***
    56       * Default Pattern Characters
    57       *
    58       */
    59      private static final String DEFAULT_PATTERN_CHARS = DateLocaleConverter.initDefaultChars();
    60
    61      // ----------------------------------------------------------- Constructors
    62
    63      /***
    64       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    65       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    66       * if a conversion error occurs. The locale is the default locale for
    67       * this instance of the Java Virtual Machine and an unlocalized pattern is used
    68       * for the convertion.
    69       *
    70       */
    71      public DateLocaleConverter() {
    72
    73          this(false);
    74      }
    75
    76      /***
    77       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    78       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    79       * if a conversion error occurs. The locale is the default locale for
    80       * this instance of the Java Virtual Machine.
    81       *
    82       * @param locPattern    Indicate whether the pattern is localized or not
    83       */
    84      public DateLocaleConverter(boolean locPattern) {
    85
    86          this(Locale.getDefault(), locPattern);
    87      }
    88
    89      /***
    90       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
    91       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
    92       * if a conversion error occurs. An unlocalized pattern is used for the convertion.
    93       *
    94       * @param locale        The locale
    95       */
    96      public DateLocaleConverter(Locale locale) {
    97
    98          this(locale, false);
    99      }
    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.info("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 }