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 * https://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 {@code AbstractConfiguration} and derived 034 * classes. There is a central conversion method - {@code convert()} - for converting a passed in object to a given 035 * target class. The basic implementation already handles a bunch of standard data type conversions. If other 036 * conversions are to be supported, this method can be overridden. 037 * </p> 038 * <p> 039 * The object passed to {@code convert()} can be a single value or a complex object (like an array, a collection, etc.) 040 * containing multiple values. It lies in the responsibility of {@code convert()} to deal with such complex objects. The 041 * implementation provided by this class tries to extract the first child element and then delegates to 042 * {@code convertValue()} which does the actual conversion. 043 * </p> 044 * 045 * @since 2.0 046 */ 047public class DefaultConversionHandler implements ConversionHandler { 048 049 /** 050 * A default instance of this class. Because an instance of this class can be shared between arbitrary objects it is 051 * possible to make use of this default instance anywhere. 052 */ 053 public static final DefaultConversionHandler INSTANCE = new DefaultConversionHandler(); 054 055 /** The default format for dates. */ 056 public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 057 058 /** 059 * Constant for a default {@code ConfigurationInterpolator} to be used if none is provided by the caller. 060 */ 061 private static final ConfigurationInterpolator NULL_INTERPOLATOR = new ConfigurationInterpolator() { 062 @Override 063 public Object interpolate(final Object value) { 064 return value; 065 } 066 }; 067 068 /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */ 069 static final ListDelimiterHandler LIST_DELIMITER_HANDLER = DisabledListDelimiterHandler.INSTANCE; 070 071 /** 072 * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <strong>null</strong>, it is used. Otherwise, a 073 * default one is returned. 074 * 075 * @param ci the {@code ConfigurationInterpolator} provided by the caller 076 * @return the {@code ConfigurationInterpolator} to be used 077 */ 078 private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) { 079 return ci != null ? ci : NULL_INTERPOLATOR; 080 } 081 082 /** The current date format. */ 083 private volatile String dateFormat; 084 085 /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */ 086 private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE; 087 088 /** 089 * Constructs a new instance. 090 */ 091 public DefaultConversionHandler() { 092 // empty 093 } 094 095 /** 096 * Performs the conversion from the passed in source object to the specified target class. This method is called for 097 * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so 098 * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary 099 * 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}