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}