You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/07/14 14:34:37 UTC

[commons-configuration] 04/04: Add DefaultConversionHandler#setListDelimiterHandler(ListDelimiterHandler).

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-configuration.git

commit 5de7c48e4d591e638562672fbbe38db5d14e89b3
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Jul 14 10:34:26 2022 -0400

    Add DefaultConversionHandler#setListDelimiterHandler(ListDelimiterHandler).
---
 src/changes/changes.xml                            |   3 +
 .../convert/DefaultConversionHandler.java          | 281 +++++++++++----------
 .../convert/TestDefaultConversionHandler.java      |  14 +-
 3 files changed, 169 insertions(+), 129 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 3e0a1fe8..b53a320a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,6 +30,9 @@
         CombinedConfiguration#getKeys() can throw NoSuchElementException.
       </action>
       <!-- ADD -->
+      <action issue="CONFIGURATION-799" type="fix" dev="ggregory" due-to="Xinshiyou, Gary Gregory">
+        Add DefaultConversionHandler#setListDelimiterHandler(ListDelimiterHandler).
+      </action>
       <!-- UPDATE -->
       <action type="update" dev="ggregory" due-to="Dependabot">
         Bump spotbugs-maven-plugin from 4.7.0.0 to 4.7.1.0 #193.
diff --git a/src/main/java/org/apache/commons/configuration2/convert/DefaultConversionHandler.java b/src/main/java/org/apache/commons/configuration2/convert/DefaultConversionHandler.java
index acd3d1ba..4cfb856f 100644
--- a/src/main/java/org/apache/commons/configuration2/convert/DefaultConversionHandler.java
+++ b/src/main/java/org/apache/commons/configuration2/convert/DefaultConversionHandler.java
@@ -45,6 +45,7 @@ import org.apache.commons.lang3.ClassUtils;
  * @since 2.0
  */
 public class DefaultConversionHandler implements ConversionHandler {
+
     /**
      * A default instance of this class. Because an instance of this class can be shared between arbitrary objects it is
      * possible to make use of this default instance anywhere.
@@ -54,9 +55,6 @@ public class DefaultConversionHandler implements ConversionHandler {
     /** The default format for dates. */
     public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
 
-    /** A helper object used for extracting values from complex objects. */
-    private static final ListDelimiterHandler EXTRACTOR = DisabledListDelimiterHandler.INSTANCE;
-
     /**
      * Constant for a default {@code ConfigurationInterpolator} to be used if none is provided by the caller.
      */
@@ -67,72 +65,142 @@ public class DefaultConversionHandler implements ConversionHandler {
         }
     };
 
+    /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
+    static final ListDelimiterHandler LIST_DELIMITER_HANDLER = DisabledListDelimiterHandler.INSTANCE;
+
     /** The current date format. */
     private volatile String dateFormat;
 
+    /** The default {@link ListDelimiterHandler} used for extracting values from complex objects. */
+    private volatile ListDelimiterHandler listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE;
+
     /**
-     * Returns the date format used by this conversion handler.
+     * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <b>null</b>, it is used. Otherwise, a
+     * default one is returned.
      *
-     * @return the date format
+     * @param ci the {@code ConfigurationInterpolator} provided by the caller
+     * @return the {@code ConfigurationInterpolator} to be used
      */
-    public String getDateFormat() {
-        final String fmt = dateFormat;
-        return fmt != null ? fmt : DEFAULT_DATE_FORMAT;
+    private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) {
+        return ci != null ? ci : NULL_INTERPOLATOR;
     }
 
     /**
-     * Sets the date format to be used by this conversion handler. This format is applied by conversions to {@code Date} or
-     * {@code Calendar} objects. The string is passed to the {@code java.text.SimpleDateFormat} class, so it must be
-     * compatible with this class. If no date format has been set, a default format is used.
+     * Performs the conversion from the passed in source object to the specified target class. This method is called for
+     * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so
+     * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary
+     * for extracting values from complex objects; it is guaranteed to be non <b>null</b>.) The source object may be a
+     * complex object, e.g. a collection or an array. This base implementation checks whether the source object is complex.
+     * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single
+     * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to
+     * be converted.
      *
-     * @param dateFormat the date format string
-     * @see #DEFAULT_DATE_FORMAT
+     * @param <T> the desired target type of the conversion
+     * @param src the source object to be converted
+     * @param targetCls the desired target class
+     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
+     * @return the converted value
+     * @throws ConversionException if conversion is not possible
      */
