View Javadoc

    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: 555824 $ $Date: 2007-07-13 01:27:15 +0100 (Fri, 13 Jul 2007) $
    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      /**</package-summary/html">Current package name *//package-summary.html">em>* 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       * The default type this <code>Converter</code> handles.
    68       */
    69      private Class defaultType = null;
    70
    71      /***
    72       * Should we return the default value on conversion errors?
    73       */
    74      private boolean useDefault = false;
    75
    76      /***
    77       * The default value specified to our Constructor, if any.
    78       */
    79      private Object defaultValue = null;
    80
    81      // ----------------------------------------------------------- Constructors
    82
    83      /***
    84       * Construct a <i>Converter</i> that throws a
    85       * <code>ConversionException</code> if an error occurs.
    86       *
    87       * @param defaultType The default type this <code>Converter</code>
    88       * handles
    89       */
    90      public AbstractConverter(Class defaultType) {
    91          this.defaultType = defaultType;
    92          if (defaultType == null) {
    93              throw new IllegalArgumentException("Default type is missing.");
    94          }
    95      }
    96
    97      /***
    98       * Construct a <i>Converter</i> that returns a default
    99       * value if an error occurs.
    100      *
    101      * @param defaultType The default type this <code>Converter</code>
    102      * handles
    103      * @param defaultValue The default value to be returned
    104      * if the value to be converted is missing or an error
    105      * occurs converting the value.
    106      */
    107     public AbstractConverter(Class defaultType, Object defaultValue) {
    108         this(defaultType);
    109         setDefaultValue(defaultValue);
    110     }
    111
    112     // --------------------------------------------------------- Public Methods
    113
    114     /***
    115      * Indicates whether a default value will be returned or exception
    116      * thrown in the event of a conversion error.
    117      *
    118      * @return <code>true</code> if a default value will be returned for
    119      * conversion errors or <code>false</code> if a {@link ConversionException}
    120      * will be thrown.
    121      */
    122     public boolean isUseDefault() {
    123         return useDefault;
    124     }
    125
    126     /***
    127      * Convert the input object into an output object of the
    128      * specified type.
    129      *
    130      * @param type Data type to which this value should be converted
    131      * @param value The input value to be converted
    132      * @return The converted value.
    133      * @throws ConversionException if conversion cannot be performed
    134      * successfully and no default is specified.
    135      */
    136     public Object convert(Class type, Object value) {
    137
    138         Class sourceType  = value == null ? null : value.getClass();
    139         Class targetType  = primitive(type  == null ? getDefaultType() : type);
    140
    141         if (log().isDebugEnabled()) {
    142             log().debug("Converting"
    143                     + (value == null ? "" : " '" + toString(sourceType) + "'")
    144                     + " value '" + value + "' to type '" + toString(targetType) + "'");
    145         }
    146
    147         value = convertArray(value);
    148
    149         // Missing Value
    150         if (value == null) {
    151             return handleMissing(targetType);
    152         }
    153
    154         sourceType = value.getClass();
    155
    156         try {
    157             // Convert --> String
    158             if (targetType.equals(String.class)) {
    159                 return convertToString(value);
    160
    161             // No conversion necessary
    162             } else if (targetType.equals(sourceType)) {
    163                 if (log().isDebugEnabled()) {
    164                     log().debug("    No conversion required, value is already a "
    165                                     + toString(targetType));
    166                 }
    167                 return value;
    168
    169             // Convert --> Type
    170             } else {
    171                 Object result = convertToType(targetType, value);
    172                 if (log().isDebugEnabled()) {
    173                     log().debug("    Converted to " + toString(targetType) +
    174                                    " value '" + result + "'");
    175                 }
    176                 return result;
    177             }
    178         } catch (Throwable t) {
    179             return handleError(targetType, value, t);
    180         }
    181
    182     }
    183
    184     /***
    185      * Convert the input object into a String.
    186      * <p>
    187      * <b>N.B.</b>This implementation simply uses the value's
    188      * <code>toString()</code> method and should be overriden if a
    189      * more sophisticated mechanism for <i>conversion to a String</i>
    190      * is required.
    191      *
    192      * @param value The input value to be converted.
    193      * @return the converted String value.
    194      * @throws Throwable if an error occurs converting to a String
    195      */
    196     protected String convertToString(Object value) throws Throwable {
    197         return value.toString();
    198     }
    199
    200     /***
    201      * Convert the input object into an output object of the
    202      * specified type.
    203      * <p>
    204      * Typical implementations will provide a minimum of
    205      * <code>String --> type</code> conversion.
    206      *
    207      * @param type Data type to which this value should be converted.
    208      * @param value The input value to be converted.
    209      * @return The converted value.
    210      * @throws Throwable if an error occurs converting to the specified type
    211      */
    212     protected abstract Object convertToType(Class type, Object value) throws Throwable;
    213
    214     /***
    215      * Return the first element from an Array (or Collection)
    216      * or the value unchanged if not an Array (or Collection).
    217      *
    218      * N.B. This needs to be overriden for array/Collection converters.
    219      *
    220      * @param value The value to convert
    221      * @return The first element in an Array (or Collection)
    222      * or the value unchanged if not an Array (or Collection)
    223      */
    224     protected Object convertArray(Object value) {
    225         if (value == null) {
    226             return null;
    227         }
    228         if (value.getClass().isArray()) {
    229             if (Array.getLength(value) > 0) {
    230                 return Array.get(value, 0);
    231             } else {
    232                 return null;
    233             }
    234         }
    235         if (value instanceof Collection) {
    236             Collection collection = (Collection)value;
    237             if (collection.size() > 0) {
    238                 return collection.iterator().next();
    239             } else {
    240                 return null;
    241             }
    242         }
    243         return value;
    244     }
    245
    246     /***
    247      * Handle Conversion Errors.
    248      * <p>
    249      * If a default value has been specified then it is returned
    250      * otherwise a ConversionException is thrown.
    251      *
    252      * @param type Data type to which this value should be converted.
    253      * @param value The input value to be converted
    254      * @param cause The exception thrown by the <code>convert</code> method
    255      * @return The default value.
    256      * @throws ConversionException if no default value has been
    257      * specified for this {@link Converter}.
    258      */
    259     protected Object handleError(Class type, Object value, Throwable cause) {
    260         if (log().isDebugEnabled()) {
    261             if (cause instanceof ConversionException) {
    262                 log().debug("    Conversion threw ConversionException: " + cause.getMessage());
    263             } else {
    264                 log().debug("    Conversion threw " + cause);
    265             }
    266         }
    267
    268         if (useDefault) {
    269             return handleMissing(type);
    270         }
    271
    272         ConversionException cex = null;
    273         if (cause instanceof ConversionException) {
    274             cex = (ConversionException)cause;
    275             if (log().isDebugEnabled()) {
    276                 log().debug("    Re-throwing ConversionException: " + cex.getMessage());
    277                 log().debug("    " + DEFAULT_CONFIG_MSG);
    278             }
    279         } else {
    280             String msg = "Error converting from '" + toString(value.getClass()) +
    281                     "' to '" + toString(type) + "' " + cause.getMessage();
    282             cex = new ConversionException(msg, cause);
    283             if (log().isDebugEnabled()) {
    284                 log().debug("    Throwing ConversionException: " + msg);
    285                 log().debug("    " + DEFAULT_CONFIG_MSG);
    286             }
    287             BeanUtils.initCause(cex, cause);
    288         }
    289
    290         throw cex;
    291
    292     }
    293
    294     /***
    295      * Handle missing values.
    296      * <p>
    297      * If a default value has been specified then it is returned
    298      * otherwise a ConversionException is thrown.
    299      *
    300      * @param type Data type to which this value should be converted.
    301      * @return The default value.
    302      * @throws ConversionException if no default value has been
    303      * specified for this {@link Converter}.
    304      */
    305     protected Object handleMissing(Class type) {
    306
    307         if (useDefault || type.equals(String.class)) {
    308             Object value = getDefault(type);
    309             if (useDefault && value != null && !(type.equals(value.getClass()))) {
    310                 try {
    311                     value = convertToType(type, defaultValue);
    312                 } catch (Throwable t) {
    313                     log().error("    Default conversion to " + toString(type)
    314                             + "failed: " + t);
    315                 }
    316             }
    317             if (log().isDebugEnabled()) {
    318                 log().debug("    Using default "
    319                         + (value == null ? "" : toString(value.getClass()) + " ")
    320                         + "value '" + defaultValue + "'");
    321             }
    322             return value;
    323         }
    324
    325         ConversionException cex =  new ConversionException("No value specified for '" +
    326                 toString(type) + "'");
    327         if (log().isDebugEnabled()) {
    328             log().debug("    Throwing ConversionException: " + cex.getMessage());
    329             log().debug("    " + DEFAULT_CONFIG_MSG);
    330         }
    331         throw cex;
    332
    333     }
    334
    335     /***
    336      * Set the default value, converting as required.
    337      * <p>
    338      * If the default value is different from the type the
    339      * <code>Converter</code> handles, it will be converted
    340      * to the handled type.
    341      *
    342      * @param defaultValue The default value to be returned
    343      * if the value to be converted is missing or an error
    344      * occurs converting the value.
    345      * @throws ConversionException if an error occurs converting
    346      * the default value
    347      */
    348     protected void setDefaultValue(Object defaultValue) {
    349         useDefault = false;
    350         if (log().isDebugEnabled()) {
    351             log().debug("Setting default value: " + defaultValue);
    352         }
    353         if (defaultValue == null) {
    354            this.defaultValue  = null;
    355         } else {
    356            this.defaultValue  = convert(getDefaultType(), defaultValue);
    357         }
    358         useDefault = true;
    359     }
    360
    361     /***
    362      * Return the default type this <code>Converter</code> handles.
    363      *
    364      * @return The default type this <code>Converter</code> handles.
    365      */
    366     protected Class getDefaultType() {
    367         return defaultType;
    368     }
    369
    370     /***
    371      * Return the default value for conversions to the specified
    372      * type.
    373      * @param type Data type to which this value should be converted.
    374      * @return The default value for the specified type.
    375      */
    376     protected Object getDefault(Class type) {
    377         if (type.equals(String.class)) {
    378             return null;
    379         } else {
    380             return defaultValue;
    381         }
    382     }
    383
    384     /***
    385      * Provide a String representation of this converter.
    386      *
    387      * @return A String representation of this converter
    388      */
    389     public String toString() {
    390         return toString(getClass()) + "[UseDefault=" + useDefault + "]";
    391     }
    392
    393     // ----------------------------------------------------------- Package Methods
    394
    395     /***
    396      * Accessor method for Log instance.
    397      * <p>
    398      * The Log instance variable is transient and
    399      * accessing it through this method ensures it
    400      * is re-initialized when this instance is
    401      * de-serialized.
    402      *
    403      * @return The Log instance.
    404      */
    405     Log log() {
    406         if (log == null) {
    407             log = LogFactory.getLog(getClass());
    408         }
    409         return log;
    410     }
    411
    412     /***
    413      * Change primitve Class types to the associated wrapper class.
    414      * @param type The class type to check.
    415      * @return The converted type.
    416      */
    417      Class primitive(Class type) {
    418         if (type == null || !type.isPrimitive()) {
    419             return type;
    420         }
    421
    422         if (type == Integer.TYPE) {
    423             return Integer.class;
    424         } else if (type == Double.TYPE) {
    425             return Double.class;
    426         } else if (type == Long.TYPE) {
    427             return Long.class;
    428         } else if (type == Boolean.TYPE) {
    429             return Boolean.class;
    430         } else if (type == Float.TYPE) {
    431             return Float.class;
    432         } else if (type == Short.TYPE) {
    433             return Short.class;
    434         } else if (type == Byte.TYPE) {
    435             return Byte.class;
    436         } else if (type == Character.TYPE) {
    437             return Character.class;
    438         } else {
    439             return type;
    440         }
    441     }
    442
    443     /***
    444      * Provide a String representation of a <code>java.lang.Class</code>.
    445      * @param type The <code>java.lang.Class</code>.
    446      * @return The String representation.
    447      */
    448     String toString(Class type) {
    449         String typeName = null;
    450         if (type == null) {
    451             typeName = "null";
    452         } else if (type.isArray()) {
    453             Class elementType = type.getComponentType();
    454             int count = 1;
    455             while (elementType.isArray()) {
    456                 elementType = elementType .getComponentType();
    457                 count++;
    458             }
    459             typeName = elementType.getName();
    460             for (int i = 0; i < count; i++) {
    461                 typeName += "[]";
    462             }
    463         } else {
    464             typeName = type.getName();
    465         }
    466         if (typeName.startsWith("java.lang.") ||
    467             typeName.startsWith("java.util.") ||
    468             typeName.startsWith("java.math.")) {
    469             typeName = typeName.substring("java.lang.".length());
    470         } else if (typeName.startsWith(PACKAGE)) {
    471             typeName = typeName.substring(PACKAGE.length());
    472         }
    473         return typeName;
    474     }
    475 }