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