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 018package org.apache.commons.beanutils.locale; 019 020import java.lang.reflect.Array; 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.Collection; 024import java.util.Locale; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.commons.beanutils.BeanUtils; 029import org.apache.commons.beanutils.locale.converters.BigDecimalLocaleConverter; 030import org.apache.commons.beanutils.locale.converters.BigIntegerLocaleConverter; 031import org.apache.commons.beanutils.locale.converters.ByteLocaleConverter; 032import org.apache.commons.beanutils.locale.converters.DoubleLocaleConverter; 033import org.apache.commons.beanutils.locale.converters.FloatLocaleConverter; 034import org.apache.commons.beanutils.locale.converters.IntegerLocaleConverter; 035import org.apache.commons.beanutils.locale.converters.LongLocaleConverter; 036import org.apache.commons.beanutils.locale.converters.ShortLocaleConverter; 037import org.apache.commons.beanutils.locale.converters.SqlDateLocaleConverter; 038import org.apache.commons.beanutils.locale.converters.SqlTimeLocaleConverter; 039import org.apache.commons.beanutils.locale.converters.SqlTimestampLocaleConverter; 040import org.apache.commons.beanutils.locale.converters.StringLocaleConverter; 041import org.apache.commons.collections.FastHashMap; 042import org.apache.commons.logging.Log; 043import org.apache.commons.logging.LogFactory; 044 045/** 046 * <p>Utility methods for converting locale-sensitive String scalar values to objects of the 047 * specified Class, String arrays to arrays of the specified Class and 048 * object to locale-sensitive String scalar value.</p> 049 * 050 * <p>This class provides the implementations used by the static utility methods in 051 * {@link LocaleConvertUtils}.</p> 052 * 053 * <p>The actual {@link LocaleConverter} instance to be used 054 * can be registered for each possible destination Class. Unless you override them, standard 055 * {@link LocaleConverter} instances are provided for all of the following 056 * destination Classes:</p> 057 * <ul> 058 * <li>java.lang.BigDecimal</li> 059 * <li>java.lang.BigInteger</li> 060 * <li>byte and java.lang.Byte</li> 061 * <li>double and java.lang.Double</li> 062 * <li>float and java.lang.Float</li> 063 * <li>int and java.lang.Integer</li> 064 * <li>long and java.lang.Long</li> 065 * <li>short and java.lang.Short</li> 066 * <li>java.lang.String</li> 067 * <li>java.sql.Date</li> 068 * <li>java.sql.Time</li> 069 * <li>java.sql.Timestamp</li> 070 * </ul> 071 * 072 * <p>For backwards compatibility, the standard locale converters 073 * for primitive types (and the corresponding wrapper classes). 074 * 075 * If you prefer to have another {@link LocaleConverter} 076 * thrown instead, replace the standard {@link LocaleConverter} instances 077 * with ones created with the one of the appropriate constructors. 078 * 079 * It's important that {@link LocaleConverter} should be registered for 080 * the specified locale and Class (or primitive type). 081 * 082 * @since 1.7 083 */ 084public class LocaleConvertUtilsBean { 085 086 /** 087 * FastHashMap implementation that uses WeakReferences to overcome 088 * memory leak problems. 089 * 090 * This is a hack to retain binary compatibility with previous 091 * releases (where FastHashMap is exposed in the API), but 092 * use WeakHashMap to resolve memory leaks. 093 */ 094 private static class DelegateFastHashMap extends FastHashMap { 095 096 private static final long serialVersionUID = 1L; 097 private final Map<Object, Object> map; 098 099 private DelegateFastHashMap(final Map<Object, Object> map) { 100 this.map = map; 101 } 102 @Override 103 public void clear() { 104 map.clear(); 105 } 106 @Override 107 public boolean containsKey(final Object key) { 108 return map.containsKey(key); 109 } 110 @Override 111 public boolean containsValue(final Object value) { 112 return map.containsValue(value); 113 } 114 @Override 115 public Set<Map.Entry<Object, Object>> entrySet() { 116 return map.entrySet(); 117 } 118 @Override 119 public boolean equals(final Object o) { 120 return map.equals(o); 121 } 122 @Override 123 public Object get(final Object key) { 124 return map.get(key); 125 } 126 @Override 127 public boolean getFast() { 128 return BeanUtils.getCacheFast(map); 129 } 130 @Override 131 public int hashCode() { 132 return map.hashCode(); 133 } 134 @Override 135 public boolean isEmpty() { 136 return map.isEmpty(); 137 } 138 @Override 139 public Set<Object> keySet() { 140 return map.keySet(); 141 } 142 @Override 143 public Object put(final Object key, final Object value) { 144 return map.put(key, value); 145 } 146 @SuppressWarnings({ "rawtypes", "unchecked" }) 147 // we operate on very generic types (<Object, Object>), so there is 148 // no need for doing type checks 149 @Override 150 public void putAll(final Map m) { 151 map.putAll(m); 152 } 153 @Override 154 public Object remove(final Object key) { 155 return map.remove(key); 156 } 157 @Override 158 public void setFast(final boolean fast) { 159 BeanUtils.setCacheFast(map, fast); 160 } 161 @Override 162 public int size() { 163 return map.size(); 164 } 165 @Override 166 public Collection<Object> values() { 167 return map.values(); 168 } 169 } 170 171 /** 172 * Gets singleton instance. 173 * This is the same as the instance used by the default {@link LocaleBeanUtilsBean} singleton. 174 * @return the singleton instance 175 */ 176 public static LocaleConvertUtilsBean getInstance() { 177 return LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getLocaleConvertUtils(); 178 } 179 180 /** The locale - default for convertion. */ 181 private Locale defaultLocale = Locale.getDefault(); 182 183 /** Indicate whether the pattern is localized or not */ 184 private boolean applyLocalized; 185 186 /** The <code>Log</code> instance for this class. */ 187 private final Log log = LogFactory.getLog(LocaleConvertUtils.class); 188 189 /** Every entry of the mapConverters is: 190 * key = locale 191 * value = FastHashMap of converters for the certain locale. 192 */ 193 private final FastHashMap mapConverters = new DelegateFastHashMap(BeanUtils.createCache()); 194 195 /** 196 * Makes the state by default (deregisters all converters for all locales) 197 * and then registers default locale converters. 198 */ 199 public LocaleConvertUtilsBean() { 200 mapConverters.setFast(false); 201 deregister(); 202 mapConverters.setFast(true); 203 } 204 205 /** 206 * Convert the specified locale-sensitive value into a String. 207 * 208 * @param value The Value to be converted 209 * @return the converted value 210 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 211 * underlying Converter 212 */ 213 public String convert(final Object value) { 214 return convert(value, defaultLocale, null); 215 } 216 217 /** 218 * Convert the specified locale-sensitive value into a String 219 * using the paticular convertion pattern. 220 * 221 * @param value The Value to be converted 222 * @param locale The locale 223 * @param pattern The convertion pattern 224 * @return the converted value 225 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 226 * underlying Converter 227 */ 228 public String convert(final Object value, final Locale locale, final String pattern) { 229 230 final LocaleConverter converter = lookup(String.class, locale); 231 232 return converter.convert(String.class, value, pattern); 233 } 234 235 /** 236 * Convert the specified locale-sensitive value into a String 237 * using the conversion pattern. 238 * 239 * @param value The Value to be converted 240 * @param pattern The convertion pattern 241 * @return the converted value 242 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 243 * underlying Converter 244 */ 245 public String convert(final Object value, final String pattern) { 246 return convert(value, defaultLocale, pattern); 247 } 248 249 /** 250 * Convert the specified value to an object of the specified class (if 251 * possible). Otherwise, return a String representation of the value. 252 * 253 * @param value The String scalar value to be converted 254 * @param clazz The Data type to which this value should be converted. 255 * @return the converted value 256 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 257 * underlying Converter 258 */ 259 public Object convert(final String value, final Class<?> clazz) { 260 261 return convert(value, clazz, defaultLocale, null); 262 } 263 264 /** 265 * Convert the specified value to an object of the specified class (if 266 * possible) using the convertion pattern. Otherwise, return a String 267 * representation of the value. 268 * 269 * @param value The String scalar value to be converted 270 * @param clazz The Data type to which this value should be converted. 271 * @param locale The locale 272 * @param pattern The convertion pattern 273 * @return the converted value 274 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 275 * underlying Converter 276 */ 277 public Object convert(final String value, final Class<?> clazz, final Locale locale, final String pattern) { 278 279 if (log.isDebugEnabled()) { 280 log.debug("Convert string " + value + " to class " + 281 clazz.getName() + " using " + locale + 282 " locale and " + pattern + " pattern"); 283 } 284 285 Class<?> targetClass = clazz; 286 LocaleConverter converter = lookup(clazz, locale); 287 288 if (converter == null) { 289 converter = lookup(String.class, locale); 290 targetClass = String.class; 291 } 292 if (log.isTraceEnabled()) { 293 log.trace(" Using converter " + converter); 294 } 295 296 return converter.convert(targetClass, value, pattern); 297 } 298 299 /** 300 * Convert the specified value to an object of the specified class (if 301 * possible) using the convertion pattern. Otherwise, return a String 302 * representation of the value. 303 * 304 * @param value The String scalar value to be converted 305 * @param clazz The Data type to which this value should be converted. 306 * @param pattern The convertion pattern 307 * @return the converted value 308 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 309 * underlying Converter 310 */ 311 public Object convert(final String value, final Class<?> clazz, final String pattern) { 312 313 return convert(value, clazz, defaultLocale, pattern); 314 } 315 316 /** 317 * Convert an array of specified values to an array of objects of the 318 * specified class (if possible) . 319 * 320 * @param values Value to be converted (may be null) 321 * @param clazz Java array or element class to be converted to 322 * @return the converted value 323 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 324 * underlying Converter 325 */ 326 public Object convert(final String[] values, final Class<?> clazz) { 327 328 return convert(values, clazz, getDefaultLocale(), null); 329 } 330 331 /** 332 * Convert an array of specified values to an array of objects of the 333 * specified class (if possible) using the convertion pattern. 334 * 335 * @param values Value to be converted (may be null) 336 * @param clazz Java array or element class to be converted to 337 * @param locale The locale 338 * @param pattern The convertion pattern 339 * @return the converted value 340 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 341 * underlying Converter 342 */ 343 public Object convert(final String[] values, final Class<?> clazz, final Locale locale, final String pattern) { 344 345 Class<?> type = clazz; 346 if (clazz.isArray()) { 347 type = clazz.getComponentType(); 348 } 349 if (log.isDebugEnabled()) { 350 log.debug("Convert String[" + values.length + "] to class " + 351 type.getName() + "[] using " + locale + 352 " locale and " + pattern + " pattern"); 353 } 354 355 final Object array = Array.newInstance(type, values.length); 356 for (int i = 0; i < values.length; i++) { 357 Array.set(array, i, convert(values[i], type, locale, pattern)); 358 } 359 360 return array; 361 } 362 363 /** 364 * Convert an array of specified values to an array of objects of the 365 * specified class (if possible) using the convertion pattern. 366 * 367 * @param values Value to be converted (may be null) 368 * @param clazz Java array or element class to be converted to 369 * @param pattern The convertion pattern 370 * @return the converted value 371 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 372 * underlying Converter 373 */ 374 public Object convert(final String[] values, final Class<?> clazz, final String pattern) { 375 376 return convert(values, clazz, getDefaultLocale(), pattern); 377 } 378 379 /** 380 * Create all {@link LocaleConverter} types for specified locale. 381 * 382 * @param locale The Locale 383 * @return The FastHashMap instance contains the all {@link LocaleConverter} types 384 * for the specified locale. 385 * @deprecated This method will be modified to return a Map in the next release. 386 */ 387 @Deprecated 388 protected FastHashMap create(final Locale locale) { 389 390 final FastHashMap converter = new DelegateFastHashMap(BeanUtils.createCache()); 391 converter.setFast(false); 392 393 converter.put(BigDecimal.class, new BigDecimalLocaleConverter(locale, applyLocalized)); 394 converter.put(BigInteger.class, new BigIntegerLocaleConverter(locale, applyLocalized)); 395 396 converter.put(Byte.class, new ByteLocaleConverter(locale, applyLocalized)); 397 converter.put(Byte.TYPE, new ByteLocaleConverter(locale, applyLocalized)); 398 399 converter.put(Double.class, new DoubleLocaleConverter(locale, applyLocalized)); 400 converter.put(Double.TYPE, new DoubleLocaleConverter(locale, applyLocalized)); 401 402 converter.put(Float.class, new FloatLocaleConverter(locale, applyLocalized)); 403 converter.put(Float.TYPE, new FloatLocaleConverter(locale, applyLocalized)); 404 405 converter.put(Integer.class, new IntegerLocaleConverter(locale, applyLocalized)); 406 converter.put(Integer.TYPE, new IntegerLocaleConverter(locale, applyLocalized)); 407 408 converter.put(Long.class, new LongLocaleConverter(locale, applyLocalized)); 409 converter.put(Long.TYPE, new LongLocaleConverter(locale, applyLocalized)); 410 411 converter.put(Short.class, new ShortLocaleConverter(locale, applyLocalized)); 412 converter.put(Short.TYPE, new ShortLocaleConverter(locale, applyLocalized)); 413 414 converter.put(String.class, new StringLocaleConverter(locale, applyLocalized)); 415 416 // conversion format patterns of java.sql.* types should correspond to default 417 // behaviour of toString and valueOf methods of these classes 418 converter.put(java.sql.Date.class, new SqlDateLocaleConverter(locale, "yyyy-MM-dd")); 419 converter.put(java.sql.Time.class, new SqlTimeLocaleConverter(locale, "HH:mm:ss")); 420 converter.put( java.sql.Timestamp.class, 421 new SqlTimestampLocaleConverter(locale, "yyyy-MM-dd HH:mm:ss.S") 422 ); 423 424 converter.setFast(true); 425 426 return converter; 427 } 428 429 /** 430 * Remove any registered {@link LocaleConverter}. 431 */ 432public void deregister() { 433 434 final FastHashMap defaultConverter = lookup(defaultLocale); 435 436 mapConverters.setFast(false); 437 438 mapConverters.clear(); 439 mapConverters.put(defaultLocale, defaultConverter); 440 441 mapConverters.setFast(true); 442} 443 444 /** 445 * Remove any registered {@link LocaleConverter} for the specified locale and Class. 446 * 447 * @param clazz Class for which to remove a registered Converter 448 * @param locale The locale 449 */ 450 public void deregister(final Class<?> clazz, final Locale locale) { 451 452 lookup(locale).remove(clazz); 453 } 454 455 /** 456 * Remove any registered {@link LocaleConverter} for the specified locale 457 * 458 * @param locale The locale 459 */ 460 public void deregister(final Locale locale) { 461 462 mapConverters.remove(locale); 463 } 464 465 /** 466 * getter for applyLocalized 467 * 468 * @return <code>true</code> if pattern is localized, 469 * otherwise <code>false</code> 470 */ 471 public boolean getApplyLocalized() { 472 return applyLocalized; 473 } 474 475 /** 476 * getter for defaultLocale. 477 * @return the default locale 478 */ 479 public Locale getDefaultLocale() { 480 481 return defaultLocale; 482 } 483 484 /** 485 * Look up and return any registered {@link LocaleConverter} for the specified 486 * destination class and locale; if there is no registered Converter, return 487 * <code>null</code>. 488 * 489 * @param clazz Class for which to return a registered Converter 490 * @param locale The Locale 491 * @return The registered locale Converter, if any 492 */ 493 public LocaleConverter lookup(final Class<?> clazz, final Locale locale) { 494 495 final LocaleConverter converter = (LocaleConverter) lookup(locale).get(clazz); 496 497 if (log.isTraceEnabled()) { 498 log.trace("LocaleConverter:" + converter); 499 } 500 501 return converter; 502 } 503 504 /** 505 * Look up and return any registered FastHashMap instance for the specified locale; 506 * if there is no registered one, return <code>null</code>. 507 * 508 * @param locale The Locale 509 * @return The FastHashMap instance contains the all {@link LocaleConverter} types for 510 * the specified locale. 511 * @deprecated This method will be modified to return a Map in the next release. 512 */ 513 @Deprecated 514 protected FastHashMap lookup(final Locale locale) { 515 FastHashMap localeConverters; 516 517 if (locale == null) { 518 localeConverters = (FastHashMap) mapConverters.get(defaultLocale); 519 } 520 else { 521 localeConverters = (FastHashMap) mapConverters.get(locale); 522 523 if (localeConverters == null) { 524 localeConverters = create(locale); 525 mapConverters.put(locale, localeConverters); 526 } 527 } 528 529 return localeConverters; 530 } 531 532 /** 533 * Register a custom {@link LocaleConverter} for the specified destination 534 * <code>Class</code>, replacing any previously registered converter. 535 * 536 * @param converter The LocaleConverter to be registered 537 * @param clazz The Destination class for conversions performed by this 538 * Converter 539 * @param locale The locale 540 */ 541 public void register(final LocaleConverter converter, final Class<?> clazz, final Locale locale) { 542 543 lookup(locale).put(clazz, converter); 544 } 545 546 /** 547 * setter for applyLocalized 548 * 549 * @param newApplyLocalized <code>true</code> if pattern is localized, 550 * otherwise <code>false</code> 551 */ 552 public void setApplyLocalized(final boolean newApplyLocalized) { 553 applyLocalized = newApplyLocalized; 554 } 555 556 /** 557 * setter for defaultLocale. 558 * @param locale the default locale 559 */ 560 public void setDefaultLocale(final Locale locale) { 561 562 if (locale == null) { 563 defaultLocale = Locale.getDefault(); 564 } 565 else { 566 defaultLocale = locale; 567 } 568 } 569}