View Javadoc
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    *     http://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 }