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