| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| AbstractConverter |
|
| 5.6875;5.688 |
| 1 | /* | |
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
| 3 | * contributor license agreements. See the NOTICE file distributed with | |
| 4 | * this work for additional information regarding copyright ownership. | |
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| 6 | * (the "License"); you may not use this file except in compliance with | |
| 7 | * the License. You may obtain a copy of the License at | |
| 8 | * | |
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | * | |
| 11 | * Unless required by applicable law or agreed to in writing, software | |
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | * See the License for the specific language governing permissions and | |
| 15 | * limitations under the License. | |
| 16 | */ | |
| 17 | package org.apache.commons.beanutils.converters; | |
| 18 | ||
| 19 | import java.lang.reflect.Array; | |
| 20 | import java.util.Collection; | |
| 21 | import org.apache.commons.logging.Log; | |
| 22 | import org.apache.commons.logging.LogFactory; | |
| 23 | import org.apache.commons.beanutils.BeanUtils; | |
| 24 | import org.apache.commons.beanutils.ConversionException; | |
| 25 | import org.apache.commons.beanutils.Converter; | |
| 26 | ||
| 27 | /** | |
| 28 | * Base {@link Converter} implementation that provides the structure | |
| 29 | * for handling conversion <b>to</b> and <b>from</b> a specified type. | |
| 30 | * <p> | |
| 31 | * This implementation provides the basic structure for | |
| 32 | * converting to/from a specified type optionally using a default | |
| 33 | * value or throwing a {@link ConversionException} if a | |
| 34 | * conversion error occurs. | |
| 35 | * <p> | |
| 36 | * Implementations should provide conversion to the specified | |
| 37 | * type and from the specified type to a <code>String</code> value | |
| 38 | * by implementing the following methods: | |
| 39 | * <ul> | |
| 40 | * <li><code>convertToString(value)</code> - convert to a String | |
| 41 | * (default implementation uses the objects <code>toString()</code> | |
| 42 | * method).</li> | |
| 43 | * <li><code>convertToType(Class, value)</code> - convert | |
| 44 | * to the specified type</li> | |
| 45 | * </ul> | |
| 46 | * | |
| 47 | * @version $Revision: 640131 $ $Date: 2008-03-22 22:10:31 -0400 (Sat, 22 Mar 2008) $ | |
| 48 | * @since 1.8.0 | |
| 49 | */ | |
| 50 | public abstract class AbstractConverter implements Converter { | |
| 51 | ||
| 52 | /** Debug logging message to indicate default value configuration */ | |
| 53 | private static final String DEFAULT_CONFIG_MSG = | |
| 54 | "(N.B. Converters can be configured to use default values to avoid throwing exceptions)"; | |
| 55 | ||
| 56 | /** Current package name */ | |
| 57 | // getPackage() below returns null on some platforms/jvm versions during the unit tests. | |
| 58 | // private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + "."; | |
| 59 | private static final String PACKAGE = "org.apache.commons.beanutils.converters."; | |
| 60 | ||
| 61 | /** | |
| 62 | * Logging for this instance. | |
| 63 | */ | |
| 64 | private transient Log log; | |
| 65 | ||
| 66 | /** | |
| 67 | * Should we return the default value on conversion errors? | |
| 68 | */ | |
| 69 | 30225 | private boolean useDefault = false; |
| 70 | ||
| 71 | /** | |
| 72 | * The default value specified to our Constructor, if any. | |
| 73 | */ | |
| 74 | 30225 | private Object defaultValue = null; |
| 75 | ||
| 76 | // ----------------------------------------------------------- Constructors | |
| 77 | ||
| 78 | /** | |
| 79 | * Construct a <i>Converter</i> that throws a | |
| 80 | * <code>ConversionException</code> if an error occurs. | |
| 81 | */ | |
| 82 | 29034 | public AbstractConverter() { |
| 83 | 29034 | } |
| 84 | ||
| 85 | /** | |
| 86 | * Construct a <i>Converter</i> that returns a default | |
| 87 | * value if an error occurs. | |
| 88 | * | |
| 89 | * @param defaultValue The default value to be returned | |
| 90 | * if the value to be converted is missing or an error | |
| 91 | * occurs converting the value. | |
| 92 | */ | |
| 93 | 1191 | public AbstractConverter(Object defaultValue) { |
| 94 | 1191 | setDefaultValue(defaultValue); |
| 95 | 1191 | } |
| 96 | ||
| 97 | // --------------------------------------------------------- Public Methods | |
| 98 | ||
| 99 | /** | |
| 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 | 182 | 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 | 18498 | Class sourceType = value == null ? null : value.getClass(); |
| 124 | 18498 | Class targetType = primitive(type == null ? getDefaultType() : type); |
| 125 | ||
| 126 | 18498 | if (log().isDebugEnabled()) { |
| 127 | 0 | log().debug("Converting" |
| 128 | + (value == null ? "" : " '" + toString(sourceType) + "'") | |
| 129 | + " value '" + value + "' to type '" + toString(targetType) + "'"); | |
| 130 | } | |
| 131 | ||
| 132 | 18498 | value = convertArray(value); |
| 133 | ||
| 134 | // Missing Value | |
| 135 | 18498 | if (value == null) { |
| 136 | 112 | return handleMissing(targetType); |
| 137 | } | |
| 138 | ||
| 139 | 18386 | sourceType = value.getClass(); |
| 140 | ||
| 141 | try { | |
| 142 | // Convert --> String | |
| 143 | 18386 | if (targetType.equals(String.class)) { |
| 144 | 759 | return convertToString(value); |
| 145 | ||
| 146 | // No conversion necessary | |
| 147 | 17627 | } else if (targetType.equals(sourceType)) { |
| 148 | 12740 | if (log().isDebugEnabled()) { |
| 149 | 0 | log().debug(" No conversion required, value is already a " |
| 150 | + toString(targetType)); | |
| 151 | } | |
| 152 | 12740 | return value; |
| 153 | ||
| 154 | // Convert --> Type | |
| 155 | } else { | |
| 156 | 4887 | Object result = convertToType(targetType, value); |
| 157 | 4634 | if (log().isDebugEnabled()) { |
| 158 | 0 | log().debug(" Converted to " + toString(targetType) + |
| 159 | " value '" + result + "'"); | |
| 160 | } | |
| 161 | 4634 | return result; |
| 162 | } | |
| 163 | 253 | } catch (Throwable t) { |
| 164 | 253 | 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 | 573 | 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 | 8661 | if (value == null) { |
| 211 | 94 | return null; |
| 212 | } | |
| 213 | 8567 | if (value.getClass().isArray()) { |
| 214 | 40 | if (Array.getLength(value) > 0) { |
| 215 | 40 | return Array.get(value, 0); |
| 216 | } else { | |
| 217 | 0 | return null; |
| 218 | } | |
| 219 | } | |
| 220 | 8527 | if (value instanceof Collection) { |
| 221 | 2 | Collection collection = (Collection)value; |
| 222 | 2 | if (collection.size() > 0) { |
| 223 | 2 | return collection.iterator().next(); |
| 224 | } else { | |
| 225 | 0 | return null; |
| 226 | } | |
| 227 | } | |
| 228 | 8525 | 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 | 253 | if (log().isDebugEnabled()) { |
| 246 | 0 | if (cause instanceof ConversionException) { |
| 247 | 0 | log().debug(" Conversion threw ConversionException: " + cause.getMessage()); |
| 248 | } else { | |
| 249 | 0 | log().debug(" Conversion threw " + cause); |
| 250 | } | |
| 251 | } | |
| 252 | ||
| 253 | 253 | if (useDefault) { |
| 254 | 81 | return handleMissing(type); |
| 255 | } | |
| 256 | ||
| 257 | 172 | ConversionException cex = null; |
| 258 | 172 | if (cause instanceof ConversionException) { |
| 259 | 131 | cex = (ConversionException)cause; |
| 260 | 131 | if (log().isDebugEnabled()) { |
| 261 | 0 | log().debug(" Re-throwing ConversionException: " + cex.getMessage()); |
| 262 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
| 263 | } | |
| 264 | } else { | |
| 265 | 41 | String msg = "Error converting from '" + toString(value.getClass()) + |
| 266 | "' to '" + toString(type) + "' " + cause.getMessage(); | |
| 267 | 41 | cex = new ConversionException(msg, cause); |
| 268 | 41 | if (log().isDebugEnabled()) { |
| 269 | 0 | log().debug(" Throwing ConversionException: " + msg); |
| 270 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
| 271 | } | |
| 272 | 41 | BeanUtils.initCause(cex, cause); |
| 273 | } | |
| 274 | ||
| 275 | 172 | 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 | 215 | if (useDefault || type.equals(String.class)) { |
| 293 | 170 | Object value = getDefault(type); |
| 294 | 170 | if (useDefault && value != null && !(type.equals(value.getClass()))) { |
| 295 | try { | |
| 296 | 20 | value = convertToType(type, defaultValue); |
| 297 | 0 | } catch (Throwable t) { |
| 298 | 0 | log().error(" Default conversion to " + toString(type) |
| 299 | + "failed: " + t); | |
| 300 | 20 | } |
| 301 | } | |
| 302 | 170 | if (log().isDebugEnabled()) { |
| 303 | 0 | log().debug(" Using default " |
| 304 | + (value == null ? "" : toString(value.getClass()) + " ") | |
| 305 | + "value '" + defaultValue + "'"); | |
| 306 | } | |
| 307 | 170 | return value; |
| 308 | } | |
| 309 | ||
| 310 | 45 | ConversionException cex = new ConversionException("No value specified for '" + |
| 311 | toString(type) + "'"); | |
| 312 | 45 | if (log().isDebugEnabled()) { |
| 313 | 0 | log().debug(" Throwing ConversionException: " + cex.getMessage()); |
| 314 | 0 | log().debug(" " + DEFAULT_CONFIG_MSG); |
| 315 | } | |
| 316 | 45 | 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 | 16633 | useDefault = false; |
| 335 | 16633 | if (log().isDebugEnabled()) { |
| 336 | 0 | log().debug("Setting default value: " + defaultValue); |
| 337 | } | |
| 338 | 16633 | if (defaultValue == null) { |
| 339 | 118 | this.defaultValue = null; |
| 340 | } else { | |
| 341 | 16515 | this.defaultValue = convert(getDefaultType(), defaultValue); |
| 342 | } | |
| 343 | 16633 | useDefault = true; |
| 344 | 16633 | } |
| 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 | 169 | if (type.equals(String.class)) { |
| 361 | 36 | return null; |
| 362 | } else { | |
| 363 | 133 | 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 | 50 | 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 | 54347 | if (log == null) { |
| 390 | 16935 | log = LogFactory.getLog(getClass()); |
| 391 | } | |
| 392 | 54347 | 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 | 18498 | if (type == null || !type.isPrimitive()) { |
| 402 | 18004 | return type; |
| 403 | } | |
| 404 | ||
| 405 | 494 | if (type == Integer.TYPE) { |
| 406 | 159 | return Integer.class; |
| 407 | 335 | } else if (type == Double.TYPE) { |
| 408 | 60 | return Double.class; |
| 409 | 275 | } else if (type == Long.TYPE) { |
| 410 | 73 | return Long.class; |
| 411 | 202 | } else if (type == Boolean.TYPE) { |
| 412 | 42 | return Boolean.class; |
| 413 | 160 | } else if (type == Float.TYPE) { |
| 414 | 54 | return Float.class; |
| 415 | 106 | } else if (type == Short.TYPE) { |
| 416 | 54 | return Short.class; |
| 417 | 52 | } else if (type == Byte.TYPE) { |
| 418 | 51 | return Byte.class; |
| 419 | 1 | } else if (type == Character.TYPE) { |
| 420 | 1 | return Character.class; |
| 421 | } else { | |
| 422 | 0 | 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 | 709 | String typeName = null; |
| 433 | 709 | if (type == null) { |
| 434 | 0 | typeName = "null"; |
| 435 | 709 | } else if (type.isArray()) { |
| 436 | 0 | Class elementType = type.getComponentType(); |
| 437 | 0 | int count = 1; |
| 438 | 0 | while (elementType.isArray()) { |
| 439 | 0 | elementType = elementType .getComponentType(); |
| 440 | 0 | count++; |
| 441 | } | |
| 442 | 0 | typeName = elementType.getName(); |
| 443 | 0 | for (int i = 0; i < count; i++) { |
| 444 | 0 | typeName += "[]"; |
| 445 | } | |
| 446 | 0 | } else { |
| 447 | 709 | typeName = type.getName(); |
| 448 | } | |
| 449 | 709 | if (typeName.startsWith("java.lang.") || |
| 450 | typeName.startsWith("java.util.") || | |
| 451 | typeName.startsWith("java.math.")) { | |
| 452 | 366 | typeName = typeName.substring("java.lang.".length()); |
| 453 | 343 | } else if (typeName.startsWith(PACKAGE)) { |
| 454 | 255 | typeName = typeName.substring(PACKAGE.length()); |
| 455 | } | |
| 456 | 709 | return typeName; |
| 457 | } | |
| 458 | } |