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