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