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
18 package org.apache.commons.configuration2;
19
20 import java.util.Iterator;
21 import java.util.Objects;
22
23 import org.apache.commons.configuration2.convert.ListDelimiterHandler;
24 import org.apache.commons.lang3.StringUtils;
25
26 /**
27 * <p>
28 * A subset of another configuration. The new Configuration object contains every key from the parent Configuration that
29 * starts with prefix. The prefix is removed from the keys in the subset.
30 * </p>
31 * <p>
32 * It is usually not necessary to use this class directly. Instead the {@link Configuration#subset(String)} method
33 * should be used, which will return a correctly initialized instance.
34 * </p>
35 */
36 public class SubsetConfiguration extends AbstractConfiguration {
37
38 /**
39 * A specialized iterator to be returned by the {@code getKeys()} methods. This implementation wraps an iterator from
40 * the parent configuration. The keys returned by this iterator are correspondingly transformed.
41 */
42 private final class SubsetIterator implements Iterator<String> {
43
44 /** Stores the wrapped iterator. */
45 private final Iterator<String> parentIterator;
46
47 /**
48 * Creates a new instance of {@code SubsetIterator} and initializes it with the parent iterator.
49 *
50 * @param it the iterator of the parent configuration
51 */
52 public SubsetIterator(final Iterator<String> it) {
53 parentIterator = it;
54 }
55
56 /**
57 * Checks whether there are more elements. Delegates to the parent iterator.
58 *
59 * @return a flag whether there are more elements
60 */
61 @Override
62 public boolean hasNext() {
63 return parentIterator.hasNext();
64 }
65
66 /**
67 * Returns the next element in the iteration. This is the next key from the parent configuration, transformed to
68 * correspond to the point of view of this subset configuration.
69 *
70 * @return the next element
71 */
72 @Override
73 public String next() {
74 return getChildKey(parentIterator.next());
75 }
76
77 /**
78 * Removes the current element from the iteration. Delegates to the parent iterator.
79 */
80 @Override
81 public void remove() {
82 parentIterator.remove();
83 }
84 }
85
86 /** The parent configuration. */
87 protected Configuration parent;
88
89 /** The prefix used to select the properties. */
90 protected String prefix;
91
92 /** The prefix delimiter */
93 protected String delimiter;
94
95 /**
96 * Create a subset of the specified configuration
97 *
98 * @param parent The parent configuration (must not be <strong>null</strong>)
99 * @param prefix The prefix used to select the properties
100 * @throws IllegalArgumentException if the parent configuration is <strong>null</strong>
101 */
102 public SubsetConfiguration(final Configuration parent, final String prefix) {
103 this(parent, prefix, null);
104 }
105
106 /**
107 * Create a subset of the specified configuration
108 *
109 * @param parent The parent configuration (must not be <strong>null</strong>)
110 * @param prefix The prefix used to select the properties
111 * @param delimiter The prefix delimiter
112 * @throws NullPointerException if the parent configuration is <strong>null</strong>
113 */
114 public SubsetConfiguration(final Configuration parent, final String prefix, final String delimiter) {
115 this.parent = Objects.requireNonNull(parent, "parent");
116 this.prefix = prefix;
117 this.delimiter = delimiter;
118 initInterpolator();
119 }
120
121 @Override
122 public void addPropertyDirect(final String key, final Object value) {
123 parent.addProperty(getParentKey(key), value);
124 }
125
126 @Override
127 protected void clearPropertyDirect(final String key) {
128 parent.clearProperty(getParentKey(key));
129 }
130
131 @Override
132 protected boolean containsKeyInternal(final String key) {
133 return parent.containsKey(getParentKey(key));
134 }
135
136 /**
137 * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
138 * but may be more expensive than the containsKey method.
139 *
140 * @since 2.11.0
141 */
142 @Override
143 protected boolean containsValueInternal(final Object value) {
144 return parent.containsValue(value);
145 }
146
147 /**
148 * Gets the key in the subset configuration associated to the specified key in the parent configuration.
149 *
150 * @param key The key in the parent configuration.
151 * @return the key in the context of this subset configuration
152 */
153 protected String getChildKey(final String key) {
154 if (!key.startsWith(prefix)) {
155 throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
156 }
157 String modifiedKey = null;
158 if (key.length() == prefix.length()) {
159 modifiedKey = "";
160 } else {
161 final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
162 modifiedKey = key.substring(i);
163 }
164
165 return modifiedKey;
166 }
167
168 @Override
169 protected Iterator<String> getKeysInternal() {
170 return new SubsetIterator(parent.getKeys(prefix, delimiter));
171 }
172
173 @Override
174 protected Iterator<String> getKeysInternal(final String prefix) {
175 return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
176 }
177
178 /**
179 * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is
180 * obtained from there.
181 */
182 @Override
183 public ListDelimiterHandler getListDelimiterHandler() {
184 return parent instanceof AbstractConfiguration ? ((AbstractConfiguration) parent).getListDelimiterHandler() : super.getListDelimiterHandler();
185 }
186
187 /**
188 * Gets the parent configuration for this subset.
189 *
190 * @return the parent configuration
191 */
192 public Configuration getParent() {
193 return parent;
194 }
195
196 /**
197 * Gets the key in the parent configuration associated to the specified key in this subset.
198 *
199 * @param key The key in the subset.
200 * @return the key as to be used by the parent
201 */
202 protected String getParentKey(final String key) {
203 if (StringUtils.isEmpty(key)) {
204 return prefix;
205 }
206 return delimiter == null ? prefix + key : prefix + delimiter + key;
207 }
208
209 /**
210 * Gets the prefix used to select the properties in the parent configuration.
211 *
212 * @return the prefix used by this subset
213 */
214 public String getPrefix() {
215 return prefix;
216 }
217
218 @Override
219 protected Object getPropertyInternal(final String key) {
220 return parent.getProperty(getParentKey(key));
221 }
222
223 /**
224 * Initializes the {@code ConfigurationInterpolator} for this sub configuration. This is a standard
225 * {@code ConfigurationInterpolator} which also references the {@code ConfigurationInterpolator} of the parent
226 * configuration.
227 */
228 private void initInterpolator() {
229 getInterpolator().setParentInterpolator(getParent().getInterpolator());
230 }
231
232 @Override
233 protected boolean isEmptyInternal() {
234 return !getKeysInternal().hasNext();
235 }
236
237 /**
238 * {@inheritDoc}
239 *
240 * The subset inherits this feature from its parent if it supports this feature.
241 */
242 @Override
243 public boolean isThrowExceptionOnMissing() {
244 if (parent instanceof AbstractConfiguration) {
245 return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
246 }
247 return super.isThrowExceptionOnMissing();
248 }
249
250 /**
251 * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is passed
252 * to the parent.
253 */
254 @Override
255 public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
256 if (parent instanceof AbstractConfiguration) {
257 ((AbstractConfiguration) parent).setListDelimiterHandler(listDelimiterHandler);
258 } else {
259 super.setListDelimiterHandler(listDelimiterHandler);
260 }
261 }
262
263 /**
264 * Sets the prefix used to select the properties in the parent configuration.
265 *
266 * @param prefix the prefix
267 */
268 public void setPrefix(final String prefix) {
269 this.prefix = prefix;
270 }
271
272 /**
273 * {@inheritDoc}
274 *
275 * Change the behavior of the parent configuration if it supports this feature.
276 */
277 @Override
278 public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) {
279 if (parent instanceof AbstractConfiguration) {
280 ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
281 } else {
282 super.setThrowExceptionOnMissing(throwExceptionOnMissing);
283 }
284 }
285
286 @Override
287 public Configuration subset(final String prefix) {
288 return parent.subset(getParentKey(prefix));
289 }
290 }