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;
023import java.util.List;
024
025/**
026 * <p>
027 * Definition of an interface that controls the handling of list delimiters in configuration properties.
028 * </p>
029 * <p>
030 * {@link org.apache.commons.configuration2.AbstractConfiguration AbstractConfiguration} supports list delimiters in
031 * property values. If such a delimiter is found, the value actually contains multiple values and has to be split. This
032 * is useful for instance for {@link org.apache.commons.configuration2.PropertiesConfiguration PropertiesConfiguration}:
033 * properties files that have to be compatible with the {@code java.util.Properties} class cannot have multiple
034 * occurrences of a single property key, therefore a different storage scheme for multi-valued properties is needed. A
035 * possible storage scheme could look as follows:
036 * </p>
037 *
038 * <pre>
039 * myProperty=value1,value2,value3
040 * </pre>
041 *
042 * <p>
043 * Here a comma is used as list delimiter. When parsing this property (and using a corresponding
044 * {@code ListDelimiterHandler} implementation) the string value is split, and three values are added for the property
045 * key.
046 * </p>
047 * <p>
048 * A {@code ListDelimiterHandler} knows how to parse and to escape property values. It is called by concrete
049 * {@code Configuration} implementations when they have to deal with properties with multiple values.
050 * </p>
051 *
052 * @since 2.0
053 */
054public interface ListDelimiterHandler {
055    /**
056     * A specialized {@code ValueTransformer} implementation which does no transformation. The {@code transformValue()}
057     * method just returns the passed in object without changes. This instance can be used by configurations which do not
058     * require additional encoding.
059     */
060    ValueTransformer NOOP_TRANSFORMER = value -> value;
061
062    /**
063     * Escapes the specified single value object. This method is called for properties containing only a single value. So
064     * this method can rely on the fact that the passed in object is not a list. An implementation has to check whether the
065     * value contains list delimiter characters and - if so - escape them accordingly.
066     *
067     * @param value the value to be escaped
068     * @param transformer a {@code ValueTransformer} for an additional encoding (must not be <b>null</b>)
069     * @return the escaped value
070     */
071    Object escape(Object value, ValueTransformer transformer);
072
073    /**
074     * Escapes all values in the given list and concatenates them to a single string. This operation is required by
075     * configurations that have to represent properties with multiple values in a single line in their external
076     * configuration representation. This may require an advanced escaping in some cases.
077     *
078     * @param values the list with all the values to be converted to a single value
079     * @param transformer a {@code ValueTransformer} for an additional encoding (must not be <b>null</b>)
080     * @return the resulting escaped value
081     */
082    Object escapeList(List<?> values, ValueTransformer transformer);
083
084    /**
085     * Parses the specified value for list delimiters and splits it if necessary. The passed in object can be either a
086     * single value or a complex one, e.g. a collection, an array, or an {@code Iterable}. It is the responsibility of this
087     * method to return an {@code Iterable} which contains all extracted values.
088     *
089     * @param value the value to be parsed
090     * @return an {@code Iterable} allowing access to all extracted values
091     */
092    Iterable<?> parse(Object value);
093
094    /**
095     * Splits the specified string at the list delimiter and returns a collection with all extracted components. A concrete
096     * implementation also has to deal with escape characters which might mask a list delimiter character at certain
097     * positions. The boolean {@code trim} flag determines whether each extracted component should be trimmed. This
098     * typically makes sense as the list delimiter may be surrounded by whitespace. However, there may be specific use cases
099     * in which automatic trimming is not desired.
100     *
101     * @param s the string to be split
102     * @param trim a flag whether each component of the string is to be trimmed
103     * @return a collection with all components extracted from the string
104     */
105    Collection<String> split(String s, boolean trim);
106
107    /**
108     * Extracts all values contained in the specified object up to the given limit. The passed in object is evaluated (if
109     * necessary in a recursive way). If it is a complex object (e.g. a collection or an array), all its elements are
110     * processed recursively and added to a target collection. The process stops if the limit is reached, but depending on
111     * the input object, it might be exceeded. (The limit is just an indicator to stop the process to avoid unnecessary work
112     * if the caller is only interested in a few values.)
113     *
114     * @param value the value to be processed
115     * @param limit the limit for aborting the processing
116     * @return a &quot;flat&quot; collection containing all primitive values of the passed in object
117     * @since 2.9.0
118     */
119    default Collection<?> flatten(final Object value, final int limit) {
120        if (value instanceof String) {
121            return split((String) value, true);
122        }
123        final Collection<Object> result = new LinkedList<>();
124        if (value instanceof Iterable) {
125            AbstractListDelimiterHandler.flattenIterator(this, result, ((Iterable<?>) value).iterator(), limit);
126        } else if (value instanceof Iterator) {
127            AbstractListDelimiterHandler.flattenIterator(this, result, (Iterator<?>) value, limit);
128        } else if (value != null) {
129            if (value.getClass().isArray()) {
130                for (int len = Array.getLength(value), idx = 0, size = 0; idx < len && size < limit; idx++, size = result.size()) {
131                    result.addAll(flatten(Array.get(value, idx), limit - size));
132                }
133            } else {
134                result.add(value);
135            }
136        }
137        return result;
138    }
139
140}