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 {@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 <b>null</b>, 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 * Performs the conversion from the passed in source object to the specified target class. This method is called for 090 * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so 091 * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary 092 * for extracting values from complex objects; it is guaranteed to be non <b>null</b>.) The source object may be a 093 * complex object, e.g. a collection or an array. This base implementation checks whether the source object is complex. 094 * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single 095 * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to 096 * be converted. 097 * 098 * @param <T> the desired target type of the conversion 099 * @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}