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 1790899 2017-04-10 21:56:46Z 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(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        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(String dateFormat)
108    {
109        this.dateFormat = dateFormat;
110    }
111
112    @Override
113    public <T> T to(Object src, Class<T> targetCls, ConfigurationInterpolator ci)
114    {
115        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(Object src, Class<?> elemClass,
128            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        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(Object src, Class<T> elemClass,
154            ConfigurationInterpolator ci, 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            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(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(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(Object src, Class<T> targetCls,
231            ConfigurationInterpolator ci)
232    {
233        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(Object source, 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(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(Object container,
279            Class<?> targetCls, ConfigurationInterpolator ci)
280    {
281        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(Object src, Class<T> targetCls,
301            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        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(Object src, Class<T> elemClass,
326            ConfigurationInterpolator ci)
327    {
328        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        T[] result = (T[]) Array.newInstance(elemClass, convertedCol.size());
333        return convertedCol.toArray(result);
334    }
335
336    /**
337     * Converts the given source object to an array of a primitive type. This
338     * method performs some checks whether the source object is already an array
339     * of the correct type or a corresponding wrapper type. If not, all values
340     * are extracted, converted one by one, and stored in a newly created array.
341     *
342     * @param src the source object
343     * @param elemClass the element class of the array
344     * @param ci the {@code ConfigurationInterpolator}
345     * @return the result array
346     * @throws ConversionException if a conversion cannot be performed
347     */
348    private Object toPrimitiveArray(Object src, Class<?> elemClass,
349            ConfigurationInterpolator ci)
350    {
351        if (src.getClass().isArray())
352        {
353            if (src.getClass().getComponentType().equals(elemClass))
354            {
355                return src;
356            }
357
358            if (src.getClass().getComponentType()
359                    .equals(ClassUtils.primitiveToWrapper(elemClass)))
360            {
361                // the value is an array of the wrapper type derived from the
362                // specified primitive type
363                int length = Array.getLength(src);
364                Object array = Array.newInstance(elemClass, length);
365
366                for (int i = 0; i < length; i++)
367                {
368                    Array.set(array, i, Array.get(src, i));
369                }
370                return array;
371            }
372        }
373
374        Collection<?> values = extractValues(src);
375        Class<?> targetClass = ClassUtils.primitiveToWrapper(elemClass);
376        Object array = Array.newInstance(elemClass, values.size());
377        int idx = 0;
378        for (Object value : values)
379        {
380            Array.set(array, idx++,
381                    convertValue(ci.interpolate(value), targetClass, ci));
382        }
383        return array;
384    }
385
386    /**
387     * Helper method for converting all values of a source object and storing
388     * them in a collection.
389     *
390     * @param <T> the target type of the conversion
391     * @param src the source object
392     * @param elemClass the target class of the conversion
393     * @param ci the {@code ConfigurationInterpolator}
394     * @param dest the collection in which to store the results
395     * @throws ConversionException if a conversion cannot be performed
396     */
397    private <T> void convertToCollection(Object src, Class<T> elemClass,
398            ConfigurationInterpolator ci, Collection<T> dest)
399    {
400        for (Object o : extractValues(ci.interpolate(src)))
401        {
402            dest.add(convert(o, elemClass, ci));
403        }
404    }
405
406    /**
407     * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not
408     * <b>null</b>, it is used. Otherwise, a default one is returned.
409     *
410     * @param ci the {@code ConfigurationInterpolator} provided by the caller
411     * @return the {@code ConfigurationInterpolator} to be used
412     */
413    private static ConfigurationInterpolator fetchInterpolator(
414            ConfigurationInterpolator ci)
415    {
416        return (ci != null) ? ci : NULL_INTERPOLATOR;
417    }
418}