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 * http://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 /** The current date format. */ 72 private volatile String dateFormat; 73 74 /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */ 75 private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE; 76 77 /** 78 * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <b>null</b>, it is used. Otherwise, a 79 * default one is returned. 80 * 81 * @param ci the {@code ConfigurationInterpolator} provided by the caller 82 * @return the {@code ConfigurationInterpolator} to be used 83 */ 84 private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) { 85 return ci != null ? ci : NULL_INTERPOLATOR; 86 } 87 88 /** 89 * Performs the conversion from the passed in source object to the specified target class. This method is called for 90 * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so 91 * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary 92 * for extracting values from complex objects; it is guaranteed to be non <b>null</b>.) The source object may be a 93 * complex object, e.g. a collection or an array. This base implementation checks whether the source object is complex. 94 * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single 95 * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to 96 * be converted. 97 * 98 * @param <T> the desired target type of the conversion 99 * @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 }