-    public void setDateFormat(final String dateFormat) {
-        this.dateFormat = dateFormat;
+    protected <T> T convert(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
+        final Object conversionSrc = isComplexObject(src) ? extractConversionValue(src, targetCls, ci) : src;
+        return convertValue(ci.interpolate(conversionSrc), targetCls, ci);
     }
 
-    @Override
-    public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
-        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
-        return convert(interpolator.interpolate(src), targetCls, interpolator);
+    /**
+     * Helper method for converting all values of a source object and storing them in a collection.
+     *
+     * @param <T> the target type of the conversion
+     * @param src the source object
+     * @param elemClass the target class of the conversion
+     * @param ci the {@code ConfigurationInterpolator}
+     * @param dest the collection in which to store the results
+     * @throws ConversionException if a conversion cannot be performed
+     */
+    private <T> void convertToCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
+        for (final Object o : extractValues(ci.interpolate(src))) {
+            dest.add(convert(o, elemClass, ci));
+        }
     }
 
     /**
-     * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
-     * target type, and adds them to a result array. Arrays of objects and of primitive types are supported. If the source
-     * object is <b>null</b>, result is <b>null</b>, too.
+     * Performs a conversion of a single value to the specified target class. The passed in source object is guaranteed to
+     * be a single value, but it can be <b>null</b>. Derived classes that want to extend the available conversions, but are
+     * happy with the handling of complex objects, just need to override this method.
+     *
+     * @param <T> the desired target type of the conversion
+     * @param src the source object (a single value)
+     * @param targetCls the target class of the conversion
+     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
+     * @return the converted value
+     * @throws ConversionException if conversion is not possible
      */
-    @Override
-    public Object toArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
+    protected <T> T convertValue(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
         if (src == null) {
             return null;
         }
-        if (isEmptyElement(src)) {
-            return Array.newInstance(elemClass, 0);
-        }
 
-        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
-        return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass, interpolator) : toObjectArray(src, elemClass, interpolator);
+        // This is a safe cast because PropertyConverter either returns an
+        // object of the correct class or throws an exception.
+        @SuppressWarnings("unchecked")
+        final T result = (T) PropertyConverter.to(targetCls, src, this);
+        return result;
     }
 
     /**
-     * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
-     * target type, and adds them to the target collection. The target collection must not be <b>null</b>. If the source
-     * object is <b>null</b>, nothing is added to the collection.
+     * Extracts a single value from a complex object. This method is called by {@code convert()} if the source object is
+     * complex. This implementation extracts the first value from the complex object and returns it.
      *
-     * @throws IllegalArgumentException if the target collection is <b>null</b>
+     * @param container the complex object
+     * @param targetCls the target class of the conversion
+     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
+     * @return the value to be converted (may be <b>null</b> if no values are found)
      */
-    @Override
-    public <T> void toCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
-        if (dest == null) {
-            throw new IllegalArgumentException("Target collection must not be null!");
-        }
+    protected Object extractConversionValue(final Object container, final Class<?> targetCls, final ConfigurationInterpolator ci) {
+        final Collection<?> values = extractValues(container, 1);
+        return values.isEmpty() ? null : ci.interpolate(values.iterator().next());
+    }
 
