    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.configuration2.convert;
    018
    019import java.lang.reflect.Array;
    020import java.util.Collection;
    021import java.util.Iterator;
    022import java.util.LinkedList;
    023
    024import org.apache.commons.configuration2.ex.ConversionException;
    025import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
    026import org.apache.commons.lang3.ClassUtils;
    027
    028/**
    029 * <p>
    030 * A default implementation of the {@code ConversionHandler} interface.
    031 * </p>
    032 * <p>
    033 * This class implements the standard data type conversions as used by
    034 * {@code AbstractConfiguration} and derived classes. There is a central
    035 * conversion method - {@code convert()} - for converting a passed in object to
    036 * a given target class. The basic implementation already handles a bunch of
    037 * standard data type conversions. If other conversions are to be supported,
    038 * this method can be overridden.
    039 * </p>
    040 * <p>
    041 * The object passed to {@code convert()} can be a single value or a complex
    042 * object (like an array, a collection, etc.) containing multiple values. It
    043 * lies in the responsibility of {@code convert()} to deal with such complex
    044 * objects. The implementation provided by this class tries to extract the first
    045 * child element and then delegates to {@code convertValue()} which does the
    046 * actual conversion.
    047 * </p>
    048 *
    049 * @since 2.0
    050 */
    051public class DefaultConversionHandler implements ConversionHandler
    052{
    053    /**
    054     * A default instance of this class. Because an instance of this class can
    055     * be shared between arbitrary objects it is possible to make use of this
    056     * default instance anywhere.
    057     */
    058    public static final DefaultConversionHandler INSTANCE =
    059            new DefaultConversionHandler();
    060
    061    /** The default format for dates. */
    062    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    063
    064    /** A helper object used for extracting values from complex objects. */
    065    private static final AbstractListDelimiterHandler EXTRACTOR =
    066            (AbstractListDelimiterHandler) DisabledListDelimiterHandler.INSTANCE;
    067
    068    /**
    069     * Constant for a default {@code ConfigurationInterpolator} to be used if
    070     * none is provided by the caller.
    071     */
    072    private static final ConfigurationInterpolator NULL_INTERPOLATOR =
    073            new ConfigurationInterpolator()
    074            {
    075                @Override
    076                public Object interpolate(final Object value)
    077                {
    078                    return value;
    079                }
    080            };
    081
    082    /** The current date format. */
    083    private volatile String dateFormat;
    084
    085    /**
    086     * Returns the date format used by this conversion handler.
    087     *
    088     * @return the date format
    089     */
    090    public String getDateFormat()
    091    {
    092        final String fmt = dateFormat;
    093        return fmt != null ? fmt : DEFAULT_DATE_FORMAT;
    094    }
    095
    096    /**
    097     * Sets the date format to be used by this conversion handler. This format
    098     * is applied by conversions to {@code Date} or {@code Calendar} objects.
    099     * The string is passed to the {@code java.text.SimpleDateFormat} class, so
    100     * it must be compatible with this class. If no date format has been set, a
    101     * default format is used.
    102     *
    103     * @param dateFormat the date format string
    104     * @see #DEFAULT_DATE_FORMAT
    105     */
    106    public void setDateFormat(final String dateFormat)
    107    {
    108        this.dateFormat = dateFormat;
    109    }
    110
    111    @Override
    112    public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci)
    113    {
    114        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
    115        return convert(interpolator.interpolate(src), targetCls, interpolator);
    116    }
    117
    118    /**
    119     * {@inheritDoc} This implementation extracts all values stored in the
    120     * passed in source object, converts them to the target type, and adds them
    121     * to a result array. Arrays of objects and of primitive types are
    122     * supported. If the source object is <b>null</b>, result is <b>null</b>,
    123     * too.
    124     */
    125    @Override
    126    public Object toArray(final Object src, final Class<?> elemClass,
    127            final ConfigurationInterpolator ci)
    128    {
    129        if (src == null)
    130        {
    131            return null;
    132        }
    133        if (isEmptyElement(src))
    134        {
    135            return Array.newInstance(elemClass, 0);
    136        }
    137
    138        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
    139        return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass,
    140                interpolator) : toObjectArray(src, elemClass, interpolator);
    141    }
    142
    143    /**
    144     * {@inheritDoc} This implementation extracts all values stored in the
    145     * passed in source object, converts them to the target type, and adds them
    146     * to the target collection. The target collection must not be <b>null</b>.
    147     * If the source object is <b>null</b>, nothing is added to the collection.
    148     *
    149     * @throws IllegalArgumentException if the target collection is <b>null</b>
    150     */
    151    @Override
    152    public <T> void toCollection(final Object src, final Class<T> elemClass,
    153            final ConfigurationInterpolator ci, final Collection<T> dest)
    154    {
    155        if (dest == null)
    156        {
    157            throw new IllegalArgumentException(
    158                    "Target collection must not be null!");
    159        }
    160
    161        if (src != null && !isEmptyElement(src))
    162        {
    163            final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
    164            convertToCollection(src, elemClass, interpolator, dest);
    165        }
    166    }
    167
    168    /**
    169     * Tests whether the passed in object is complex (which means that it
    170     * contains multiple values). This method is called by
    171     * {@link #convert(Object, Class, ConfigurationInterpolator)} to figure out
    172     * whether a actions are required to extract a single value from a complex
    173     * source object. This implementation considers the following objects as
    174     * complex:
    175     * <ul>
    176     * <li>{@code Iterable} objects</li>
    177     * <li>{@code Iterator} objects</li>
    178     * <li>Arrays</li>
    179     * </ul>
    180     *
    181     * @param src the source object
    182     * @return <b>true</b> if this is a complex object, <b>false</b> otherwise
    183     */
    184    protected boolean isComplexObject(final Object src)
    185    {
    186        return src instanceof Iterator<?> || src instanceof Iterable<?>
    187                || (src != null && src.getClass().isArray());
    188    }
    189
    190    /**
    191     * Tests whether the passed in object represents an empty element. This
    192     * method is called by conversion methods to arrays or collections. If it
    193     * returns <b>true</b>, the resulting array or collection will be empty.
    194     * This implementation returns <b>true</b> if and only if the passed in
    195     * object is an empty string. With this method it can be controlled if and
    196     * how empty elements in configurations are handled.
    197     *
    198     * @param src the object to be tested
    199     * @return a flag whether this object is an empty element
    200     */
    201    protected boolean isEmptyElement(final Object src)
    202    {
    203        return (src instanceof CharSequence)
    204                && ((CharSequence) src).length() == 0;
    205    }
    206
    207    /**
    208     * Performs the conversion from the passed in source object to the specified
    209     * target class. This method is called for each conversion to be done. The
    210     * source object has already been passed to the
    211     * {@link ConfigurationInterpolator}, so interpolation does not have to be
    212     * done again. (The passed in {@code ConfigurationInterpolator} may still be
    213     * necessary for extracting values from complex objects; it is guaranteed to
    214     * be non <b>null</b>.) The source object may be a complex object, e.g. a
    215     * collection or an array. This base implementation checks whether the
    216     * source object is complex. If so, it delegates to
    217     * {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)}
    218     * to obtain a single value. Eventually,
    219     * {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called
    220     * with the single value to be converted.
    221     *
    222     * @param <T> the desired target type of the conversion
    223     * @param src the source object to be converted
    224     * @param targetCls the desired target class
    225     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
    226     * @return the converted value
    227     * @throws ConversionException if conversion is not possible
    228     */
    229    protected <T> T convert(final Object src, final Class<T> targetCls,
    230            final ConfigurationInterpolator ci)
    231    {
    232        final Object conversionSrc =
    233                isComplexObject(src) ? extractConversionValue(src, targetCls,
    234                        ci) : src;
    235        return convertValue(ci.interpolate(conversionSrc), targetCls, ci);
    236    }
    237
    238    /**
    239     * Extracts a maximum number of values contained in the given source object
    240     * and returns them as flat collection. This method is useful if the caller
    241     * only needs a subset of values, e.g. only the first one.
    242     *
    243     * @param source the source object (may be a single value or a complex
    244     *        object)
    245     * @param limit the number of elements to extract
    246     * @return a collection with all extracted values
    247     */
    248    protected Collection<?> extractValues(final Object source, final int limit)
    249    {
    250        return EXTRACTOR.flatten(source, limit);
    251    }
    252
    253    /**
    254     * Extracts all values contained in the given source object and returns them
    255     * as a flat collection.
    256     *
    257     * @param source the source object (may be a single value or a complex
    258     *        object)
    259     * @return a collection with all extracted values
    260     */
    261    protected Collection<?> extractValues(final Object source)
    262    {
    263        return extractValues(source, Integer.MAX_VALUE);
    264    }
    265
    266    /**
    267     * Extracts a single value from a complex object. This method is called by
    268     * {@code convert()} if the source object is complex. This implementation
    269     * extracts the first value from the complex object and returns it.
    270     *
    271     * @param container the complex object
    272     * @param targetCls the target class of the conversion
    273     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
    274     * @return the value to be converted (may be <b>null</b> if no values are
    275     *         found)
    276     */
    277    protected Object extractConversionValue(final Object container,
    278            final Class<?> targetCls, final ConfigurationInterpolator ci)
    279    {
    280        final Collection<?> values = extractValues(container, 1);
    281        return values.isEmpty() ? null : ci.interpolate(values.iterator()
    282                .next());
    283    }
    284
    285    /**
    286     * Performs a conversion of a single value to the specified target class.
    287     * The passed in source object is guaranteed to be a single value, but it
    288     * can be <b>null</b>. Derived classes that want to extend the available
    289     * conversions, but are happy with the handling of complex objects, just
    290     * need to override this method.
    291     *
    292     * @param <T> the desired target type of the conversion
    293     * @param src the source object (a single value)
    294     * @param targetCls the target class of the conversion
    295     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
    296     * @return the converted value
    297     * @throws ConversionException if conversion is not possible
    298     */
    299    protected <T> T convertValue(final Object src, final Class<T> targetCls,
    300            final ConfigurationInterpolator ci)
    301    {
    302        if (src == null)
    303        {
    304            return null;
    305        }
    306
    307        // This is a safe cast because PropertyConverter either returns an
    308        // object of the correct class or throws an exception.
    309        @SuppressWarnings("unchecked")
    310        final
    311        T result = (T) PropertyConverter.to(targetCls, src,
    312               this);
    313        return result;
    314    }
    315
    316    /**
    317     * Converts the given source object to an array of objects.
    318     *
    319     * @param src the source object
    320     * @param elemClass the element class of the array
    321     * @param ci the {@code ConfigurationInterpolator}
    322     * @return the result array
    323     * @throws ConversionException if a conversion cannot be performed
    324     */
    325    private <T> T[] toObjectArray(final Object src, final Class<T> elemClass,
    326            final ConfigurationInterpolator ci)
    327    {
    328        final Collection<T> convertedCol = new LinkedList<>();
    329        convertToCollection(src, elemClass, ci, convertedCol);
    330        // Safe to cast because the element class is specified
    331        @SuppressWarnings("unchecked")
    332        final
    333        T[] result = (T[]) Array.newInstance(elemClass, convertedCol.size());
    334        return convertedCol.toArray(result);
    335    }
    336
    337    /**
    338     * Converts the given source object to an array of a primitive type. This
    339     * method performs some checks whether the source object is already an array
    340     * of the correct type or a corresponding wrapper type. If not, all values
    341     * are extracted, converted one by one, and stored in a newly created array.
    342     *
    343     * @param src the source object
    344     * @param elemClass the element class of the array
    345     * @param ci the {@code ConfigurationInterpolator}
    346     * @return the result array
    347     * @throws ConversionException if a conversion cannot be performed
    348     */
    349    private Object toPrimitiveArray(final Object src, final Class<?> elemClass,
    350            final ConfigurationInterpolator ci)
    351    {
    352        if (src.getClass().isArray())
    353        {
    354            if (src.getClass().getComponentType().equals(elemClass))
    355            {
    356                return src;
    357            }
    358
    359            if (src.getClass().getComponentType()
    360                    .equals(ClassUtils.primitiveToWrapper(elemClass)))
    361            {
    362                // the value is an array of the wrapper type derived from the
    363                // specified primitive type
    364                final int length = Array.getLength(src);
    365                final Object array = Array.newInstance(elemClass, length);
    366
    367                for (int i = 0; i < length; i++)
    368                {
    369                    Array.set(array, i, Array.get(src, i));
    370                }
    371                return array;
    372            }
    373        }
    374
    375        final Collection<?> values = extractValues(src);
    376        final Class<?> targetClass = ClassUtils.primitiveToWrapper(elemClass);
    377        final Object array = Array.newInstance(elemClass, values.size());
    378        int idx = 0;
    379        for (final Object value : values)
    380        {
    381            Array.set(array, idx++,
    382                    convertValue(ci.interpolate(value), targetClass, ci));
    383        }
    384        return array;
    385    }
    386
    387    /**
    388     * Helper method for converting all values of a source object and storing
    389     * them in a collection.
    390     *
    391     * @param <T> the target type of the conversion
    392     * @param src the source object
    393     * @param elemClass the target class of the conversion
    394     * @param ci the {@code ConfigurationInterpolator}
    395     * @param dest the collection in which to store the results
    396     * @throws ConversionException if a conversion cannot be performed
    397     */
    398    private <T> void convertToCollection(final Object src, final Class<T> elemClass,
    399            final ConfigurationInterpolator ci, final Collection<T> dest)
    400    {
    401        for (final Object o : extractValues(ci.interpolate(src)))
    402        {
    403            dest.add(convert(o, elemClass, ci));
    404        }
    405    }
    406
    407    /**
    408     * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not
    409     * <b>null</b>, it is used. Otherwise, a default one is returned.
    410     *
    411     * @param ci the {@code ConfigurationInterpolator} provided by the caller
    412     * @return the {@code ConfigurationInterpolator} to be used
    413     */
    414    private static ConfigurationInterpolator fetchInterpolator(
    415            final ConfigurationInterpolator ci)
    416    {
    417        return ci != null ? ci : NULL_INTERPOLATOR;
    418    }
    419}