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