-        if (src != null && !isEmptyElement(src)) {
-            final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
-            convertToCollection(src, elemClass, interpolator, dest);
-        }
+    /**
+     * Extracts all values contained in the given source object and returns them as a flat collection.
+     *
+     * @param source the source object (may be a single value or a complex object)
+     * @return a collection with all extracted values
+     */
+    protected Collection<?> extractValues(final Object source) {
+        return extractValues(source, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Extracts a maximum number of values contained in the given source object and returns them as flat collection. This
+     * method is useful if the caller only needs a subset of values, e.g. only the first one.
+     *
+     * @param source the source object (may be a single value or a complex object)
+     * @param limit the number of elements to extract
+     * @return a collection with all extracted values
+     */
+    protected Collection<?> extractValues(final Object source, final int limit) {
+        return listDelimiterHandler.flatten(source, limit);
+    }
+
+    /**
+     * Returns the date format used by this conversion handler.
+     *
+     * @return the date format
+     */
+    public String getDateFormat() {
+        final String fmt = dateFormat;
+        return fmt != null ? fmt : DEFAULT_DATE_FORMAT;
+    }
+
+    /**
+     * Gets the {@link ListDelimiterHandler} used for extracting values from complex objects.
+     *
+     * @return the {@link ListDelimiterHandler} used for extracting values from complex objects, never null.
+     * @since 2.9.0
+     */
+    public ListDelimiterHandler getListDelimiterHandler() {
+        return listDelimiterHandler;
     }
 
     /**
@@ -166,85 +234,69 @@ public class DefaultConversionHandler implements ConversionHandler {
     }
 
     /**
-     * Performs the conversion from the passed in source object to the specified target class. This method is called for
-     * each conversion to be done. The source object has already been passed to the {@link ConfigurationInterpolator}, so
-     * interpolation does not have to be done again. (The passed in {@code ConfigurationInterpolator} may still be necessary
-     * for extracting values from complex objects; it is guaranteed to be non <b>null</b>.) The source object may be a
-     * complex object, e.g. a collection or an array. This base implementation checks whether the source object is complex.
-     * If so, it delegates to {@link #extractConversionValue(Object, Class, ConfigurationInterpolator)} to obtain a single
-     * value. Eventually, {@link #convertValue(Object, Class, ConfigurationInterpolator)} is called with the single value to
-     * be converted.
+     * Sets the date format to be used by this conversion handler. This format is applied by conversions to {@code Date} or
+     * {@code Calendar} objects. The string is passed to the {@code java.text.SimpleDateFormat} class, so it must be
+     * compatible with this class. If no date format has been set, a default format is used.
      *
-     * @param <T> the desired target type of the conversion
-     * @param src the source object to be converted
-     * @param targetCls the desired target class
-     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
-     * @return the converted value
-     * @throws ConversionException if conversion is not possible
+     * @param dateFormat the date format string
+     * @see #DEFAULT_DATE_FORMAT
      */
-    protected <T> T convert(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
-        final Object conversionSrc = isComplexObject(src) ? extractConversionValue(src, targetCls, ci) : src;
-        return convertValue(ci.interpolate(conversionSrc), targetCls, ci);
+    public void setDateFormat(final String dateFormat) {
+        this.dateFormat = dateFormat;
     }
 
     /**
-     * Extracts a maximum number of values contained in the given source object and returns them as flat collection. This
-     * method is useful if the caller only needs a subset of values, e.g. only the first one.
+     * Sets the {@link ListDelimiterHandler} used for extracting values from complex objects.
      *
-     * @param source the source object (may be a single value or a complex object)
-     * @param limit the number of elements to extract
-     * @return a collection with all extracted values
+     * @param listDelimiterHandler the {@link ListDelimiterHandler} used for extracting values from complex objects. Setting
+     *        the value to null resets the value to its default.
+     * @since 2.9.0
      */
-    protected Collection<?> extractValues(final Object source, final int limit) {
-        return EXTRACTOR.flatten(source, limit);
+    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
+        this.listDelimiterHandler = listDelimiterHandler != null ? listDelimiterHandler : LIST_DELIMITER_HANDLER;
     }
 
-    /**
-     * Extracts all values contained in the given source object and returns them as a flat collection.
-     *
-     * @param source the source object (may be a single value or a complex object)
-     * @return a collection with all extracted values
-     */
-    protected Collection<?> extractValues(final Object source) {
-        return extractValues(source, Integer.MAX_VALUE);
+    @Override
+    public <T> T to(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
+        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
+        return convert(interpolator.interpolate(src), targetCls, interpolator);
     }
 
     /**
-     * Extracts a single value from a complex object. This method is called by {@code convert()} if the source object is
-     * complex. This implementation extracts the first value from the complex object and returns it.
-     *
-     * @param container the complex object
-     * @param targetCls the target class of the conversion
-     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
-     * @return the value to be converted (may be <b>null</b> if no values are found)
+     * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
+     * target type, and adds them to a result array. Arrays of objects and of primitive types are supported. If the source
+     * object is <b>null</b>, result is <b>null</b>, too.
      */
-    protected Object extractConversionValue(final Object container, final Class<?> targetCls, final ConfigurationInterpolator ci) {
-        final Collection<?> values = extractValues(container, 1);
-        return values.isEmpty() ? null : ci.interpolate(values.iterator().next());
+    @Override
+    public Object toArray(final Object src, final Class<?> elemClass, final ConfigurationInterpolator ci) {
+        if (src == null) {
+            return null;
+        }
+        if (isEmptyElement(src)) {
+            return Array.newInstance(elemClass, 0);
+        }
+
+        final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
+        return elemClass.isPrimitive() ? toPrimitiveArray(src, elemClass, interpolator) : toObjectArray(src, elemClass, interpolator);
     }
 
     /**
-     * Performs a conversion of a single value to the specified target class. The passed in source object is guaranteed to
-     * be a single value, but it can be <b>null</b>. Derived classes that want to extend the available conversions, but are
-     * happy with the handling of complex objects, just need to override this method.
+     * {@inheritDoc} This implementation extracts all values stored in the passed in source object, converts them to the
+     * target type, and adds them to the target collection. The target collection must not be <b>null</b>. If the source
+     * object is <b>null</b>, nothing is added to the collection.
      *
-     * @param <T> the desired target type of the conversion
-     * @param src the source object (a single value)
-     * @param targetCls the target class of the conversion
-     * @param ci the {@code ConfigurationInterpolator} (not <b>null</b>)
-     * @return the converted value
-     * @throws ConversionException if conversion is not possible
+     * @throws IllegalArgumentException if the target collection is <b>null</b>
      */
-    protected <T> T convertValue(final Object src, final Class<T> targetCls, final ConfigurationInterpolator ci) {
-        if (src == null) {
-            return null;
+    @Override
+    public <T> void toCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
+        if (dest == null) {
+            throw new IllegalArgumentException("Target collection must not be null!");
         }
 
-        // This is a safe cast because PropertyConverter either returns an
-        // object of the correct class or throws an exception.
-        @SuppressWarnings("unchecked")
-        final T result = (T) PropertyConverter.to(targetCls, src, this);
-        return result;
+        if (src != null && !isEmptyElement(src)) {
+            final ConfigurationInterpolator interpolator = fetchInterpolator(ci);
+            convertToCollection(src, elemClass, interpolator, dest);
+        }
     }
 
     /**
@@ -304,31 +356,4 @@ public class DefaultConversionHandler implements ConversionHandler {
         }
         return array;
     }
-
-    /**
-     * Helper method for converting all values of a source object and storing them in a collection.
-     *
-     * @param <T> the target type of the conversion
-     * @param src the source object
-     * @param elemClass the target class of the conversion
-     * @param ci the {@code ConfigurationInterpolator}
-     * @param dest the collection in which to store the results
-     * @throws ConversionException if a conversion cannot be performed
-     */
-    private <T> void convertToCollection(final Object src, final Class<T> elemClass, final ConfigurationInterpolator ci, final Collection<T> dest) {
-        for (final Object o : extractValues(ci.interpolate(src))) {
-            dest.add(convert(o, elemClass, ci));
-        }
-    }
-
-    /**
-     * Obtains a {@code ConfigurationInterpolator}. If the passed in one is not <b>null</b>, it is used. Otherwise, a
-     * default one is returned.
-     *
-     * @param ci the {@code ConfigurationInterpolator} provided by the caller
-     * @return the {@code ConfigurationInterpolator} to be used
-     */
-    private static ConfigurationInterpolator fetchInterpolator(final ConfigurationInterpolator ci) {
-        return ci != null ? ci : NULL_INTERPOLATOR;
-    }
 }
diff --git a/src/test/java/org/apache/commons/configuration2/convert/TestDefaultConversionHandler.java b/src/test/java/org/apache/commons/configuration2/convert/TestDefaultConversionHandler.java
index 05d79161..ce751c83 100644
--- a/src/test/java/org/apache/commons/configuration2/convert/TestDefaultConversionHandler.java
+++ b/src/test/java/org/apache/commons/configuration2/convert/TestDefaultConversionHandler.java
@@ -76,7 +76,7 @@ public class TestDefaultConversionHandler {
     }
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() {
         handler = new DefaultConversionHandler();
     }
 
@@ -88,6 +88,18 @@ public class TestDefaultConversionHandler {
         assertEquals("Wrong date format", DefaultConversionHandler.DEFAULT_DATE_FORMAT, handler.getDateFormat());
     }
 
+    @Test
+    public synchronized void testListDelimiterHandler() {
+        assertEquals(DefaultConversionHandler.LIST_DELIMITER_HANDLER, handler.getListDelimiterHandler());
+        handler.setListDelimiterHandler(null);
+        assertEquals(DefaultConversionHandler.LIST_DELIMITER_HANDLER, handler.getListDelimiterHandler());
+        final LegacyListDelimiterHandler legacyListDelimiterHandler = new LegacyListDelimiterHandler(',');
+        handler.setListDelimiterHandler(legacyListDelimiterHandler);
+        assertEquals(legacyListDelimiterHandler, handler.getListDelimiterHandler());
+        handler.setListDelimiterHandler(null);
+        assertEquals(DefaultConversionHandler.LIST_DELIMITER_HANDLER, handler.getListDelimiterHandler());
+    }
+
     /**
      * Tests whether the date format can be changed.
      */