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 * @version $Id: DefaultConversionHandler.java 1842194 2018-09-27 22:24:23Z ggregory $ 050 * @since 2.0 051 */ 052public class DefaultConversionHandler implements ConversionHandler 053{ 054 /** 055 * A default instance of this class. Because an instance of this class can 056 * be shared between arbitrary objects it is possible to make use of this 057 * default instance anywhere. 058 */ 059 public static final DefaultConversionHandler INSTANCE = 060 new DefaultConversionHandler(); 061 062 /** The default format for dates. */ 063 public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; 064 065 /** A helper object used for extracting values from complex objects. */ 066 private static final AbstractListDelimiterHandler EXTRACTOR = 067 (AbstractListDelimiterHandler) DisabledListDelimiterHandler.INSTANCE; 068 069 /** 070 * Constant for a default {@code ConfigurationInterpolator} to be used if 071 * none is provided by the caller. 072 */ 073 private static final ConfigurationInterpolator NULL_INTERPOLATOR = 074 new ConfigurationInterpolator() 075 { 076 @Override 077 public Object interpolate(final Object value) 078 { 079 return value; 080 } 081 }; 082 083 /** The current date format. */ 084 private volatile String dateFormat; 085 086 /** 087 * Returns the date format used by this conversion handler. 088 * 089 * @return the date format 090 */ 091 public String getDateFormat() 092 { 093 final String fmt = dateFormat; 094 return (fmt != null) ? fmt : DEFAULT_DATE_FORMAT; 095 } 096 097 /** 098 * Sets the date format to be used by this conversion handler. This format 099 * is applied by conversions to {@code Date} or {@code Calendar} objects. 100 * The string is passed to the {@code java.text.SimpleDateFormat} class, so 101 * it must be compatible with this class. If no date format has been set, a 102 * default format is used. 103 * 104 * @param dateFormat the date format string 105 * @see #DEFAULT_DATE_FORMAT 106 */ 107 public void setDateFormat(final String dateFormat) 108 { 109 this.dateFormat = dateFormat; 110 } 111 112 @Override 113 public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) 114 { 115 final ConfigurationInterpolator interpolator = fetchInterpolator(ci); 116 return convert(interpolator.interpolate(src), targetCls, interpolator); 117 } 118 119 /** 120 * {@inheritDoc} This implementation extracts all values stored in the 121 * passed in source object, converts them to the target type, and adds them 122 * to a result array. Arrays of objects and of primitive types are 123 * supported. If the source object is <b>null</b>, result is <b>null</b>, 124 * too. 125 */ 126 @Override 127 public Object toArray(final Object src, final Class<?> elemClass, 128 final ConfigurationInterpolator ci) 129 { 130 if (src == null) 131 { 132 return null; 133 } 134 if (isEmptyElement(src)) 135 { 136 return Array.newInstance(elemClass, 0); 137 } 138 139 final ConfigurationInterpolator interpolator = fetchInterpolator(ci); 140 return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass, 141 interpolator) : toObjectArray(src, elemClass, interpolator); 142 } 143 144 /** 145 * {@inheritDoc} This implementation extracts all values stored in the 146 * passed in source object, converts them to the target type, and adds them 147 * to the target collection. The target collection must not be <b>null</b>. 148 * If the source object is <b>null</b>, nothing is added to the collection. 149 * 150 * @throws IllegalArgumentException if the target collection is <b>null</b> 151 */ 152 @Override 153 public <T> void toCollection(final Object src, final Class<T> elemClass, 154 final ConfigurationInterpolator ci, final Collection<T> dest) 155 { 156 if (dest == null) 157 { 158 throw new IllegalArgumentException( 159 "Target collection must not be null!"); 160 } 161 162 if (src != null && !isEmptyElement(src)) 163 { 164 final ConfigurationInterpolator interpolator = fetchInterpolator(ci); 165 convertToCollection(src, elemClass, interpolator, dest); 166 } 167 } 168 169 /** 170 * Tests whether the passed in object is complex (which means that it 171 * contains multiple values). This method is called by 172 * {@link #convert(Object, Class, ConfigurationInterpolator)} to figure out 173 * whether a actions are required to extract a single value from a complex 174 * source object. This implementation considers the following objects as 175 * complex: 176 * <ul> 177 * <li>{@code Iterable} objects</li> 178 * <li>{@code Iterator} objects</li> 179 * <li>Arrays</li> 180 * </ul> 181 * 182 * @param src the source object 183 * @return <b>true</b> if this is a complex object, <b>false</b> otherwise 184 */ 185 protected boolean isComplexObject(final Object src) 186 { 187 return src instanceof Iterator<?> || src instanceof Iterable<?> 188 || (src != null && src.getClass().isArray()); 189 } 190 191 /** 192 * Tests whether the passed in object represents an empty element. This 193 * method is called by conversion methods to arrays or collections. If it 194 * returns <b>true</b>, the resulting array or collection will be empty. 195 * This implementation returns <b>true</b> if and only if the passed in 196 * object is an empty string. With this method it can be controlled if and 197 * how empty elements in configurations are handled. 198 * 199 * @param src the object to be tested 200 * @return a flag whether this object is an empty element 201 */ 202 protected boolean isEmptyElement(final Object src) 203 { 204 return (src instanceof CharSequence) 205 && ((CharSequence) src).length() == 0; 206 } 207 208 /** 209 * Performs the conversion from the passed in source object to the specified 210 * target class. This method is called for each conversion to be done. The 211 * source object has already been passed to the 212 * {@link ConfigurationInterpolator}, so interpolation does not have to be 213 * done again. (The passed in {@code ConfigurationInterpolator} may still be 214 * necessary for extracting values from complex objects; it is guaranteed to 215 * be non <b>null</b>.) The source object may be a complex object, e.g. a 216 * collection or an array. This base implementation checks whether the 217 * source object is complex. If so, it delegates to 218 * {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} 219 * to obtain a single value. Eventually, 220 * {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called 221 * with the single value to be converted. 222 * 223 * @param <T> the desired target type of the conversion 224 * @param src the source object to be converted 225 * @param targetCls the desired target class 226 * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>) 227 * @return the converted value 228 * @throws ConversionException if conversion is not possible 229 */ 230 protected <T> T convert(final Object src, final Class<T> targetCls, 231 final ConfigurationInterpolator ci) 232 { 233 final Object conversionSrc = 234 isComplexObject(src) ? extractConversionValue(src, targetCls, 235 ci) : src; 236 return convertValue(ci.interpolate(conversionSrc), targetCls, ci); 237 } 238 239 /** 240 * Extracts a maximum number of values contained in the given source object 241 * and returns them as flat collection. This method is useful if the caller 242 * only needs a subset of values, e.g. only the first one. 243 * 244 * @param source the source object (may be a single value or a complex 245 * object) 246 * @param limit the number of elements to extract 247 * @return a collection with all extracted values 248 */ 249 protected Collection<?> extractValues(final Object source, final int limit) 250 { 251 return EXTRACTOR.flatten(source, limit); 252 } 253 254 /** 255 * Extracts all values contained in the given source object and returns them 256 * as a flat collection. 257 * 258 * @param source the source object (may be a single value or a complex 259 * object) 260 * @return a collection with all extracted values 261 */ 262 protected Collection<?> extractValues(final Object source) 263 { 264 return extractValues(source, Integer.MAX_VALUE); 265 } 266 267 /** 268 * Extracts a single value from a complex object. This method is called by 269 * {@code convert()} if the source object is complex. This implementation 270 * extracts the first value from the complex object and returns it. 271 * 272 * @param container the complex object 273 * @param targetCls the target class of the conversion 274 * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>) 275 * @return the value to be converted (may be <b>null</b> if no values are 276 * found) 277 */ 278 protected Object extractConversionValue(final Object container, 279 final Class<?> targetCls, final ConfigurationInterpolator ci) 280 { 281 final Collection<?> values = extractValues(container, 1); 282 return values.isEmpty() ? null : ci.interpolate(values.iterator() 283 .next()); 284 } 285 286 /** 287 * Performs a conversion of a single value to the specified target class. 288 * The passed in source object is guaranteed to be a single value, but it 289 * can be <b>null</b>. Derived classes that want to extend the available 290 * conversions, but are happy with the handling of complex objects, just 291 * need to override this method. 292 * 293 * @param <T> the desired target type of the conversion 294 * @param src the source object (a single value) 295 * @param targetCls the target class of the conversion 296 * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>) 297 * @return the converted value 298 * @throws ConversionException if conversion is not possible 299 */ 300 protected <T> T convertValue(final Object src, final Class<T> targetCls, 301 final ConfigurationInterpolator ci) 302 { 303 if (src == null) 304 { 305 return null; 306 } 307 308 // This is a safe cast because PropertyConverter either returns an 309 // object of the correct class or throws an exception. 310 @SuppressWarnings("unchecked") 311 final 312 T result = (T) PropertyConverter.to(targetCls, src, 313 this); 314 return result; 315 } 316 317 /** 318 * Converts the given source object to an array of objects. 319 * 320 * @param src the source object 321 * @param elemClass the element class of the array 322 * @param ci the {@code ConfigurationInterpolator} 323 * @return the result array 324 * @throws ConversionException if a conversion cannot be performed 325 */ 326 private <T> T[] toObjectArray(final Object src, final Class<T> elemClass, 327 final ConfigurationInterpolator ci) 328 { 329 final Collection<T> convertedCol = new LinkedList<>(); 330 convertToCollection(src, elemClass, ci, convertedCol); 331 // Safe to cast because the element class is specified 332 @SuppressWarnings("unchecked") 333 final 334 T[] result = (T[]) Array.newInstance(elemClass, convertedCol.size()); 335 return convertedCol.toArray(result); 336 } 337 338 /** 339 * Converts the given source object to an array of a primitive type. This 340 * method performs some checks whether the source object is already an array 341 * of the correct type or a corresponding wrapper type. If not, all values 342 * are extracted, converted one by one, and stored in a newly created array. 343 * 344 * @param src the source object 345 * @param elemClass the element class of the array 346 * @param ci the {@code ConfigurationInterpolator} 347 * @return the result array 348 * @throws ConversionException if a conversion cannot be performed 349 */ 350 private Object toPrimitiveArray(final Object src, final Class<?> elemClass, 351 final ConfigurationInterpolator ci) 352 { 353 if (src.getClass().isArray()) 354 { 355 if (src.getClass().getComponentType().equals(elemClass)) 356 { 357 return src; 358 } 359 360 if (src.getClass().getComponentType() 361 .equals(ClassUtils.primitiveToWrapper(elemClass))) 362 { 363 // the value is an array of the wrapper type derived from the 364 // specified primitive type 365 final int length = Array.getLength(src); 366 final Object array = Array.newInstance(elemClass, length); 367 368 for (int i = 0; i < length; i++) 369 { 370 Array.set(array, i, Array.get(src, i)); 371 } 372 return array; 373 } 374 } 375 376 final Collection<?> values = extractValues(src); 377 final Class<?> targetClass = ClassUtils.primitiveToWrapper(elemClass); 378 final Object array = Array.newInstance(elemClass, values.size()); 379 int idx = 0; 380 for (final Object value : values) 381 { 382 Array.set(array, idx++, 383 convertValue(ci.interpolate(value), targetClass, ci)); 384 } 385 return array; 386 } 387 388 /** 389 * Helper method for converting all values of a source object and storing 390 * them in a collection. 391 * 392 * @param <T> the target type of the conversion 393 * @param src the source object 394 * @param elemClass the target class of the conversion 395 * @param ci the {@code ConfigurationInterpolator} 396 * @param dest the collection in which to store the results 397 * @throws ConversionException if a conversion cannot be performed 398 */ 399 private <T> void convertToCollection(final Object src, final Class<T> elemClass, 400 final ConfigurationInterpolator ci, final Collection<T> dest) 401 { 402 for (final Object o : extractValues(ci.interpolate(src))) 403 { 404 dest.add(convert(o, elemClass, ci)); 405 } 406 } 407 408 /** 409 * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not 410 * <b>null</b>, it is used. Otherwise, a default one is returned. 411 * 412 * @param ci the {@code ConfigurationInterpolator} provided by the caller 413 * @return the {@code ConfigurationInterpolator} to be used 414 */ 415 private static ConfigurationInterpolator fetchInterpolator( 416 final ConfigurationInterpolator ci) 417 { 418 return (ci != null) ? ci : NULL_INTERPOLATOR; 419 } 420}