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    *     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 }