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 }