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 java.text.DateFormat;
21  import java.text.DateFormatSymbols;
22  import java.text.ParseException;
23  import java.text.ParsePosition;
24  import java.text.SimpleDateFormat;
25  import java.util.Locale;
26  
27  import org.apache.commons.beanutils.ConversionException;
28  import org.apache.commons.beanutils.locale.BaseLocaleConverter;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
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   * @version $Id$
42   */
43  
44  public class DateLocaleConverter extends BaseLocaleConverter {
45  
46      // ----------------------------------------------------- Instance Variables
47  
48      /** All logging goes through this logger */
49      private final Log log = LogFactory.getLog(DateLocaleConverter.class);
50  
51      /** Should the date conversion be lenient? */
52      boolean isLenient = false;
53  
54      /**
55       * Default Pattern Characters
56       *
57       */
58      private static final String DEFAULT_PATTERN_CHARS = DateLocaleConverter.initDefaultChars();
59  
60      // ----------------------------------------------------------- Constructors
61  
62      /**
63       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
64       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
65       * if a conversion error occurs. The locale is the default locale for
66       * this instance of the Java Virtual Machine and an unlocalized pattern is used
67       * for the convertion.
68       *
69       */
70      public DateLocaleConverter() {
71  
72          this(false);
73      }
74  
75      /**
76       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
77       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
78       * if a conversion error occurs. The locale is the default locale for
79       * this instance of the Java Virtual Machine.
80       *
81       * @param locPattern    Indicate whether the pattern is localized or not
82       */
83      public DateLocaleConverter(final boolean locPattern) {
84  
85          this(Locale.getDefault(), locPattern);
86      }
87  
88      /**
89       * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
90       * that will throw a {@link org.apache.commons.beanutils.ConversionException}
91       * if a conversion error occurs. An unlocalized pattern is used for the convertion.
92       *
93       * @param locale        The locale
94       */
95      public DateLocaleConverter(final Locale locale) {
96  
97          this(locale, false);
98      }
99  
100     /**
101      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
102      * that will throw a {@link org.apache.commons.beanutils.ConversionException}
103      * if a conversion error occurs.
104      *
105      * @param locale        The locale
106      * @param locPattern    Indicate whether the pattern is localized or not
107      */
108     public DateLocaleConverter(final Locale locale, final boolean locPattern) {
109 
110         this(locale, (String) null, locPattern);
111     }
112 
113     /**
114      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
115      * that will throw a {@link org.apache.commons.beanutils.ConversionException}
116      * if a conversion error occurs. An unlocalized pattern is used for the convertion.
117      *
118      * @param locale        The locale
119      * @param pattern       The convertion pattern
120      */
121     public DateLocaleConverter(final Locale locale, final String pattern) {
122 
123         this(locale, pattern, false);
124     }
125 
126     /**
127      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
128      * that will throw a {@link org.apache.commons.beanutils.ConversionException}
129      * if a conversion error occurs.
130      *
131      * @param locale        The locale
132      * @param pattern       The convertion pattern
133      * @param locPattern    Indicate whether the pattern is localized or not
134      */
135     public DateLocaleConverter(final Locale locale, final String pattern, final boolean locPattern) {
136 
137         super(locale, pattern, locPattern);
138     }
139 
140     /**
141      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
142      * that will return the specified default value
143      * if a conversion error occurs. The locale is the default locale for
144      * this instance of the Java Virtual Machine and an unlocalized pattern is used
145      * for the convertion.
146      *
147      * @param defaultValue  The default value to be returned
148      */
149     public DateLocaleConverter(final Object defaultValue) {
150 
151         this(defaultValue, false);
152     }
153 
154     /**
155      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
156      * that will return the specified default value
157      * if a conversion error occurs. The locale is the default locale for
158      * this instance of the Java Virtual Machine.
159      *
160      * @param defaultValue  The default value to be returned
161      * @param locPattern    Indicate whether the pattern is localized or not
162      */
163     public DateLocaleConverter(final Object defaultValue, final boolean locPattern) {
164 
165         this(defaultValue, Locale.getDefault(), locPattern);
166     }
167 
168     /**
169      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
170      * that will return the specified default value
171      * if a conversion error occurs. An unlocalized pattern is used for the convertion.
172      *
173      * @param defaultValue  The default value to be returned
174      * @param locale        The locale
175      */
176     public DateLocaleConverter(final Object defaultValue, final Locale locale) {
177 
178         this(defaultValue, locale, false);
179     }
180 
181     /**
182      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
183      * that will return the specified default value
184      * if a conversion error occurs.
185      *
186      * @param defaultValue  The default value to be returned
187      * @param locale        The locale
188      * @param locPattern    Indicate whether the pattern is localized or not
189      */
190     public DateLocaleConverter(final Object defaultValue, final Locale locale, final boolean locPattern) {
191 
192         this(defaultValue, locale, null, locPattern);
193     }
194 
195 
196     /**
197      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
198      * that will return the specified default value
199      * if a conversion error occurs. An unlocalized pattern is used for the convertion.
200      *
201      * @param defaultValue  The default value to be returned
202      * @param locale        The locale
203      * @param pattern       The convertion pattern
204      */
205     public DateLocaleConverter(final Object defaultValue, final Locale locale, final String pattern) {
206 
207         this(defaultValue, locale, pattern, false);
208     }
209 
210     /**
211      * Create a {@link org.apache.commons.beanutils.locale.LocaleConverter}
212      * that will return the specified default value
213      * if a conversion error occurs.
214      *
215      * @param defaultValue  The default value to be returned
216      * @param locale        The locale
217      * @param pattern       The convertion pattern
218      * @param locPattern    Indicate whether the pattern is localized or not
219      */
220     public DateLocaleConverter(final Object defaultValue, final Locale locale, final String pattern, final boolean locPattern) {
221 
222         super(defaultValue, locale, pattern, locPattern);
223     }
224 
225     // --------------------------------------------------------- Methods
226 
227     /**
228      * Returns whether date formatting is lenient.
229      *
230      * @return true if the <code>DateFormat</code> used for formatting is lenient
231      * @see java.text.DateFormat#isLenient
232      */
233     public boolean isLenient() {
234         return isLenient;
235     }
236 
237     /**
238      * Specify whether or not date-time parsing should be lenient.
239      *
240      * @param lenient true if the <code>DateFormat</code> used for formatting should be lenient
241      * @see java.text.DateFormat#setLenient
242      */
243     public void setLenient(final boolean lenient) {
244         isLenient = lenient;
245     }
246 
247     // --------------------------------------------------------- Methods
248 
249     /**
250      * Convert the specified locale-sensitive input object into an output object of the
251      * specified type.
252      *
253      * @param value The input object to be converted
254      * @param pattern The pattern is used for the convertion
255      * @return the converted Date value
256      *
257      * @throws org.apache.commons.beanutils.ConversionException
258      * if conversion cannot be performed successfully
259      * @throws ParseException if an error occurs parsing
260      */
261     @Override
262     protected Object parse(final 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          final 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         final ParsePosition pos = new ParsePosition(0);
286         final String strValue = value.toString();
287         final 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(final String localizedPattern, final 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          final DateFormatSymbols localizedSymbols = new DateFormatSymbols(locale);
319          final 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 (final 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(final String pattern, final String fromChars, final String toChars) {
341 
342         final StringBuilder converted = new StringBuilder();
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                     final 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         final DateFormatSymbols defaultSymbols = new DateFormatSymbols(Locale.US);
381         return defaultSymbols.getLocalPatternChars();
382     }
383 
384 }