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 * https://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
19 import java.lang.reflect.Array;
20 import java.nio.file.Path;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.Set;
26
27 /**
28 * <p>
29 * An abstract base class for concrete {@code ListDelimiterHandler} implementations.
30 * </p>
31 * <p>
32 * This base class provides a fully functional implementation for parsing a value object which can deal with different
33 * cases like collections, arrays, iterators, etc. This logic is typically needed by every concrete subclass. Other
34 * methods are partly implemented handling special corner cases like <strong>null</strong> values; concrete subclasses do not have
35 * do implement the corresponding checks.
36 * </p>
37 *
38 * @since 2.0
39 */
40 public abstract class AbstractListDelimiterHandler implements ListDelimiterHandler {
41
42 static Collection<?> flatten(final ListDelimiterHandler handler, final Object value, final int limit, final Set<Object> dejaVu) {
43 if (value instanceof String) {
44 return handler.split((String) value, true);
45 }
46 dejaVu.add(value);
47 final Collection<Object> result = new LinkedList<>();
48 if (value instanceof Path) {
49 // Don't handle as an Iterable.
50 result.add(value);
51 } else if (value instanceof Iterable) {
52 flattenIterator(handler, result, ((Iterable<?>) value).iterator(), limit, dejaVu);
53 } else if (value instanceof Iterator) {
54 flattenIterator(handler, result, (Iterator<?>) value, limit, dejaVu);
55 } else if (value != null) {
56 if (value.getClass().isArray()) {
57 for (int len = Array.getLength(value), idx = 0, size = 0; idx < len && size < limit; idx++, size = result.size()) {
58 result.addAll(handler.flatten(Array.get(value, idx), limit - size));
59 }
60 } else {
61 result.add(value);
62 }
63 }
64 return result;
65 }
66
67 /**
68 * Flattens the given iterator. For each element in the iteration {@code flatten()} is called recursively.
69 *
70 * @param handler the working handler
71 * @param target the target collection
72 * @param iterator the iterator to process
73 * @param limit a limit for the number of elements to extract
74 * @param dejaVue Previously visited objects.
75 */
76 static void flattenIterator(final ListDelimiterHandler handler, final Collection<Object> target, final Iterator<?> iterator, final int limit,
77 final Set<Object> dejaVue) {
78 int size = target.size();
79 while (size < limit && iterator.hasNext()) {
80 final Object next = iterator.next();
81 if (!dejaVue.contains(next)) {
82 target.addAll(flatten(handler, next, limit - size, dejaVue));
83 size = target.size();
84 }
85 }
86 }
87
88 /**
89 * Constructs a new instance.
90 */
91 public AbstractListDelimiterHandler() {
92 // empty
93 }
94
95 /**
96 * {@inheritDoc} This implementation checks whether the object to be escaped is a string. If yes, it delegates to
97 * {@link #escapeString(String)}, otherwise no escaping is performed. Eventually, the passed in transformer is invoked
98 * so that additional encoding can be performed.
99 */
100 @Override
101 public Object escape(final Object value, final ValueTransformer transformer) {
102 return transformer.transformValue(value instanceof String ? escapeString((String) value) : value);
103 }
104
105 /**
106 * Escapes the specified string. This method is called by {@code escape()} if the passed in object is a string. Concrete
107 * subclasses have to implement their specific escaping logic here, so that the list delimiters they support are
108 * properly escaped.
109 *
110 * @param s the string to be escaped (not <strong>null</strong>)
111 * @return the escaped string
112 */
113 protected abstract String escapeString(String s);
114
115 /**
116 * Performs the actual work as advertised by the {@code parse()} method. This method delegates to
117 * {@link #flatten(Object, int)} without specifying a limit.
118 *
119 * @param value the value to be processed
120 * @return a "flat" collection containing all primitive values of the passed in object
121 */
122 private Collection<?> flatten(final Object value) {
123 return flatten(value, Integer.MAX_VALUE);
124 }
125
126 /**
127 * {@inheritDoc} Depending on the type of the passed in object the following things happen:
128 * <ul>
129 * <li>Strings are checked for delimiter characters and split if necessary. This is done by calling the {@code split()}
130 * method.</li>
131 * <li>For objects implementing the {@code Iterable} interface, the corresponding {@code Iterator} is obtained, and
132 * contained elements are added to the resulting iteration.</li>
133 * <li>Arrays are treated as {@code Iterable} objects.</li>
134 * <li>All other types are directly inserted.</li>
135 * <li>Recursive combinations are supported, for example a collection containing an array that contains strings: The resulting
136 * collection will only contain primitive objects.</li>
137 * </ul>
138 */
139 @Override
140 public Iterable<?> parse(final Object value) {
141 return flatten(value);
142 }
143
144 /**
145 * {@inheritDoc} This implementation handles the case that the passed in string is <strong>null</strong>. In this case, an empty
146 * collection is returned. Otherwise, this method delegates to {@link #splitString(String, boolean)}.
147 */
148 @Override
149 public Collection<String> split(final String s, final boolean trim) {
150 return s == null ? new ArrayList<>(0) : splitString(s, trim);
151 }
152
153 /**
154 * Actually splits the passed in string which is guaranteed to be not <strong>null</strong>. This method is called by the base
155 * implementation of the {@code split()} method. Here the actual splitting logic has to be implemented.
156 *
157 * @param s the string to be split (not <strong>null</strong>)
158 * @param trim a flag whether the single components have to be trimmed
159 * @return a collection with the extracted components of the passed in string
160 */
161 protected abstract Collection<String> splitString(String s, boolean trim);
162 }