AbstractListDelimiterHandler.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.nio.file.Path;
  20. import java.util.ArrayList;
  21. import java.util.Collection;
  22. import java.util.Iterator;
  23. import java.util.LinkedList;
  24. import java.util.Set;

  25. /**
  26.  * <p>
  27.  * An abstract base class for concrete {@code ListDelimiterHandler} implementations.
  28.  * </p>
  29.  * <p>
  30.  * This base class provides a fully functional implementation for parsing a value object which can deal with different
  31.  * cases like collections, arrays, iterators, etc. This logic is typically needed by every concrete subclass. Other
  32.  * methods are partly implemented handling special corner cases like <strong>null</strong> values; concrete subclasses do not have
  33.  * do implement the corresponding checks.
  34.  * </p>
  35.  *
  36.  * @since 2.0
  37.  */
  38. public abstract class AbstractListDelimiterHandler implements ListDelimiterHandler {

  39.     static Collection<?> flatten(final ListDelimiterHandler handler, final Object value, final int limit, final Set<Object> dejaVu) {
  40.         dejaVu.add(value);
  41.         if (value instanceof String) {
  42.             return handler.split((String) value, true);
  43.         }
  44.         final Collection<Object> result = new LinkedList<>();
  45.         if (value instanceof Path) {
  46.             // Don't handle as an Iterable.
  47.             result.add(value);
  48.         } else if (value instanceof Iterable) {
  49.             flattenIterator(handler, result, ((Iterable<?>) value).iterator(), limit, dejaVu);
  50.         } else if (value instanceof Iterator) {
  51.             flattenIterator(handler, result, (Iterator<?>) value, limit, dejaVu);
  52.         } else if (value != null) {
  53.             if (value.getClass().isArray()) {
  54.                 for (int len = Array.getLength(value), idx = 0, size = 0; idx < len && size < limit; idx++, size = result.size()) {
  55.                     result.addAll(handler.flatten(Array.get(value, idx), limit - size));
  56.                 }
  57.             } else {
  58.                 result.add(value);
  59.             }
  60.         }
  61.         return result;
  62.     }

  63.     /**
  64.      * Flattens the given iterator. For each element in the iteration {@code flatten()} is called recursively.
  65.      *
  66.      * @param handler the working handler
  67.      * @param target the target collection
  68.      * @param iterator the iterator to process
  69.      * @param limit a limit for the number of elements to extract
  70.      * @param dejaVue Previously visited objects.
  71.      */
  72.     static void flattenIterator(final ListDelimiterHandler handler, final Collection<Object> target, final Iterator<?> iterator, final int limit,
  73.             final Set<Object> dejaVue) {
  74.         int size = target.size();
  75.         while (size < limit && iterator.hasNext()) {
  76.             final Object next = iterator.next();
  77.             if (!dejaVue.contains(next)) {
  78.                 target.addAll(flatten(handler, next, limit - size, dejaVue));
  79.                 size = target.size();
  80.             }
  81.         }
  82.     }

  83.     /**
  84.      * {@inheritDoc} This implementation checks whether the object to be escaped is a string. If yes, it delegates to
  85.      * {@link #escapeString(String)}, otherwise no escaping is performed. Eventually, the passed in transformer is invoked
  86.      * so that additional encoding can be performed.
  87.      */
  88.     @Override
  89.     public Object escape(final Object value, final ValueTransformer transformer) {
  90.         return transformer.transformValue(value instanceof String ? escapeString((String) value) : value);
  91.     }

  92.     /**
  93.      * Escapes the specified string. This method is called by {@code escape()} if the passed in object is a string. Concrete
  94.      * subclasses have to implement their specific escaping logic here, so that the list delimiters they support are
  95.      * properly escaped.
  96.      *
  97.      * @param s the string to be escaped (not <strong>null</strong>)
  98.      * @return the escaped string
  99.      */
  100.     protected abstract String escapeString(String s);

  101.     /**
  102.      * Performs the actual work as advertised by the {@code parse()} method. This method delegates to
  103.      * {@link #flatten(Object, int)} without specifying a limit.
  104.      *
  105.      * @param value the value to be processed
  106.      * @return a &quot;flat&quot; collection containing all primitive values of the passed in object
  107.      */
  108.     private Collection<?> flatten(final Object value) {
  109.         return flatten(value, Integer.MAX_VALUE);
  110.     }

  111.     /**
  112.      * {@inheritDoc} Depending on the type of the passed in object the following things happen:
  113.      * <ul>
  114.      * <li>Strings are checked for delimiter characters and split if necessary. This is done by calling the {@code split()}
  115.      * method.</li>
  116.      * <li>For objects implementing the {@code Iterable} interface, the corresponding {@code Iterator} is obtained, and
  117.      * contained elements are added to the resulting iteration.</li>
  118.      * <li>Arrays are treated as {@code Iterable} objects.</li>
  119.      * <li>All other types are directly inserted.</li>
  120.      * <li>Recursive combinations are supported, for example a collection containing an array that contains strings: The resulting
  121.      * collection will only contain primitive objects.</li>
  122.      * </ul>
  123.      */
  124.     @Override
  125.     public Iterable<?> parse(final Object value) {
  126.         return flatten(value);
  127.     }

  128.     /**
  129.      * {@inheritDoc} This implementation handles the case that the passed in string is <strong>null</strong>. In this case, an empty
  130.      * collection is returned. Otherwise, this method delegates to {@link #splitString(String, boolean)}.
  131.      */
  132.     @Override
  133.     public Collection<String> split(final String s, final boolean trim) {
  134.         return s == null ? new ArrayList<>(0) : splitString(s, trim);
  135.     }

  136.     /**
  137.      * Actually splits the passed in string which is guaranteed to be not <strong>null</strong>. This method is called by the base
  138.      * implementation of the {@code split()} method. Here the actual splitting logic has to be implemented.
  139.      *
  140.      * @param s the string to be split (not <strong>null</strong>)
  141.      * @param trim a flag whether the single components have to be trimmed
  142.      * @return a collection with the extracted components of the passed in string
  143.      */
  144.     protected abstract Collection<String> splitString(String s, boolean trim);
  145. }