View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.convert;
18  
19  import java.lang.reflect.Array;
20  import java.util.Collection;
21  import java.util.Iterator;
22  import java.util.LinkedList;
23  
24  import org.apache.commons.configuration2.ex.ConversionException;
25  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
26  import org.apache.commons.lang3.ClassUtils;
27  
28  /**
29   * <p>
30   * A default implementation of the {@code ConversionHandler} interface.
31   * </p>
32   * <p>
33   * This class implements the standard data type conversions as used by {@code AbstractConfiguration} and derived
34   * classes. There is a central conversion method - {@code convert()} - for converting a passed in object to a given
35   * target class. The basic implementation already handles a bunch of standard data type conversions. If other
36   * conversions are to be supported, this method can be overridden.
37   * </p>
38   * <p>
39   * The object passed to {@code convert()} can be a single value or a complex object (like an array, a collection, etc.)
40   * containing multiple values. It lies in the responsibility of {@code convert()} to deal with such complex objects. The
41   * implementation provided by this class tries to extract the first child element and then delegates to
42   * {@code convertValue()} which does the actual conversion.
43   * </p>
44   *
45   * @since 2.0
46   */
47  public class DefaultConversionHandler implements ConversionHandler {
48  
49      /**
50       * A default instance of this class. Because an instance of this class can be shared between arbitrary objects it is
51       * possible to make use of this default instance anywhere.
52       */
53      public static final DefaultConversionHandler INSTANCE = new DefaultConversionHandler();
54  
55      /** The default format for dates. */
56      public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
57  
58      /**
59       * Constant for a default {@code ConfigurationInterpolator} to be used if none is provided by the caller.
60       */
61      private static final ConfigurationInterpolator NULL_INTERPOLATOR = new ConfigurationInterpolator() {
62          @Override
63          public Object interpolate(final Object value) {
64              return value;
65          }
66      };
67  
68      /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
69      static final ListDelimiterHandler LIST_DELIMITER_HANDLER = DisabledListDelimiterHandler.INSTANCE;
70  
71      /**
72       * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <strong>null</strong>, it is used. Otherwise, a
73       * default one is returned.
74       *
75       * @param ci the {@code ConfigurationInterpolator} provided by the caller
76       * @return the {@code ConfigurationInterpolator} to be used
77       */
78      private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) {
79          return ci != null ? ci : NULL_INTERPOLATOR;
80      }
81  
82      /** The current date format. */
83      private volatile String dateFormat;
84  
85      /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
86      private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;
87  
88      /**
89       * Constructs a new instance.
90       */
91      public DefaultConversionHandler() {
92          // empty
93      }
94  
95      /**
96       * Performs the conversion from the passed in source object to the specified target class. This method is called for
97       * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so
98       * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary
99       * for extracting values from complex objects; it is guaranteed to be non <strong>null</strong>.) The source object may be a
100      * complex object, for example a collection or an array. This base implementation checks whether the source object is complex.
101      * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single
102      * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to
103      * be converted.
104      *
105      * @param <T> the desired target type of the conversion
106      * @param src the source object to be converted
107      * @param targetCls the desired target class
108      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
109      * @return the converted value
110      * @throws ConversionException if conversion is not possible
111      */
112     protected <T> T convert(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
113         final Object conversionSrc = isComplexObject(src) ? extractConversionValue(src, targetCls, ci) : src;
114         return convertValue(ci.interpolate(conversionSrc), targetCls, ci);
115     }
116 
117     /**
118      * Helper method for converting all values of a source object and storing them in a collection.
119      *
120      * @param <T> the target type of the conversion
121      * @param src the source object
122      * @param elemClass the target class of the conversion
123      * @param ci the {@code ConfigurationInterpolator}
124      * @param dest the collection in which to store the results
125      * @throws ConversionException if a conversion cannot be performed
126      */
127     private <T> void convertToCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
128         extractValues(ci.interpolate(src)).forEach(o -> dest.add(convert(o, elemClass, ci)));
129     }
130 
131     /**
132      * Performs a conversion of a single value to the specified target class. The passed in source object is guaranteed to
133      * be a single value, but it can be <strong>null</strong>. Derived classes that want to extend the available conversions, but are
134      * happy with the handling of complex objects, just need to override this method.
135      *
136      * @param <T> the desired target type of the conversion
137      * @param src the source object (a single value)
138      * @param targetCls the target class of the conversion
139      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
140      * @return the converted value
141      * @throws ConversionException if conversion is not possible
142      */
143     protected <T> T convertValue(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
144         if (src == null) {
145             return null;
146         }
147 
148         // This is a safe cast because PropertyConverter either returns an
149         // object of the correct class or throws an exception.
150         @SuppressWarnings("unchecked")
151         final T result = (T) PropertyConverter.to(targetCls, src, this);
152         return result;
153     }
154 
155     /**
156      * Extracts a single value from a complex object. This method is called by {@code convert()} if the source object is
157      * complex. This implementation extracts the first value from the complex object and returns it.
158      *
159      * @param container the complex object
160      * @param targetCls the target class of the conversion
161      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
162      * @return the value to be converted (may be <strong>null</strong> if no values are found)
163      */
164     protected Object extractConversionValue(final Object container, final Class<?> targetCls, final ConfigurationInterpolator ci) {
165         final Collection<?> values = extractValues(container, 1);
166         return values.isEmpty() ? null : ci.interpolate(values.iterator().next());
167     }
168 
169     /**
170      * Extracts all values contained in the given source object and returns them as a flat collection.
171      *
172      * @param source the source object (may be a single value or a complex object)
173      * @return a collection with all extracted values
174      */
175     protected Collection<?> extractValues(final Object source) {
176         return extractValues(source, Integer.MAX_VALUE);
177     }
178 
179     /**
180      * Extracts a maximum number of values contained in the given source object and returns them as flat collection. This
181      * method is useful if the caller only needs a subset of values, for example only the first one.
182      *
183      * @param source the source object (may be a single value or a complex object)
184      * @param limit the number of elements to extract
185      * @return a collection with all extracted values
186      */
187     protected Collection<?> extractValues(final Object source, final int limit) {
188         return listDelimiterHandler.flatten(source, limit);
189     }
190 
191     /**
192      * Gets the date format used by this conversion handler.
193      *
194      * @return the date format
195      */
196     public String getDateFormat() {
197         final String fmt = dateFormat;
198         return fmt != null ? fmt : DEFAULT_DATE_FORMAT;
199     }
200 
201     /**
202      * Gets the {@link ListDelimiterHandler} used for extracting values from complex objects.
203      *
204      * @return the {@link ListDelimiterHandler} used for extracting values from complex objects, never null.
205      * @since 2.9.0
206      */
207     public ListDelimiterHandler getListDelimiterHandler() {
208         return listDelimiterHandler;
209     }
210 
211     /**
212      * Tests whether the passed in object is complex (which means that it contains multiple values). This method is called
213      * by {@link #convert(Object, Class, ConfigurationInterpolator)} to figure out whether a actions are required to extract
214      * a single value from a complex source object. This implementation considers the following objects as complex:
215      * <ul>
216      * <li>{@code Iterable} objects</li>
217      * <li>{@code Iterator} objects</li>
218      * <li>Arrays</li>
219      * </ul>
220      *
221      * @param src the source object
222      * @return <strong>true</strong> if this is a complex object, <strong>false</strong> otherwise
223      */
224     protected boolean isComplexObject(final Object src) {
225         return src instanceof Iterator<?> || src instanceof Iterable<?> || src != null && src.getClass().isArray();
226     }
227 
228     /**
229      * Tests whether the passed in object represents an empty element. This method is called by conversion methods to arrays
230      * or collections. If it returns <strong>true</strong>, the resulting array or collection will be empty. This implementation
231      * returns <strong>true</strong> if and only if the passed in object is an empty string. With this method it can be controlled if
232      * and how empty elements in configurations are handled.
233      *
234      * @param src the object to be tested
235      * @return a flag whether this object is an empty element
236      */
237     protected boolean isEmptyElement(final Object src) {
238         return src instanceof CharSequence && ((CharSequence) src).length() == 0;
239     }
240 
241     /**
242      * Sets the date format to be used by this conversion handler. This format is applied by conversions to {@code Date} or
243      * {@code Calendar} objects. The string is passed to the {@link java.text.SimpleDateFormat} class, so it must be
244      * compatible with this class. If no date format has been set, a default format is used.
245      *
246      * @param dateFormat the date format string
247      * @see #DEFAULT_DATE_FORMAT
248      */
249     public void setDateFormat(final String dateFormat) {
250         this.dateFormat = dateFormat;
251     }
252 
253     /**
254      * Sets the {@link ListDelimiterHandler} used for extracting values from complex objects.
255      *
256      * @param listDelimiterHandler the {@link ListDelimiterHandler} used for extracting values from complex objects. Setting
257      *        the value to null resets the value to its default.
258      * @since 2.9.0
259      */
260     public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
261         this.listDelimiterHandler = listDelimiterHandler != null ? listDelimiterHandler : LIST_DELIMITER_HANDLER;
262     }
263 
264     @Override
265     public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
266         final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
267         return convert(interpolator.interpolate(src), targetCls, interpolator);
268     }
269 
270     /**
271      * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
272      * target type, and adds them to a result array. Arrays of objects and of primitive types are supported. If the source
273      * object is <strong>null</strong>, result is <strong>null</strong>, too.
274      */
275     @Override
276     public Object toArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
277         if (src == null) {
278             return null;
279         }
280         if (isEmptyElement(src)) {
281             return Array.newInstance(elemClass, 0);
282         }
283 
284         final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
285         return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass, interpolator) : toObjectArray(src, elemClass, interpolator);
286     }
287 
288     /**
289      * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
290      * target type, and adds them to the target collection. The target collection must not be <strong>null</strong>. If the source
291      * object is <strong>null</strong>, nothing is added to the collection.
292      *
293      * @throws IllegalArgumentException if the target collection is <strong>null</strong>
294      */
295     @Override
296     public <T> void toCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
297         if (dest == null) {
298             throw new IllegalArgumentException("Target collection must not be null!");
299         }
300 
301         if (src != null && !isEmptyElement(src)) {
302             convertToCollection(src, elemClass, fetchInterpolator(ci), dest);
303         }
304     }
305 
306     /**
307      * Converts the given source object to an array of objects.
308      *
309      * @param src the source object
310      * @param elemClass the element class of the array
311      * @param ci the {@code ConfigurationInterpolator}
312      * @return the result array
313      * @throws ConversionException if a conversion cannot be performed
314      */
315     private <T> T[] toObjectArray(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci) {
316         final Collection<T> convertedCol = new LinkedList<>();
317         convertToCollection(src, elemClass, ci, convertedCol);
318         // Safe to cast because the element class is specified
319         @SuppressWarnings("unchecked")
320         final T[] result = (T[]) Array.newInstance(elemClass, convertedCol.size());
321         return convertedCol.toArray(result);
322     }
323 
324     /**
325      * Converts the given source object to an array of a primitive type. This method performs some checks whether the source
326      * object is already an array of the correct type or a corresponding wrapper type. If not, all values are extracted,
327      * converted one by one, and stored in a newly created array.
328      *
329      * @param src the source object
330      * @param elemClass the element class of the array
331      * @param ci the {@code ConfigurationInterpolator}
332      * @return the result array
333      * @throws ConversionException if a conversion cannot be performed
334      */
335     private Object toPrimitiveArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
336         if (src.getClass().isArray()) {
337             if (src.getClass().getComponentType().equals(elemClass)) {
338                 return src;
339             }
340 
341             if (src.getClass().getComponentType().equals(ClassUtils.primitiveToWrapper(elemClass))) {
342                 // the value is an array of the wrapper type derived from the
343                 // specified primitive type
344                 final int length = Array.getLength(src);
345                 final Object array = Array.newInstance(elemClass, length);
346 
347                 for (int i = 0; i < length; i++) {
348                     Array.set(array, i, Array.get(src, i));
349                 }
350                 return array;
351             }
352         }
353 
354         final Collection<?> values = extractValues(src);
355         final Class<?> targetClass = ClassUtils.primitiveToWrapper(elemClass);
356         final Object array = Array.newInstance(elemClass, values.size());
357         int idx = 0;
358         for (final Object value : values) {
359             Array.set(array, idx++, convertValue(ci.interpolate(value), targetClass, ci));
360         }
361         return array;
362     }
363 }