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 */
017
018 package org.apache.commons.configuration;
019
020 import java.util.Iterator;
021
022 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
023
024 /**
025 * <p>A subset of another configuration. The new Configuration object contains
026 * every key from the parent Configuration that starts with prefix. The prefix
027 * is removed from the keys in the subset.</p>
028 * <p>It is usually not necessary to use this class directly. Instead the
029 * {@link Configuration#subset(String)} method should be used,
030 * which will return a correctly initialized instance.</p>
031 *
032 * @author Emmanuel Bourg
033 * @version $Id: SubsetConfiguration.java 1210202 2011-12-04 20:30:46Z oheger $
034 */
035 public class SubsetConfiguration extends AbstractConfiguration
036 {
037 /** The parent configuration. */
038 protected Configuration parent;
039
040 /** The prefix used to select the properties. */
041 protected String prefix;
042
043 /** The prefix delimiter */
044 protected String delimiter;
045
046 /**
047 * Create a subset of the specified configuration
048 *
049 * @param parent The parent configuration
050 * @param prefix The prefix used to select the properties
051 */
052 public SubsetConfiguration(Configuration parent, String prefix)
053 {
054 this.parent = parent;
055 this.prefix = prefix;
056 }
057
058 /**
059 * Create a subset of the specified configuration
060 *
061 * @param parent The parent configuration
062 * @param prefix The prefix used to select the properties
063 * @param delimiter The prefix delimiter
064 */
065 public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
066 {
067 this.parent = parent;
068 this.prefix = prefix;
069 this.delimiter = delimiter;
070 }
071
072 /**
073 * Return the key in the parent configuration associated to the specified
074 * key in this subset.
075 *
076 * @param key The key in the subset.
077 * @return the key as to be used by the parent
078 */
079 protected String getParentKey(String key)
080 {
081 if ("".equals(key) || key == null)
082 {
083 return prefix;
084 }
085 else
086 {
087 return delimiter == null ? prefix + key : prefix + delimiter + key;
088 }
089 }
090
091 /**
092 * Return the key in the subset configuration associated to the specified
093 * key in the parent configuration.
094 *
095 * @param key The key in the parent configuration.
096 * @return the key in the context of this subset configuration
097 */
098 protected String getChildKey(String key)
099 {
100 if (!key.startsWith(prefix))
101 {
102 throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
103 }
104 else
105 {
106 String modifiedKey = null;
107 if (key.length() == prefix.length())
108 {
109 modifiedKey = "";
110 }
111 else
112 {
113 int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
114 modifiedKey = key.substring(i);
115 }
116
117 return modifiedKey;
118 }
119 }
120
121 /**
122 * Return the parent configuration for this subset.
123 *
124 * @return the parent configuration
125 */
126 public Configuration getParent()
127 {
128 return parent;
129 }
130
131 /**
132 * Return the prefix used to select the properties in the parent configuration.
133 *
134 * @return the prefix used by this subset
135 */
136 public String getPrefix()
137 {
138 return prefix;
139 }
140
141 /**
142 * Set the prefix used to select the properties in the parent configuration.
143 *
144 * @param prefix the prefix
145 */
146 public void setPrefix(String prefix)
147 {
148 this.prefix = prefix;
149 }
150
151 @Override
152 public Configuration subset(String prefix)
153 {
154 return parent.subset(getParentKey(prefix));
155 }
156
157 public boolean isEmpty()
158 {
159 return !getKeys().hasNext();
160 }
161
162 public boolean containsKey(String key)
163 {
164 return parent.containsKey(getParentKey(key));
165 }
166
167 @Override
168 public void addPropertyDirect(String key, Object value)
169 {
170 parent.addProperty(getParentKey(key), value);
171 }
172
173 @Override
174 protected void clearPropertyDirect(String key)
175 {
176 parent.clearProperty(getParentKey(key));
177 }
178
179 public Object getProperty(String key)
180 {
181 return parent.getProperty(getParentKey(key));
182 }
183
184 @Override
185 public Iterator<String> getKeys(String prefix)
186 {
187 return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
188 }
189
190 public Iterator<String> getKeys()
191 {
192 return new SubsetIterator(parent.getKeys(prefix));
193 }
194
195 @Override
196 protected Object interpolate(Object base)
197 {
198 if (delimiter == null && "".equals(prefix))
199 {
200 return super.interpolate(base);
201 }
202 else
203 {
204 SubsetConfiguration config = new SubsetConfiguration(parent, "");
205 ConfigurationInterpolator interpolator = config.getInterpolator();
206 getInterpolator().registerLocalLookups(interpolator);
207 if (parent instanceof AbstractConfiguration)
208 {
209 interpolator.setParentInterpolator(((AbstractConfiguration) parent).getInterpolator());
210 }
211 return config.interpolate(base);
212 }
213 }
214
215 @Override
216 protected String interpolate(String base)
217 {
218 return super.interpolate(base);
219 }
220
221 /**
222 * {@inheritDoc}
223 *
224 * Change the behavior of the parent configuration if it supports this feature.
225 */
226 @Override
227 public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
228 {
229 if (parent instanceof AbstractConfiguration)
230 {
231 ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
232 }
233 else
234 {
235 super.setThrowExceptionOnMissing(throwExceptionOnMissing);
236 }
237 }
238
239 /**
240 * {@inheritDoc}
241 *
242 * The subset inherits this feature from its parent if it supports this feature.
243 */
244 @Override
245 public boolean isThrowExceptionOnMissing()
246 {
247 if (parent instanceof AbstractConfiguration)
248 {
249 return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
250 }
251 else
252 {
253 return super.isThrowExceptionOnMissing();
254 }
255 }
256
257 /**
258 * Returns the list delimiter. This property will be fetched from the parent
259 * configuration if supported.
260 *
261 * @return the list delimiter
262 * @since 1.4
263 */
264 @Override
265 public char getListDelimiter()
266 {
267 return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
268 .getListDelimiter()
269 : super.getListDelimiter();
270 }
271
272 /**
273 * Sets the list delimiter. If the parent configuration supports this
274 * feature, the delimiter will be set at the parent.
275 *
276 * @param delim the new list delimiter
277 * @since 1.4
278 */
279 @Override
280 public void setListDelimiter(char delim)
281 {
282 if (parent instanceof AbstractConfiguration)
283 {
284 ((AbstractConfiguration) parent).setListDelimiter(delim);
285 }
286 else
287 {
288 super.setListDelimiter(delim);
289 }
290 }
291
292 /**
293 * Returns a flag whether string properties should be checked for list
294 * delimiter characters. This implementation ensures that this flag is kept
295 * in sync with the parent configuration if this object supports this
296 * feature.
297 *
298 * @return the delimiter parsing disabled flag
299 * @since 1.4
300 */
301 @Override
302 public boolean isDelimiterParsingDisabled()
303 {
304 return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
305 .isDelimiterParsingDisabled()
306 : super.isDelimiterParsingDisabled();
307 }
308
309 /**
310 * Sets a flag whether list parsing is disabled. This implementation will
311 * also set the flag at the parent configuration if this object supports
312 * this feature.
313 *
314 * @param delimiterParsingDisabled the delimiter parsing disabled flag
315 * @since 1.4
316 */
317 @Override
318 public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
319 {
320 if (parent instanceof AbstractConfiguration)
321 {
322 ((AbstractConfiguration) parent)
323 .setDelimiterParsingDisabled(delimiterParsingDisabled);
324 }
325 else
326 {
327 super.setDelimiterParsingDisabled(delimiterParsingDisabled);
328 }
329 }
330
331
332 /**
333 * A specialized iterator to be returned by the {@code getKeys()}
334 * methods. This implementation wraps an iterator from the parent
335 * configuration. The keys returned by this iterator are correspondingly
336 * transformed.
337 */
338 private class SubsetIterator implements Iterator<String>
339 {
340 /** Stores the wrapped iterator. */
341 private final Iterator<String> parentIterator;
342
343 /**
344 * Creates a new instance of {@code SubsetIterator} and
345 * initializes it with the parent iterator.
346 *
347 * @param it the iterator of the parent configuration
348 */
349 public SubsetIterator(Iterator<String> it)
350 {
351 parentIterator = it;
352 }
353
354 /**
355 * Checks whether there are more elements. Delegates to the parent
356 * iterator.
357 *
358 * @return a flag whether there are more elements
359 */
360 public boolean hasNext()
361 {
362 return parentIterator.hasNext();
363 }
364
365 /**
366 * Returns the next element in the iteration. This is the next key from
367 * the parent configuration, transformed to correspond to the point of
368 * view of this subset configuration.
369 *
370 * @return the next element
371 */
372 public String next()
373 {
374 return getChildKey(parentIterator.next());
375 }
376
377 /**
378 * Removes the current element from the iteration. Delegates to the
379 * parent iterator.
380 */
381 public void remove()
382 {
383 parentIterator.remove();
384 }
385 }
386 }