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 }