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 }