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.beanutils2.locale; 019 020import java.lang.reflect.Array; 021import java.math.BigDecimal; 022import java.math.BigInteger; 023import java.util.Locale; 024import java.util.Map; 025 026import org.apache.commons.beanutils2.BeanUtils; 027import org.apache.commons.beanutils2.ConversionException; 028import org.apache.commons.beanutils2.locale.converters.BigDecimalLocaleConverter; 029import org.apache.commons.beanutils2.locale.converters.BigIntegerLocaleConverter; 030import org.apache.commons.beanutils2.locale.converters.ByteLocaleConverter; 031import org.apache.commons.beanutils2.locale.converters.DoubleLocaleConverter; 032import org.apache.commons.beanutils2.locale.converters.FloatLocaleConverter; 033import org.apache.commons.beanutils2.locale.converters.IntegerLocaleConverter; 034import org.apache.commons.beanutils2.locale.converters.LongLocaleConverter; 035import org.apache.commons.beanutils2.locale.converters.ShortLocaleConverter; 036import org.apache.commons.beanutils2.locale.converters.StringLocaleConverter; 037import org.apache.commons.beanutils2.sql.converters.locale.SqlDateLocaleConverter; 038import org.apache.commons.beanutils2.sql.converters.locale.SqlTimeLocaleConverter; 039import org.apache.commons.beanutils2.sql.converters.locale.SqlTimestampLocaleConverter; 040import org.apache.commons.logging.Log; 041import org.apache.commons.logging.LogFactory; 042 043/** 044 * <p> 045 * Utility methods for converting locale-sensitive String scalar values to objects of the specified Class, String arrays to arrays of the specified Class and 046 * object to locale-sensitive String scalar value. 047 * </p> 048 * 049 * <p> 050 * This class provides the implementations used by the static utility methods in {@link LocaleConvertUtils}. 051 * </p> 052 * 053 * <p> 054 * The actual {@link LocaleConverter} instance to be used can be registered for each possible destination Class. Unless you override them, standard 055 * {@link LocaleConverter} instances are provided for all of the following destination Classes: 056 * </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> 073 * For backwards compatibility, the standard locale converters for primitive types (and the corresponding wrapper classes). 074 * 075 * If you prefer to have another {@link LocaleConverter} thrown instead, replace the standard {@link LocaleConverter} instances with ones created with the one 076 * of the appropriate constructors. 077 * 078 * It's important that {@link LocaleConverter} should be registered for the specified locale and Class (or primitive type). 079 * 080 * @since 1.7 081 */ 082public class LocaleConvertUtilsBean { 083 084 /** The {@code Log} instance for this class. */ 085 private static final Log LOG = LogFactory.getLog(LocaleConvertUtilsBean.class); 086 087 /** 088 * Gets singleton instance. This is the same as the instance used by the default {@link LocaleBeanUtilsBean} singleton. 089 * 090 * @return the singleton instance 091 */ 092 public static LocaleConvertUtilsBean getInstance() { 093 return LocaleBeanUtilsBean.getLocaleBeanUtilsInstance().getLocaleConvertUtils(); 094 } 095 096 /** The locale - default for conversion. */ 097 private Locale defaultLocale = Locale.getDefault(); 098 099 /** Indicate whether the pattern is localized or not */ 100 private boolean applyLocalized; 101 102 /** 103 * Every entry of the mapConverters is: 104 * <ul> 105 * <li>key = locale</li> 106 * <li>value = map of converters for the certain locale.</li> 107 * <ul> 108 */ 109 private final Map<Locale, Map<Class<?>, LocaleConverter<?>>> mapConverters; 110 111 /** 112 * Makes the state by default (deregisters all converters for all locales) and then registers default locale converters. 113 */ 114 public LocaleConvertUtilsBean() { 115 mapConverters = BeanUtils.createCache(); 116 deregister(); 117 } 118 119 /** 120 * Convert the specified locale-sensitive value into a String. 121 * 122 * @param value The Value to be converted 123 * @return the converted value 124 * @throws ConversionException if thrown by an underlying Converter 125 */ 126 public String convert(final Object value) { 127 return convert(value, defaultLocale, null); 128 } 129 130 /** 131 * Convert the specified locale-sensitive value into a String using the particular conversion pattern. 132 * 133 * @param value The Value to be converted 134 * @param locale The locale 135 * @param pattern The conversion pattern 136 * @return the converted value 137 * @throws ConversionException if thrown by an underlying Converter 138 */ 139 public String convert(final Object value, final Locale locale, final String pattern) { 140 final LocaleConverter<String> converter = lookup(String.class, locale); 141 return converter.convert(String.class, value, pattern); 142 } 143 144 /** 145 * Convert the specified locale-sensitive value into a String using the conversion pattern. 146 * 147 * @param value The Value to be converted 148 * @param pattern The conversion pattern 149 * @return the converted value 150 * @throws ConversionException if thrown by an underlying Converter 151 */ 152 public String convert(final Object value, final String pattern) { 153 return convert(value, defaultLocale, pattern); 154 } 155 156 /** 157 * Convert the specified value to an object of the specified class (if possible). Otherwise, return a String representation of the value. 158 * 159 * @param value The String scalar value to be converted 160 * @param clazz The Data type to which this value should be converted. 161 * @return the converted value 162 * @throws ConversionException if thrown by an underlying Converter 163 */ 164 public Object convert(final String value, final Class<?> clazz) { 165 return convert(value, clazz, defaultLocale, null); 166 } 167 168 /** 169 * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of 170 * the value. 171 * 172 * @param value The String scalar value to be converted 173 * @param clazz The Data type to which this value should be converted. 174 * @param locale The locale 175 * @param pattern The conversion pattern 176 * @return the converted value 177 * @throws ConversionException if thrown by an underlying Converter 178 */ 179 public Object convert(final String value, final Class<?> clazz, final Locale locale, final String pattern) { 180 if (LOG.isDebugEnabled()) { 181 LOG.debug("Convert string " + value + " to class " + clazz.getName() + " using " + locale + " locale and " + pattern + " pattern"); 182 } 183 184 Class<?> targetClass = clazz; 185 LocaleConverter converter = lookup(clazz, locale); 186 187 if (converter == null) { 188 converter = lookup(String.class, locale); 189 targetClass = String.class; 190 } 191 if (LOG.isTraceEnabled()) { 192 LOG.trace(" Using converter " + converter); 193 } 194 195 return converter.convert(targetClass, value, pattern); 196 } 197 198 /** 199 * Convert the specified value to an object of the specified class (if possible) using the conversion pattern. Otherwise, return a String representation of 200 * the value. 201 * 202 * @param value The String scalar value to be converted 203 * @param clazz The Data type to which this value should be converted. 204 * @param pattern The conversion pattern 205 * @return the converted value 206 * @throws ConversionException if thrown by an underlying Converter 207 */ 208 public Object convert(final String value, final Class<?> clazz, final String pattern) { 209 return convert(value, clazz, defaultLocale, pattern); 210 } 211 212 /** 213 * Convert an array of specified values to an array of objects of the specified class (if possible) . 214 * 215 * @param values Value to be converted (may be null) 216 * @param clazz Java array or element class to be converted to 217 * @return the converted value 218 * @throws ConversionException if thrown by an underlying Converter 219 */ 220 public Object convert(final String[] values, final Class<?> clazz) { 221 return convert(values, clazz, getDefaultLocale(), null); 222 } 223 224 /** 225 * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern. 226 * 227 * @param <T> The result component type 228 * @param values Value to be converted (may be null) 229 * @param clazz Java array or element class to be converted to 230 * @param locale The locale 231 * @param pattern The conversion pattern 232 * @return the converted value 233 * @throws ConversionException if thrown by an underlying Converter 234 */ 235 public <T> T[] convert(final String[] values, final Class<T> clazz, final Locale locale, final String pattern) { 236 Class<?> type = clazz; 237 if (clazz.isArray()) { 238 type = clazz.getComponentType(); 239 } 240 if (LOG.isDebugEnabled()) { 241 LOG.debug("Convert String[" + values.length + "] to class " + type.getName() + "[] using " + locale + " locale and " + pattern + " pattern"); 242 } 243 244 final T[] array = (T[]) Array.newInstance(type, values.length); 245 for (int i = 0; i < values.length; i++) { 246 Array.set(array, i, convert(values[i], type, locale, pattern)); 247 } 248 return array; 249 } 250 251 /** 252 * Convert an array of specified values to an array of objects of the specified class (if possible) using the conversion pattern. 253 * 254 * @param <T> The array component type 255 * @param values Value to be converted (may be null) 256 * @param clazz Java array or element class to be converted to 257 * @param pattern The conversion pattern 258 * @return the converted value 259 * @throws ConversionException if thrown by an underlying Converter 260 */ 261 public <T> T[] convert(final String[] values, final Class<T> clazz, final String pattern) { 262 return convert(values, clazz, getDefaultLocale(), pattern); 263 } 264 265 /** 266 * Create all {@link LocaleConverter} types for specified locale. 267 * 268 * @param locale The Locale 269 * @return The map instance contains the all {@link LocaleConverter} types for the specified locale. 270 */ 271 protected Map<Class<?>, LocaleConverter<?>> create(final Locale locale) { 272 final Map<Class<?>, LocaleConverter<?>> converter = BeanUtils.createCache(); 273 274 converter.put(BigDecimal.class, BigDecimalLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 275 converter.put(BigInteger.class, BigIntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 276 277 converter.put(Byte.class, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 278 converter.put(Byte.TYPE, ByteLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 279 280 converter.put(Double.class, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 281 converter.put(Double.TYPE, DoubleLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 282 283 converter.put(Float.class, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 284 converter.put(Float.TYPE, FloatLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 285 286 converter.put(Integer.class, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 287 converter.put(Integer.TYPE, IntegerLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 288 289 converter.put(Long.class, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 290 converter.put(Long.TYPE, LongLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 291 292 converter.put(Short.class, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 293 converter.put(Short.TYPE, ShortLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 294 295 converter.put(String.class, StringLocaleConverter.builder().setLocale(locale).setLocalizedPattern(applyLocalized).get()); 296 297 // conversion format patterns of java.sql.* types should correspond to default 298 // behavior of toString and valueOf methods of these classes 299 converter.put(java.sql.Date.class, SqlDateLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd").get()); 300 converter.put(java.sql.Time.class, SqlTimeLocaleConverter.builder().setLocale(locale).setPattern("HH:mm:ss").get()); 301 converter.put(java.sql.Timestamp.class, SqlTimestampLocaleConverter.builder().setLocale(locale).setPattern("yyyy-MM-dd HH:mm:ss.S").get()); 302 303 return converter; 304 } 305 306 /** 307 * Remove any registered {@link LocaleConverter}. 308 */ 309 public void deregister() { 310 final Map<Class<?>, LocaleConverter<?>> defaultConverter = lookup(defaultLocale); 311 mapConverters.clear(); 312 mapConverters.put(defaultLocale, defaultConverter); 313 } 314 315 /** 316 * Remove any registered {@link LocaleConverter} for the specified locale and Class. 317 * 318 * @param clazz Class for which to remove a registered Converter 319 * @param locale The locale 320 */ 321 public void deregister(final Class<?> clazz, final Locale locale) { 322 lookup(locale).remove(clazz); 323 } 324 325 /** 326 * Remove any registered {@link LocaleConverter} for the specified locale 327 * 328 * @param locale The locale 329 */ 330 public void deregister(final Locale locale) { 331 mapConverters.remove(locale); 332 } 333 334 /** 335 * getter for applyLocalized 336 * 337 * @return {@code true} if pattern is localized, otherwise {@code false} 338 */ 339 public boolean getApplyLocalized() { 340 return applyLocalized; 341 } 342 343 /** 344 * getter for defaultLocale. 345 * 346 * @return the default locale 347 */ 348 public Locale getDefaultLocale() { 349 return defaultLocale; 350 } 351 352 /** 353 * Look up and return any registered {@link LocaleConverter} for the specified destination class and locale; if there is no registered Converter, return 354 * {@code null}. 355 * 356 * @param <T> The converter type. 357 * @param clazz Class for which to return a registered Converter 358 * @param locale The Locale 359 * @return The registered locale Converter, if any 360 */ 361 public <T> LocaleConverter<T> lookup(final Class<T> clazz, final Locale locale) { 362 final LocaleConverter<T> converter = (LocaleConverter<T>) lookup(locale).get(clazz); 363 364 if (LOG.isTraceEnabled()) { 365 LOG.trace("LocaleConverter:" + converter); 366 } 367 368 return converter; 369 } 370 371 /** 372 * Look up and return any registered map instance for the specified locale; if there is no registered one, return {@code null}. 373 * 374 * @param locale The Locale 375 * @return The map instance contains the all {@link LocaleConverter} types for the specified locale. 376 */ 377 protected Map<Class<?>, LocaleConverter<?>> lookup(final Locale locale) { 378 return mapConverters.computeIfAbsent(locale == null ? defaultLocale : locale, this::create); 379 } 380 381 /** 382 * Register a custom {@link LocaleConverter} for the specified destination {@code Class}, replacing any previously registered converter. 383 * 384 * @param <T> The converter type. 385 * @param converter The LocaleConverter to be registered 386 * @param clazz The Destination class for conversions performed by this Converter 387 * @param locale The locale 388 */ 389 public <T> void register(final LocaleConverter<T> converter, final Class<T> clazz, final Locale locale) { 390 lookup(locale).put(clazz, converter); 391 } 392 393 /** 394 * setter for applyLocalized 395 * 396 * @param newApplyLocalized {@code true} if pattern is localized, otherwise {@code false} 397 */ 398 public void setApplyLocalized(final boolean newApplyLocalized) { 399 applyLocalized = newApplyLocalized; 400 } 401 402 /** 403 * setter for defaultLocale. 404 * 405 * @param locale the default locale 406 */ 407 public void setDefaultLocale(final Locale locale) { 408 if (locale == null) { 409 defaultLocale = Locale.getDefault(); 410 } else { 411 defaultLocale = locale; 412 } 413 } 414}