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