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 }