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 */ 017package org.apache.commons.beanutils.converters; 018 019import java.lang.reflect.Array; 020import java.util.Collection; 021 022import org.apache.commons.beanutils.ConversionException; 023import org.apache.commons.beanutils.ConvertUtils; 024import org.apache.commons.beanutils.Converter; 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028/** 029 * Base {@link Converter} implementation that provides the structure 030 * for handling conversion <strong>to</strong> and <strong>from</strong> a specified type. 031 * <p> 032 * This implementation provides the basic structure for 033 * converting to/from a specified type optionally using a default 034 * value or throwing a {@link ConversionException} if a 035 * conversion error occurs. 036 * <p> 037 * Implementations should provide conversion to the specified 038 * type and from the specified type to a <code>String</code> value 039 * by implementing the following methods: 040 * <ul> 041 * <li><code>convertToString(value)</code> - convert to a String 042 * (default implementation uses the objects <code>toString()</code> 043 * method).</li> 044 * <li><code>convertToType(Class, value)</code> - convert 045 * to the specified type</li> 046 * </ul> 047 * <p> 048 * The default value has to be compliant to the default type of this 049 * converter - which is enforced by the generic type parameter. If a 050 * conversion is not possible and a default value is set, the converter 051 * tries to transform the default value to the requested target type. 052 * If this fails, a {@code ConversionException} if thrown. 053 * 054 * @since 1.8.0 055 */ 056public abstract class AbstractConverter implements Converter { 057 058 /** Debug logging message to indicate default value configuration */ 059 private static final String DEFAULT_CONFIG_MSG = 060 "(Converters can be configured to use default values to avoid throwing exceptions)"; 061 062 /** Current package name */ 063 // getPackage() below returns null on some platforms/jvm versions during the unit tests. 064// private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + "."; 065 private static final String PACKAGE = "org.apache.commons.beanutils.converters."; 066 067 /** 068 * Logging for this instance. 069 */ 070 private transient Log log; 071 072 /** 073 * Should we return the default value on conversion errors? 074 */ 075 private boolean useDefault; 076 077 /** 078 * The default value specified to our Constructor, if any. 079 */ 080 private Object defaultValue; 081 082 /** 083 * Construct a <em>Converter</em> that throws a 084 * <code>ConversionException</code> if an error occurs. 085 */ 086 public AbstractConverter() { 087 } 088 089 /** 090 * Construct a <em>Converter</em> that returns a default 091 * value if an error occurs. 092 * 093 * @param defaultValue The default value to be returned 094 * if the value to be converted is missing or an error 095 * occurs converting the value. 096 */ 097 public AbstractConverter(final Object defaultValue) { 098 setDefaultValue(defaultValue); 099 } 100 101 /** 102 * Generates a standard conversion exception with a message indicating that 103 * the passed in value cannot be converted to the desired target type. 104 * 105 * @param type the target type 106 * @param value the value to be converted 107 * @return a {@code ConversionException} with a standard message 108 * @since 1.9 109 */ 110 protected ConversionException conversionException(final Class<?> type, final Object value) { 111 return new ConversionException("Can't convert value '" + value 112 + "' to type " + type); 113 } 114 115 /** 116 * Convert the input object into an output object of the 117 * specified type. 118 * 119 * @param <T> the target type of the conversion 120 * @param type Data type to which this value should be converted 121 * @param value The input value to be converted 122 * @return The converted value. 123 * @throws ConversionException if conversion cannot be performed 124 * successfully and no default is specified. 125 */ 126 @Override 127 public <T> T convert(final Class<T> type, Object value) { 128 129 if (type == null) { 130 return convertToDefaultType(type, value); 131 } 132 133 Class<?> sourceType = value == null ? null : value.getClass(); 134 final Class<T> targetType = ConvertUtils.primitiveToWrapper(type); 135 136 if (log().isDebugEnabled()) { 137 log().debug("Converting" 138 + (value == null ? "" : " '" + toString(sourceType) + "'") 139 + " value '" + value + "' to type '" + toString(targetType) + "'"); 140 } 141 142 value = convertArray(value); 143 144 // Missing Value 145 if (value == null) { 146 return handleMissing(targetType); 147 } 148 149 sourceType = value.getClass(); 150 151 try { 152 // Convert --> String 153 if (targetType.equals(String.class)) { 154 return targetType.cast(convertToString(value)); 155 156 // No conversion necessary 157 } 158 if (targetType.equals(sourceType)) { 159 if (log().isDebugEnabled()) { 160 log().debug(" No conversion required, value is already a " 161 + toString(targetType)); 162 } 163 return targetType.cast(value); 164 165 // Convert --> Type 166 } 167 final Object result = convertToType(targetType, value); 168 if (log().isDebugEnabled()) { 169 log().debug(" Converted to " + toString(targetType) + 170 " value '" + result + "'"); 171 } 172 return targetType.cast(result); 173 } catch (final Throwable t) { 174 return handleError(targetType, value, t); 175 } 176 177 } 178 179 /** 180 * Return the first element from an Array (or Collection) 181 * or the value unchanged if not an Array (or Collection). 182 * 183 * This needs to be overriden for array/Collection converters. 184 * 185 * @param value The value to convert 186 * @return The first element in an Array (or Collection) 187 * or the value unchanged if not an Array (or Collection) 188 */ 189 protected Object convertArray(final Object value) { 190 if (value == null) { 191 return null; 192 } 193 if (value.getClass().isArray()) { 194 if (Array.getLength(value) > 0) { 195 return Array.get(value, 0); 196 } 197 return null; 198 } 199 if (value instanceof Collection) { 200 final Collection<?> collection = (Collection<?>)value; 201 if (collection.size() > 0) { 202 return collection.iterator().next(); 203 } 204 return null; 205 } 206 return value; 207 } 208 209 /** 210 * Performs a conversion to the default type. This method is called if we do 211 * not have a target class. In this case, the T parameter is not set. 212 * Therefore, we can cast to it (which is required to fulfill the contract 213 * of the method signature). 214 * 215 * @param <T> the type of the result object 216 * @param targetClass the target class of the conversion 217 * @param value the value to be converted 218 * @return the converted value 219 */ 220 private <T> T convertToDefaultType(final Class<T> targetClass, final Object value) { 221 return (T) convert(getDefaultType(), value); 222 } 223 224 /** 225 * Convert the input object into a String. 226 * <p> 227 * <strong>N.B.</strong>This implementation simply uses the value's 228 * <code>toString()</code> method and should be overriden if a 229 * more sophisticated mechanism for <em>conversion to a String</em> 230 * is required. 231 * 232 * @param value The input value to be converted. 233 * @return the converted String value. 234 * @throws Throwable if an error occurs converting to a String 235 */ 236 protected String convertToString(final Object value) throws Throwable { 237 return value.toString(); 238 } 239 240 /** 241 * Convert the input object into an output object of the 242 * specified type. 243 * <p> 244 * Typical implementations will provide a minimum of 245 * <code>String to type</code> conversion. 246 * 247 * @param <T> Target type of the conversion. 248 * @param type Data type to which this value should be converted. 249 * @param value The input value to be converted. 250 * @return The converted value. 251 * @throws Throwable if an error occurs converting to the specified type 252 */ 253 protected abstract <T> T convertToType(Class<T> type, Object value) throws Throwable; 254 255 /** 256 * Return the default value for conversions to the specified 257 * type. 258 * @param type Data type to which this value should be converted. 259 * @return The default value for the specified type. 260 */ 261 protected Object getDefault(final Class<?> type) { 262 if (type.equals(String.class)) { 263 return null; 264 } 265 return defaultValue; 266 } 267 268 /** 269 * Return the default type this <code>Converter</code> handles. 270 * 271 * @return The default type this <code>Converter</code> handles. 272 */ 273 protected abstract Class<?> getDefaultType(); 274 275 /** 276 * Handle Conversion Errors. 277 * <p> 278 * If a default value has been specified then it is returned 279 * otherwise a ConversionException is thrown. 280 * 281 * @param <T> Target type of the conversion. 282 * @param type Data type to which this value should be converted. 283 * @param value The input value to be converted 284 * @param cause The exception thrown by the <code>convert</code> method 285 * @return The default value. 286 * @throws ConversionException if no default value has been 287 * specified for this {@link Converter}. 288 */ 289 protected <T> T handleError(final Class<T> type, final Object value, final Throwable cause) { 290 if (log().isDebugEnabled()) { 291 if (cause instanceof ConversionException) { 292 log().debug(" Conversion threw ConversionException: " + cause.getMessage()); 293 } else { 294 log().debug(" Conversion threw " + cause); 295 } 296 } 297 298 if (useDefault) { 299 return handleMissing(type); 300 } 301 302 ConversionException cex = null; 303 if (cause instanceof ConversionException) { 304 cex = (ConversionException)cause; 305 if (log().isDebugEnabled()) { 306 log().debug(" Re-throwing ConversionException: " + cex.getMessage()); 307 log().debug(" " + DEFAULT_CONFIG_MSG); 308 } 309 } else { 310 final String msg = "Error converting from '" + toString(value.getClass()) + 311 "' to '" + toString(type) + "' " + cause.getMessage(); 312 cex = new ConversionException(msg, cause); 313 if (log().isDebugEnabled()) { 314 log().debug(" Throwing ConversionException: " + msg); 315 log().debug(" " + DEFAULT_CONFIG_MSG); 316 } 317 } 318 319 throw cex; 320 321 } 322 323 /** 324 * Handle missing values. 325 * <p> 326 * If a default value has been specified, then it is returned (after a cast 327 * to the desired target class); otherwise a ConversionException is thrown. 328 * 329 * @param <T> the desired target type 330 * @param type Data type to which this value should be converted. 331 * @return The default value. 332 * @throws ConversionException if no default value has been 333 * specified for this {@link Converter}. 334 */ 335 protected <T> T handleMissing(final Class<T> type) { 336 337 if (useDefault || type.equals(String.class)) { 338 Object value = getDefault(type); 339 if (useDefault && value != null && !type.equals(value.getClass())) { 340 try { 341 value = convertToType(type, defaultValue); 342 } catch (final Throwable t) { 343 throw new ConversionException("Default conversion to " + toString(type) 344 + " failed.", t); 345 } 346 } 347 if (log().isDebugEnabled()) { 348 log().debug(" Using default " 349 + (value == null ? "" : toString(value.getClass()) + " ") 350 + "value '" + defaultValue + "'"); 351 } 352 // value is now either null or of the desired target type 353 return type.cast(value); 354 } 355 356 final ConversionException cex = new ConversionException("No value specified for '" + 357 toString(type) + "'"); 358 if (log().isDebugEnabled()) { 359 log().debug(" Throwing ConversionException: " + cex.getMessage()); 360 log().debug(" " + DEFAULT_CONFIG_MSG); 361 } 362 throw cex; 363 364 } 365 366 /** 367 * Indicates whether a default value will be returned or exception 368 * thrown in the event of a conversion error. 369 * 370 * @return <code>true</code> if a default value will be returned for 371 * conversion errors or <code>false</code> if a {@link ConversionException} 372 * will be thrown. 373 */ 374 public boolean isUseDefault() { 375 return useDefault; 376 } 377 378 /** 379 * Accessor method for Log instance. 380 * <p> 381 * The Log instance variable is transient and 382 * accessing it through this method ensures it 383 * is re-initialized when this instance is 384 * de-serialized. 385 * 386 * @return The Log instance. 387 */ 388 Log log() { 389 if (log == null) { 390 log = LogFactory.getLog(getClass()); 391 } 392 return log; 393 } 394 395 /** 396 * Set the default value, converting as required. 397 * <p> 398 * If the default value is different from the type the 399 * <code>Converter</code> handles, it will be converted 400 * to the handled type. 401 * 402 * @param defaultValue The default value to be returned 403 * if the value to be converted is missing or an error 404 * occurs converting the value. 405 * @throws ConversionException if an error occurs converting 406 * the default value 407 */ 408 protected void setDefaultValue(final Object defaultValue) { 409 useDefault = false; 410 if (log().isDebugEnabled()) { 411 log().debug("Setting default value: " + defaultValue); 412 } 413 if (defaultValue == null) { 414 this.defaultValue = null; 415 } else { 416 this.defaultValue = convert(getDefaultType(), defaultValue); 417 } 418 useDefault = true; 419 } 420 421 /** 422 * Provide a String representation of this converter. 423 * 424 * @return A String representation of this converter 425 */ 426 @Override 427 public String toString() { 428 return toString(getClass()) + "[UseDefault=" + useDefault + "]"; 429 } 430 431 /** 432 * Provide a String representation of a <code>java.lang.Class</code>. 433 * @param type The <code>java.lang.Class</code>. 434 * @return The String representation. 435 */ 436 String toString(final Class<?> type) { 437 String typeName = null; 438 if (type == null) { 439 typeName = "null"; 440 } else if (type.isArray()) { 441 Class<?> elementType = type.getComponentType(); 442 int count = 1; 443 while (elementType.isArray()) { 444 elementType = elementType .getComponentType(); 445 count++; 446 } 447 typeName = elementType.getName(); 448 for (int i = 0; i < count; i++) { 449 typeName += "[]"; 450 } 451 } else { 452 typeName = type.getName(); 453 } 454 if (typeName.startsWith("java.lang.") || 455 typeName.startsWith("java.util.") || 456 typeName.startsWith("java.math.")) { 457 typeName = typeName.substring("java.lang.".length()); 458 } else if (typeName.startsWith(PACKAGE)) { 459 typeName = typeName.substring(PACKAGE.length()); 460 } 461 return typeName; 462 } 463}