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    /** The current date format. */
072    private volatile String dateFormat;
073
074    /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
075    private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;
076
077    /**
078     * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <b>null</b>, it is used. Otherwise, a
079     * default one is returned.
080     *
081     * @param ci the {@code ConfigurationInterpolator} provided by the caller
082     * @return the {@code ConfigurationInterpolator} to be used
083     */
084    private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) {
085        return ci != null ? ci : NULL_INTERPOLATOR;
086    }
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 <b>null</b>.) The source object may be a
093     * complex object, e.g. 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 <b>null</b>)
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 <b>null</b>. 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 <b>null</b>)
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 <b>null</b>)
155     * @return the value to be converted (may be <b>null</b> 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, e.g. 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 <b>true</b> if this is a complex object, <b>false</b> 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 <b>true</b>, the resulting array or collection will be empty. This implementation
224     * returns <b>true</b> 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 <b>null</b>, result is <b>null</b>, 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 <b>null</b>. If the source
284     * object is <b>null</b>, nothing is added to the collection.
285     *
286     * @throws IllegalArgumentException if the target collection is <b>null</b>
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}