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