DefaultConversionHandler.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.convert;

  18. import java.lang.reflect.Array;
  19. import java.util.Collection;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;

  22. import org.apache.commons.configuration2.ex.ConversionException;
  23. import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
  24. import org.apache.commons.lang3.ClassUtils;

  25. /**
  26.  * <p>
  27.  * A default implementation of the {@code ConversionHandler} interface.
  28.  * </p>
  29.  * <p>
  30.  * This class implements the standard data type conversions as used by {@code AbstractConfiguration} and derived
  31.  * classes. There is a central conversion method - {@code convert()} - for converting a passed in object to a given
  32.  * target class. The basic implementation already handles a bunch of standard data type conversions. If other
  33.  * conversions are to be supported, this method can be overridden.
  34.  * </p>
  35.  * <p>
  36.  * The object passed to {@code convert()} can be a single value or a complex object (like an array, a collection, etc.)
  37.  * containing multiple values. It lies in the responsibility of {@code convert()} to deal with such complex objects. The
  38.  * implementation provided by this class tries to extract the first child element and then delegates to
  39.  * {@code convertValue()} which does the actual conversion.
  40.  * </p>
  41.  *
  42.  * @since 2.0
  43.  */
  44. public class DefaultConversionHandler implements ConversionHandler {

  45.     /**
  46.      * A default instance of this class. Because an instance of this class can be shared between arbitrary objects it is
  47.      * possible to make use of this default instance anywhere.
  48.      */
  49.     public static final DefaultConversionHandler INSTANCE = new DefaultConversionHandler();

  50.     /** The default format for dates. */
  51.     public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

  52.     /**
  53.      * Constant for a default {@code ConfigurationInterpolator} to be used if none is provided by the caller.
  54.      */
  55.     private static final ConfigurationInterpolator NULL_INTERPOLATOR = new ConfigurationInterpolator() {
  56.         @Override
  57.         public Object interpolate(final Object value) {
  58.             return value;
  59.         }
  60.     };

  61.     /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
  62.     static final ListDelimiterHandler LIST_DELIMITER_HANDLER = DisabledListDelimiterHandler.INSTANCE;

  63.     /**
  64.      * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <strong>null</strong>, it is used. Otherwise, a
  65.      * default one is returned.
  66.      *
  67.      * @param ci the {@code ConfigurationInterpolator} provided by the caller
  68.      * @return the {@code ConfigurationInterpolator} to be used
  69.      */
  70.     private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) {
  71.         return ci != null ? ci : NULL_INTERPOLATOR;
  72.     }

  73.     /** The current date format. */
  74.     private volatile String dateFormat;

  75.     /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
  76.     private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;

  77.     /**
  78.      * Performs the conversion from the passed in source object to the specified target class. This method is called for
  79.      * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so
  80.      * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary
  81.      * for extracting values from complex objects; it is guaranteed to be non <strong>null</strong>.) The source object may be a
  82.      * complex object, for example a collection or an array. This base implementation checks whether the source object is complex.
  83.      * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single
  84.      * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to
  85.      * be converted.
  86.      *
  87.      * @param <T> the desired target type of the conversion
  88.      * @param src the source object to be converted
  89.      * @param targetCls the desired target class
  90.      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
  91.      * @return the converted value
  92.      * @throws ConversionException if conversion is not possible
  93.      */
  94.     protected <T> T convert(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
  95.         final Object conversionSrc = isComplexObject(src) ? extractConversionValue(src, targetCls, ci) : src;
  96.         return convertValue(ci.interpolate(conversionSrc), targetCls, ci);
  97.     }

  98.     /**
  99.      * Helper method for converting all values of a source object and storing them in a collection.
  100.      *
  101.      * @param <T> the target type of the conversion
  102.      * @param src the source object
  103.      * @param elemClass the target class of the conversion
  104.      * @param ci the {@code ConfigurationInterpolator}
  105.      * @param dest the collection in which to store the results
  106.      * @throws ConversionException if a conversion cannot be performed
  107.      */
  108.     private <T> void convertToCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
  109.         extractValues(ci.interpolate(src)).forEach(o -> dest.add(convert(o, elemClass, ci)));
  110.     }

  111.     /**
  112.      * Performs a conversion of a single value to the specified target class. The passed in source object is guaranteed to
  113.      * be a single value, but it can be <strong>null</strong>. Derived classes that want to extend the available conversions, but are
  114.      * happy with the handling of complex objects, just need to override this method.
  115.      *
  116.      * @param <T> the desired target type of the conversion
  117.      * @param src the source object (a single value)
  118.      * @param targetCls the target class of the conversion
  119.      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
  120.      * @return the converted value
  121.      * @throws ConversionException if conversion is not possible
  122.      */
  123.     protected <T> T convertValue(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
  124.         if (src == null) {
  125.             return null;
  126.         }

  127.         // This is a safe cast because PropertyConverter either returns an
  128.         // object of the correct class or throws an exception.
  129.         @SuppressWarnings("unchecked")
  130.         final T result = (T) PropertyConverter.to(targetCls, src, this);
  131.         return result;
  132.     }

  133.     /**
  134.      * Extracts a single value from a complex object. This method is called by {@code convert()} if the source object is
  135.      * complex. This implementation extracts the first value from the complex object and returns it.
  136.      *
  137.      * @param container the complex object
  138.      * @param targetCls the target class of the conversion
  139.      * @param ci the {@code ConfigurationInterpolator} (not <strong>null</strong>)
  140.      * @return the value to be converted (may be <strong>null</strong> if no values are found)
  141.      */
  142.     protected Object extractConversionValue(final Object container, final Class<?> targetCls, final ConfigurationInterpolator ci) {
  143.         final Collection<?> values = extractValues(container, 1);
  144.         return values.isEmpty() ? null : ci.interpolate(values.iterator().next());
  145.     }

  146.     /**
  147.      * Extracts all values contained in the given source object and returns them as a flat collection.
  148.      *
  149.      * @param source the source object (may be a single value or a complex object)
  150.      * @return a collection with all extracted values
  151.      */
  152.     protected Collection<?> extractValues(final Object source) {
  153.         return extractValues(source, Integer.MAX_VALUE);
  154.     }

  155.     /**
  156.      * Extracts a maximum number of values contained in the given source object and returns them as flat collection. This
  157.      * method is useful if the caller only needs a subset of values, for example only the first one.
  158.      *
  159.      * @param source the source object (may be a single value or a complex object)
  160.      * @param limit the number of elements to extract
  161.      * @return a collection with all extracted values
  162.      */
  163.     protected Collection<?> extractValues(final Object source, final int limit) {
  164.         return listDelimiterHandler.flatten(source, limit);
  165.     }

  166.     /**
  167.      * Gets the date format used by this conversion handler.
  168.      *
  169.      * @return the date format
  170.      */
  171.     public String getDateFormat() {
  172.         final String fmt = dateFormat;
  173.         return fmt != null ? fmt : DEFAULT_DATE_FORMAT;
  174.     }

  175.     /**
  176.      * Gets the {@link ListDelimiterHandler} used for extracting values from complex objects.
  177.      *
  178.      * @return the {@link ListDelimiterHandler} used for extracting values from complex objects, never null.
  179.      * @since 2.9.0
  180.      */
  181.     public ListDelimiterHandler getListDelimiterHandler() {
  182.         return listDelimiterHandler;
  183.     }

  184.     /**
  185.      * Tests whether the passed in object is complex (which means that it contains multiple values). This method is called
  186.      * by {@link #convert(Object, Class, ConfigurationInterpolator)} to figure out whether a actions are required to extract
  187.      * a single value from a complex source object. This implementation considers the following objects as complex:
  188.      * <ul>
  189.      * <li>{@code Iterable} objects</li>
  190.      * <li>{@code Iterator} objects</li>
  191.      * <li>Arrays</li>
  192.      * </ul>
  193.      *
  194.      * @param src the source object
  195.      * @return <strong>true</strong> if this is a complex object, <strong>false</strong> otherwise
  196.      */
  197.     protected boolean isComplexObject(final Object src) {
  198.         return src instanceof Iterator<?> || src instanceof Iterable<?> || src != null && src.getClass().isArray();
  199.     }

  200.     /**
  201.      * Tests whether the passed in object represents an empty element. This method is called by conversion methods to arrays
  202.      * or collections. If it returns <strong>true</strong>, the resulting array or collection will be empty. This implementation
  203.      * returns <strong>true</strong> if and only if the passed in object is an empty string. With this method it can be controlled if
  204.      * and how empty elements in configurations are handled.
  205.      *
  206.      * @param src the object to be tested
  207.      * @return a flag whether this object is an empty element
  208.      */
  209.     protected boolean isEmptyElement(final Object src) {
  210.         return src instanceof CharSequence && ((CharSequence) src).length() == 0;
  211.     }

  212.     /**
  213.      * Sets the date format to be used by this conversion handler. This format is applied by conversions to {@code Date} or
  214.      * {@code Calendar} objects. The string is passed to the {@link java.text.SimpleDateFormat} class, so it must be
  215.      * compatible with this class. If no date format has been set, a default format is used.
  216.      *
  217.      * @param dateFormat the date format string
  218.      * @see #DEFAULT_DATE_FORMAT
  219.      */
  220.     public void setDateFormat(final String dateFormat) {
  221.         this.dateFormat = dateFormat;
  222.     }

  223.     /**
  224.      * Sets the {@link ListDelimiterHandler} used for extracting values from complex objects.
  225.      *
  226.      * @param listDelimiterHandler the {@link ListDelimiterHandler} used for extracting values from complex objects. Setting
  227.      *        the value to null resets the value to its default.
  228.      * @since 2.9.0
  229.      */
  230.     public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
  231.         this.listDelimiterHandler = listDelimiterHandler != null ? listDelimiterHandler : LIST_DELIMITER_HANDLER;
  232.     }

  233.     @Override
  234.     public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
  235.         final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
  236.         return convert(interpolator.interpolate(src), targetCls, interpolator);
  237.     }

  238.     /**
  239.      * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
  240.      * target type, and adds them to a result array. Arrays of objects and of primitive types are supported. If the source
  241.      * object is <strong>null</strong>, result is <strong>null</strong>, too.
  242.      */
  243.     @Override
  244.     public Object toArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
  245.         if (src == null) {
  246.             return null;
  247.         }
  248.         if (isEmptyElement(src)) {
  249.             return Array.newInstance(elemClass, 0);
  250.         }

  251.         final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
  252.         return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass, interpolator) : toObjectArray(src, elemClass, interpolator);
  253.     }

  254.     /**
  255.      * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
  256.      * target type, and adds them to the target collection. The target collection must not be <strong>null</strong>. If the source
  257.      * object is <strong>null</strong>, nothing is added to the collection.
  258.      *
  259.      * @throws IllegalArgumentException if the target collection is <strong>null</strong>
  260.      */
  261.     @Override
  262.     public <T> void toCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
  263.         if (dest == null) {
  264.             throw new IllegalArgumentException("Target collection must not be null!");
  265.         }

  266.         if (src != null && !isEmptyElement(src)) {
  267.             convertToCollection(src, elemClass, fetchInterpolator(ci), dest);
  268.         }
  269.     }

  270.     /**
  271.      * Converts the given source object to an array of objects.
  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 <T> T[] toObjectArray(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci) {
  280.         final Collection<T> convertedCol = new LinkedList<>();
  281.         convertToCollection(src, elemClass, ci, convertedCol);
  282.         // Safe to cast because the element class is specified
  283.         @SuppressWarnings("unchecked")
  284.         final T[] result = (T[]) Array.newInstance(elemClass, convertedCol.size());
  285.         return convertedCol.toArray(result);
  286.     }

  287.     /**
  288.      * Converts the given source object to an array of a primitive type. This method performs some checks whether the source
  289.      * object is already an array of the correct type or a corresponding wrapper type. If not, all values are extracted,
  290.      * converted one by one, and stored in a newly created array.
  291.      *
  292.      * @param src the source object
  293.      * @param elemClass the element class of the array
  294.      * @param ci the {@code ConfigurationInterpolator}
  295.      * @return the result array
  296.      * @throws ConversionException if a conversion cannot be performed
  297.      */
  298.     private Object toPrimitiveArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
  299.         if (src.getClass().isArray()) {
  300.             if (src.getClass().getComponentType().equals(elemClass)) {
  301.                 return src;
  302.             }

  303.             if (src.getClass().getComponentType().equals(ClassUtils.primitiveToWrapper(elemClass))) {
  304.                 // the value is an array of the wrapper type derived from the
  305.                 // specified primitive type
  306.                 final int length = Array.getLength(src);
  307.                 final Object array = Array.newInstance(elemClass, length);

  308.                 for (int i = 0; i < length; i++) {
  309.                     Array.set(array, i, Array.get(src, i));
  310.                 }
  311.                 return array;
  312.             }
  313.         }

  314.         final Collection<?> values = extractValues(src);
  315.         final Class<?> targetClass = ClassUtils.primitiveToWrapper(elemClass);
  316.         final Object array = Array.newInstance(elemClass, values.size());
  317.         int idx = 0;
  318.         for (final Object value : values) {
  319.             Array.set(array, idx++, convertValue(ci.interpolate(value), targetClass, ci));
  320.         }
  321.         return array;
  322.     }
  323. }