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 * https://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 specified class (if possible). 318 * 319 * @param values Value to be converted (may be null) 320 * @param clazz Java array or element class to be converted to 321 * @return the converted value 322 * @throws org.apache.commons.beanutils.ConversionException if thrown by an underlying Converter 323 */ 324 public Object convert(final String[] values, final Class<?> clazz) { 325 return convert(values, clazz, getDefaultLocale(), null); 326 } 327 328 /** 329 * Convert an array of specified values to an array of objects of the 330 * specified class (if possible) using the convertion pattern. 331 * 332 * @param values Value to be converted (may be null) 333 * @param clazz Java array or element class to be converted to 334 * @param locale The locale 335 * @param pattern The convertion pattern 336 * @return the converted value 337 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 338 * underlying Converter 339 */ 340 public Object convert(final String[] values, final Class<?> clazz, final Locale locale, final String pattern) { 341 342 Class<?> type = clazz; 343 if (clazz.isArray()) { 344 type = clazz.getComponentType(); 345 } 346 if (log.isDebugEnabled()) { 347 log.debug("Convert String[" + values.length + "] to class " + 348 type.getName() + "[] using " + locale + 349 " locale and " + pattern + " pattern"); 350 } 351 352 final Object array = Array.newInstance(type, values.length); 353 for (int i = 0; i < values.length; i++) { 354 Array.set(array, i, convert(values[i], type, locale, pattern)); 355 } 356 357 return array; 358 } 359 360 /** 361 * Convert an array of specified values to an array of objects of the 362 * specified class (if possible) using the convertion pattern. 363 * 364 * @param values Value to be converted (may be null) 365 * @param clazz Java array or element class to be converted to 366 * @param pattern The convertion pattern 367 * @return the converted value 368 * @throws org.apache.commons.beanutils.ConversionException if thrown by an 369 * underlying Converter 370 */ 371 public Object convert(final String[] values, final Class<?> clazz, final String pattern) { 372 373 return convert(values, clazz, getDefaultLocale(), pattern); 374 } 375 376 /** 377 * Create all {@link LocaleConverter} types for specified locale. 378 * 379 * @param locale The Locale 380 * @return The FastHashMap instance contains the all {@link LocaleConverter} types 381 * for the specified locale. 382 * @deprecated This method will be modified to return a Map in the next release. 383 */ 384 @Deprecated 385 protected FastHashMap create(final Locale locale) { 386 387 final FastHashMap converter = new DelegateFastHashMap(BeanUtils.createCache()); 388 converter.setFast(false); 389 390 converter.put(BigDecimal.class, new BigDecimalLocaleConverter(locale, applyLocalized)); 391 converter.put(BigInteger.class, new BigIntegerLocaleConverter(locale, applyLocalized)); 392 393 converter.put(Byte.class, new ByteLocaleConverter(locale, applyLocalized)); 394 converter.put(Byte.TYPE, new ByteLocaleConverter(locale, applyLocalized)); 395 396 converter.put(Double.class, new DoubleLocaleConverter(locale, applyLocalized)); 397 converter.put(Double.TYPE, new DoubleLocaleConverter(locale, applyLocalized)); 398 399 converter.put(Float.class, new FloatLocaleConverter(locale, applyLocalized)); 400 converter.put(Float.TYPE, new FloatLocaleConverter(locale, applyLocalized)); 401 402 converter.put(Integer.class, new IntegerLocaleConverter(locale, applyLocalized)); 403 converter.put(Integer.TYPE, new IntegerLocaleConverter(locale, applyLocalized)); 404 405 converter.put(Long.class, new LongLocaleConverter(locale, applyLocalized)); 406 converter.put(Long.TYPE, new LongLocaleConverter(locale, applyLocalized)); 407 408 converter.put(Short.class, new ShortLocaleConverter(locale, applyLocalized)); 409 converter.put(Short.TYPE, new ShortLocaleConverter(locale, applyLocalized)); 410 411 converter.put(String.class, new StringLocaleConverter(locale, applyLocalized)); 412 413 // conversion format patterns of java.sql.* types should correspond to default 414 // behavior of toString and valueOf methods of these classes 415 converter.put(java.sql.Date.class, new SqlDateLocaleConverter(locale, "yyyy-MM-dd")); 416 converter.put(java.sql.Time.class, new SqlTimeLocaleConverter(locale, "HH:mm:ss")); 417 converter.put(java.sql.Timestamp.class, new SqlTimestampLocaleConverter(locale, "yyyy-MM-dd HH:mm:ss.S")); 418 419 converter.setFast(true); 420 421 return converter; 422 } 423 424 /** 425 * Remove any registered {@link LocaleConverter}. 426 */ 427public void deregister() { 428 429 final FastHashMap defaultConverter = lookup(defaultLocale); 430 431 mapConverters.setFast(false); 432 433 mapConverters.clear(); 434 mapConverters.put(defaultLocale, defaultConverter); 435 436 mapConverters.setFast(true); 437} 438 439 /** 440 * Remove any registered {@link LocaleConverter} for the specified locale and Class. 441 * 442 * @param clazz Class for which to remove a registered Converter 443 * @param locale The locale 444 */ 445 public void deregister(final Class<?> clazz, final Locale locale) { 446 447 lookup(locale).remove(clazz); 448 } 449 450 /** 451 * Remove any registered {@link LocaleConverter} for the specified locale 452 * 453 * @param locale The locale 454 */ 455 public void deregister(final Locale locale) { 456 457 mapConverters.remove(locale); 458 } 459 460 /** 461 * getter for applyLocalized 462 * 463 * @return <code>true</code> if pattern is localized, 464 * otherwise <code>false</code> 465 */ 466 public boolean getApplyLocalized() { 467 return applyLocalized; 468 } 469 470 /** 471 * getter for defaultLocale. 472 * @return the default locale 473 */ 474 public Locale getDefaultLocale() { 475 476 return defaultLocale; 477 } 478 479 /** 480 * Look up and return any registered {@link LocaleConverter} for the specified 481 * destination class and locale; if there is no registered Converter, return 482 * <code>null</code>. 483 * 484 * @param clazz Class for which to return a registered Converter 485 * @param locale The Locale 486 * @return The registered locale Converter, if any 487 */ 488 public LocaleConverter lookup(final Class<?> clazz, final Locale locale) { 489 490 final LocaleConverter converter = (LocaleConverter) lookup(locale).get(clazz); 491 492 if (log.isTraceEnabled()) { 493 log.trace("LocaleConverter:" + converter); 494 } 495 496 return converter; 497 } 498 499 /** 500 * Look up and return any registered FastHashMap instance for the specified locale; 501 * if there is no registered one, return <code>null</code>. 502 * 503 * @param locale The Locale 504 * @return The FastHashMap instance contains the all {@link LocaleConverter} types for 505 * the specified locale. 506 * @deprecated This method will be modified to return a Map in the next release. 507 */ 508 @Deprecated 509 protected FastHashMap lookup(final Locale locale) { 510 FastHashMap localeConverters; 511 512 if (locale == null) { 513 localeConverters = (FastHashMap) mapConverters.get(defaultLocale); 514 } 515 else { 516 localeConverters = (FastHashMap) mapConverters.get(locale); 517 518 if (localeConverters == null) { 519 localeConverters = create(locale); 520 mapConverters.put(locale, localeConverters); 521 } 522 } 523 524 return localeConverters; 525 } 526 527 /** 528 * Register a custom {@link LocaleConverter} for the specified destination 529 * <code>Class</code>, replacing any previously registered converter. 530 * 531 * @param converter The LocaleConverter to be registered 532 * @param clazz The Destination class for conversions performed by this 533 * Converter 534 * @param locale The locale 535 */ 536 public void register(final LocaleConverter converter, final Class<?> clazz, final Locale locale) { 537 538 lookup(locale).put(clazz, converter); 539 } 540 541 /** 542 * setter for applyLocalized 543 * 544 * @param newApplyLocalized <code>true</code> if pattern is localized, 545 * otherwise <code>false</code> 546 */ 547 public void setApplyLocalized(final boolean newApplyLocalized) { 548 applyLocalized = newApplyLocalized; 549 } 550 551 /** 552 * setter for defaultLocale. 553 * @param locale the default locale 554 */ 555 public void setDefaultLocale(final Locale locale) { 556 557 if (locale == null) { 558 defaultLocale = Locale.getDefault(); 559 } 560 else { 561 defaultLocale = locale; 562 } 563 } 564}