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    }