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.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.LinkedList;
024
025/**
026 * <p>
027 * An abstract base class for concrete {@code ListDelimiterHandler}
028 * implementations.
029 * </p>
030 * <p>
031 * This base class provides a fully functional implementation for parsing a
032 * value object which can deal with different cases like collections, arrays,
033 * iterators, etc. This logic is typically needed by every concrete subclass.
034 * Other methods are partly implemented handling special corner cases like
035 * <b>null</b> values; concrete subclasses do not have do implement the
036 * corresponding checks.
037 * </p>
038 *
039 * @since 2.0
040 */
041public abstract class AbstractListDelimiterHandler implements
042        ListDelimiterHandler
043{
044    /**
045     * {@inheritDoc} Depending on the type of the passed in object the following
046     * things happen:
047     * <ul>
048     * <li>Strings are checked for delimiter characters and split if necessary.
049     * This is done by calling the {@code split()} method.</li>
050     * <li>For objects implementing the {@code Iterable} interface, the
051     * corresponding {@code Iterator} is obtained, and contained elements are
052     * added to the resulting iteration.</li>
053     * <li>Arrays are treated as {@code Iterable} objects.</li>
054     * <li>All other types are directly inserted.</li>
055     * <li>Recursive combinations are supported, e.g. a collection containing an
056     * array that contains strings: The resulting collection will only contain
057     * primitive objects.</li>
058     * </ul>
059     */
060    @Override
061    public Iterable<?> parse(final Object value)
062    {
063        return flatten(value);
064    }
065
066    /**
067     * {@inheritDoc} This implementation handles the case that the passed in
068     * string is <b>null</b>. In this case, an empty collection is returned.
069     * Otherwise, this method delegates to {@link #splitString(String, boolean)}.
070     */
071    @Override
072    public Collection<String> split(final String s, final boolean trim)
073    {
074        if (s == null)
075        {
076            return new ArrayList<>(0);
077        }
078        return splitString(s, trim);
079    }
080
081    /**
082     * {@inheritDoc} This implementation checks whether the object to be escaped
083     * is a string. If yes, it delegates to {@link #escapeString(String)},
084     * otherwise no escaping is performed. Eventually, the passed in transformer
085     * is invoked so that additional encoding can be performed.
086     */
087    @Override
088    public Object escape(final Object value, final ValueTransformer transformer)
089    {
090        final Object escValue =
091                value instanceof String ? escapeString((String) value)
092                        : value;
093        return transformer.transformValue(escValue);
094    }
095
096    /**
097     * Actually splits the passed in string which is guaranteed to be not
098     * <b>null</b>. This method is called by the base implementation of the
099     * {@code split()} method. Here the actual splitting logic has to be
100     * implemented.
101     *
102     * @param s the string to be split (not <b>null</b>)
103     * @param trim a flag whether the single components have to be trimmed
104     * @return a collection with the extracted components of the passed in
105     *         string
106     */
107    protected abstract Collection<String> splitString(String s, boolean trim);
108
109    /**
110     * Escapes the specified string. This method is called by {@code escape()}
111     * if the passed in object is a string. Concrete subclasses have to
112     * implement their specific escaping logic here, so that the list delimiters
113     * they support are properly escaped.
114     *
115     * @param s the string to be escaped (not <b>null</b>)
116     * @return the escaped string
117     */
118    protected abstract String escapeString(String s);
119
120    /**
121     * Extracts all values contained in the specified object up to the given
122     * limit. The passed in object is evaluated (if necessary in a recursive
123     * way). If it is a complex object (e.g. a collection or an array), all its
124     * elements are processed recursively and added to a target collection. The
125     * process stops if the limit is reached, but depending on the input object,
126     * it might be exceeded. (The limit is just an indicator to stop the process
127     * to avoid unnecessary work if the caller is only interested in a few
128     * values.)
129     *
130     * @param value the value to be processed
131     * @param limit the limit for aborting the processing
132     * @return a &quot;flat&quot; collection containing all primitive values of
133     *         the passed in object
134     */
135    Collection<?> flatten(final Object value, final int limit)
136    {
137        if (value instanceof String)
138        {
139            return split((String) value, true);
140        }
141
142        final Collection<Object> result = new LinkedList<>();
143        if (value instanceof Iterable)
144        {
145            flattenIterator(result, ((Iterable<?>) value).iterator(), limit);
146        }
147        else if (value instanceof Iterator)
148        {
149            flattenIterator(result, (Iterator<?>) value, limit);
150        }
151        else if (value != null)
152        {
153            if (value.getClass().isArray())
154            {
155                for (int len = Array.getLength(value), idx = 0, size = 0; idx < len
156                        && size < limit; idx++, size = result.size())
157                {
158                    result.addAll(flatten(Array.get(value, idx), limit - size));
159                }
160            }
161            else
162            {
163                result.add(value);
164            }
165        }
166
167        return result;
168    }
169
170    /**
171     * Performs the actual work as advertised by the {@code parse()} method.
172     * This method delegates to {@link #flatten(Object, int)} without specifying
173     * a limit.
174     *
175     * @param value the value to be processed
176     * @return a &quot;flat&quot; collection containing all primitive values of
177     *         the passed in object
178     */
179    private Collection<?> flatten(final Object value)
180    {
181        return flatten(value, Integer.MAX_VALUE);
182    }
183
184    /**
185     * Flattens the given iterator. For each element in the iteration
186     * {@code flatten()} is called recursively.
187     *
188     * @param target the target collection
189     * @param it the iterator to process
190     * @param limit a limit for the number of elements to extract
191     */
192    private void flattenIterator(final Collection<Object> target, final Iterator<?> it, final int limit)
193    {
194        int size = target.size();
195        while (size < limit && it.hasNext())
196        {
197            target.addAll(flatten(it.next(), limit - size));
198            size = target.size();
199        }
200    }
201}