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 */
017package org.apache.commons.beanutils.converters;
018
019import java.lang.reflect.Array;
020import java.util.Collection;
021
022import org.apache.commons.beanutils.ConversionException;
023import org.apache.commons.beanutils.ConvertUtils;
024import org.apache.commons.beanutils.Converter;
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027
028/**
029 * Base {@link Converter} implementation that provides the structure
030 * for handling conversion <strong>to</strong> and <strong>from</strong> a specified type.
031 * <p>
032 * This implementation provides the basic structure for
033 * converting to/from a specified type optionally using a default
034 * value or throwing a {@link ConversionException} if a
035 * conversion error occurs.
036 * <p>
037 * Implementations should provide conversion to the specified
038 * type and from the specified type to a <code>String</code> value
039 * by implementing the following methods:
040 * <ul>
041 *     <li><code>convertToString(value)</code> - convert to a String
042 *        (default implementation uses the objects <code>toString()</code>
043 *        method).</li>
044 *     <li><code>convertToType(Class, value)</code> - convert
045 *         to the specified type</li>
046 * </ul>
047 * <p>
048 * The default value has to be compliant to the default type of this
049 * converter - which is enforced by the generic type parameter. If a
050 * conversion is not possible and a default value is set, the converter
051 * tries to transform the default value to the requested target type.
052 * If this fails, a {@code ConversionException} if thrown.
053 *
054 * @since 1.8.0
055 */
056public abstract class AbstractConverter implements Converter {
057
058    /** Debug logging message to indicate default value configuration */
059    private static final String DEFAULT_CONFIG_MSG =
060        "(N.B. Converters can be configured to use default values to avoid throwing exceptions)";
061
062    /** Current package name */
063    //    getPackage() below returns null on some platforms/jvm versions during the unit tests.
064//    private static final String PACKAGE = AbstractConverter.class.getPackage().getName() + ".";
065    private static final String PACKAGE = "org.apache.commons.beanutils.converters.";
066
067    /**
068     * Logging for this instance.
069     */
070    private transient Log log;
071
072    /**
073     * Should we return the default value on conversion errors?
074     */
075    private boolean useDefault;
076
077    /**
078     * The default value specified to our Constructor, if any.
079     */
080    private Object defaultValue;
081
082    /**
083     * Construct a <em>Converter</em> that throws a
084     * <code>ConversionException</code> if an error occurs.
085     */
086    public AbstractConverter() {
087    }
088
089    /**
090     * Construct a <em>Converter</em> that returns a default
091     * value if an error occurs.
092     *
093     * @param defaultValue The default value to be returned
094     * if the value to be converted is missing or an error
095     * occurs converting the value.
096     */
097    public AbstractConverter(final Object defaultValue) {
098        setDefaultValue(defaultValue);
099    }
100
101    /**
102     * Generates a standard conversion exception with a message indicating that
103     * the passed in value cannot be converted to the desired target type.
104     *
105     * @param type the target type
106     * @param value the value to be converted
107     * @return a {@code ConversionException} with a standard message
108     * @since 1.9
109     */
110    protected ConversionException conversionException(final Class<?> type, final Object value) {
111        return new ConversionException("Can't convert value '" + value
112                + "' to type " + type);
113    }
114
115    /**
116     * Convert the input object into an output object of the
117     * specified type.
118     *
119     * @param <T> the target type of the conversion
120     * @param type Data type to which this value should be converted
121     * @param value The input value to be converted
122     * @return The converted value.
123     * @throws ConversionException if conversion cannot be performed
124     * successfully and no default is specified.
125     */
126    @Override
127    public <T> T convert(final Class<T> type, Object value) {
128
129        if (type == null) {
130            return convertToDefaultType(type, value);
131        }
132
133        Class<?> sourceType  = value == null ? null : value.getClass();
134        final Class<T> targetType  = ConvertUtils.primitiveToWrapper(type);
135
136        if (log().isDebugEnabled()) {
137            log().debug("Converting"
138                    + (value == null ? "" : " '" + toString(sourceType) + "'")
139                    + " value '" + value + "' to type '" + toString(targetType) + "'");
140        }
141
142        value = convertArray(value);
143
144        // Missing Value
145        if (value == null) {
146            return handleMissing(targetType);
147        }
148
149        sourceType = value.getClass();
150
151        try {
152            // Convert --> String
153            if (targetType.equals(String.class)) {
154                return targetType.cast(convertToString(value));
155
156            // No conversion necessary
157            }
158            if (targetType.equals(sourceType)) {
159                if (log().isDebugEnabled()) {
160                    log().debug("    No conversion required, value is already a "
161                                    + toString(targetType));
162                }
163                return targetType.cast(value);
164
165            // Convert --> Type
166            }
167            final Object result = convertToType(targetType, value);
168            if (log().isDebugEnabled()) {
169                log().debug("    Converted to " + toString(targetType) +
170                               " value '" + result + "'");
171            }
172            return targetType.cast(result);
173        } catch (final Throwable t) {
174            return handleError(targetType, value, t);
175        }
176
177    }
178
179    /**
180     * Return the first element from an Array (or Collection)
181     * or the value unchanged if not an Array (or Collection).
182     *
183     * N.B. This needs to be overriden for array/Collection converters.
184     *
185     * @param value The value to convert
186     * @return The first element in an Array (or Collection)
187     * or the value unchanged if not an Array (or Collection)
188     */
189    protected Object convertArray(final Object value) {
190        if (value == null) {
191            return null;
192        }
193        if (value.getClass().isArray()) {
194            if (Array.getLength(value) > 0) {
195                return Array.get(value, 0);
196            }
197            return null;
198        }
199        if (value instanceof Collection) {
200            final Collection<?> collection = (Collection<?>)value;
201            if (collection.size() > 0) {
202                return collection.iterator().next();
203            }
204            return null;
205        }
206        return value;
207    }
208
209    /**
210     * Performs a conversion to the default type. This method is called if we do
211     * not have a target class. In this case, the T parameter is not set.
212     * Therefore, we can cast to it (which is required to fulfill the contract
213     * of the method signature).
214     *
215     * @param <T> the type of the result object
216     * @param targetClass the target class of the conversion
217     * @param value the value to be converted
218     * @return the converted value
219     */
220    private <T> T convertToDefaultType(final Class<T> targetClass, final Object value) {
221        return (T) convert(getDefaultType(), value);
222    }
223
224    /**
225     * Convert the input object into a String.
226     * <p>
227     * <strong>N.B.</strong>This implementation simply uses the value's
228     * <code>toString()</code> method and should be overriden if a
229     * more sophisticated mechanism for <em>conversion to a String</em>
230     * is required.
231     *
232     * @param value The input value to be converted.
233     * @return the converted String value.
234     * @throws Throwable if an error occurs converting to a String
235     */
236    protected String convertToString(final Object value) throws Throwable {
237        return value.toString();
238    }
239
240    /**
241     * Convert the input object into an output object of the
242     * specified type.
243     * <p>
244     * Typical implementations will provide a minimum of
245     * <code>String to type</code> conversion.
246     *
247     * @param <T> Target type of the conversion.
248     * @param type Data type to which this value should be converted.
249     * @param value The input value to be converted.
250     * @return The converted value.
251     * @throws Throwable if an error occurs converting to the specified type
252     */
253    protected abstract <T> T convertToType(Class<T> type, Object value) throws Throwable;
254
255    /**
256     * Return the default value for conversions to the specified
257     * type.
258     * @param type Data type to which this value should be converted.
259     * @return The default value for the specified type.
260     */
261    protected Object getDefault(final Class<?> type) {
262        if (type.equals(String.class)) {
263            return null;
264        }
265        return defaultValue;
266    }
267
268    /**
269     * Return the default type this <code>Converter</code> handles.
270     *
271     * @return The default type this <code>Converter</code> handles.
272     */
273    protected abstract Class<?> getDefaultType();
274
275    /**
276     * Handle Conversion Errors.
277     * <p>
278     * If a default value has been specified then it is returned
279     * otherwise a ConversionException is thrown.
280     *
281     * @param <T> Target type of the conversion.
282     * @param type Data type to which this value should be converted.
283     * @param value The input value to be converted
284     * @param cause The exception thrown by the <code>convert</code> method
285     * @return The default value.
286     * @throws ConversionException if no default value has been
287     * specified for this {@link Converter}.
288     */
289    protected <T> T handleError(final Class<T> type, final Object value, final Throwable cause) {
290        if (log().isDebugEnabled()) {
291            if (cause instanceof ConversionException) {
292                log().debug("    Conversion threw ConversionException: " + cause.getMessage());
293            } else {
294                log().debug("    Conversion threw " + cause);
295            }
296        }
297
298        if (useDefault) {
299            return handleMissing(type);
300        }
301
302        ConversionException cex = null;
303        if (cause instanceof ConversionException) {
304            cex = (ConversionException)cause;
305            if (log().isDebugEnabled()) {
306                log().debug("    Re-throwing ConversionException: " + cex.getMessage());
307                log().debug("    " + DEFAULT_CONFIG_MSG);
308            }
309        } else {
310            final String msg = "Error converting from '" + toString(value.getClass()) +
311                    "' to '" + toString(type) + "' " + cause.getMessage();
312            cex = new ConversionException(msg, cause);
313            if (log().isDebugEnabled()) {
314                log().debug("    Throwing ConversionException: " + msg);
315                log().debug("    " + DEFAULT_CONFIG_MSG);
316            }
317        }
318
319        throw cex;
320
321    }
322
323    /**
324     * Handle missing values.
325     * <p>
326     * If a default value has been specified, then it is returned (after a cast
327     * to the desired target class); otherwise a ConversionException is thrown.
328     *
329     * @param <T> the desired target type
330     * @param type Data type to which this value should be converted.
331     * @return The default value.
332     * @throws ConversionException if no default value has been
333     * specified for this {@link Converter}.
334     */
335    protected <T> T handleMissing(final Class<T> type) {
336
337        if (useDefault || type.equals(String.class)) {
338            Object value = getDefault(type);
339            if (useDefault && value != null && !type.equals(value.getClass())) {
340                try {
341                    value = convertToType(type, defaultValue);
342                } catch (final Throwable t) {
343                    throw new ConversionException("Default conversion to " + toString(type)
344                            + " failed.", t);
345                }
346            }
347            if (log().isDebugEnabled()) {
348                log().debug("    Using default "
349                        + (value == null ? "" : toString(value.getClass()) + " ")
350                        + "value '" + defaultValue + "'");
351            }
352            // value is now either null or of the desired target type
353            return type.cast(value);
354        }
355
356        final ConversionException cex =  new ConversionException("No value specified for '" +
357                toString(type) + "'");
358        if (log().isDebugEnabled()) {
359            log().debug("    Throwing ConversionException: " + cex.getMessage());
360            log().debug("    " + DEFAULT_CONFIG_MSG);
361        }
362        throw cex;
363
364    }
365
366    /**
367     * Indicates whether a default value will be returned or exception
368     * thrown in the event of a conversion error.
369     *
370     * @return <code>true</code> if a default value will be returned for
371     * conversion errors or <code>false</code> if a {@link ConversionException}
372     * will be thrown.
373     */
374    public boolean isUseDefault() {
375        return useDefault;
376    }
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     * Set the default value, converting as required.
397     * <p>
398     * If the default value is different from the type the
399     * <code>Converter</code> handles, it will be converted
400     * to the handled type.
401     *
402     * @param defaultValue The default value to be returned
403     * if the value to be converted is missing or an error
404     * occurs converting the value.
405     * @throws ConversionException if an error occurs converting
406     * the default value
407     */
408    protected void setDefaultValue(final Object defaultValue) {
409        useDefault = false;
410        if (log().isDebugEnabled()) {
411            log().debug("Setting default value: " + defaultValue);
412        }
413        if (defaultValue == null) {
414           this.defaultValue  = null;
415        } else {
416           this.defaultValue  = convert(getDefaultType(), defaultValue);
417        }
418        useDefault = true;
419    }
420
421    /**
422     * Provide a String representation of this converter.
423     *
424     * @return A String representation of this converter
425     */
426    @Override
427    public String toString() {
428        return toString(getClass()) + "[UseDefault=" + useDefault + "]";
429    }
430
431    /**
432     * Provide a String representation of a <code>java.lang.Class</code>.
433     * @param type The <code>java.lang.Class</code>.
434     * @return The String representation.
435     */
436    String toString(final Class<?> type) {
437        String typeName = null;
438        if (type == null) {
439            typeName = "null";
440        } else if (type.isArray()) {
441            Class<?> elementType = type.getComponentType();
442            int count = 1;
443            while (elementType.isArray()) {
444                elementType = elementType .getComponentType();
445                count++;
446            }
447            typeName = elementType.getName();
448            for (int i = 0; i < count; i++) {
449                typeName += "[]";
450            }
451        } else {
452            typeName = type.getName();
453        }
454        if (typeName.startsWith("java.lang.") ||
455            typeName.startsWith("java.util.") ||
456            typeName.startsWith("java.math.")) {
457            typeName = typeName.substring("java.lang.".length());
458        } else if (typeName.startsWith(PACKAGE)) {
459            typeName = typeName.substring(PACKAGE.length());
460        }
461        return typeName;
462    }
463}