Coverage Report - org.apache.commons.beanutils.converters.AbstractConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractConverter
77%
99/128
75%
75/100
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  
 }