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