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.configuration;
19  
20  import java.util.Iterator;
21  
22  import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
23  
24  /**
25   * <p>A subset of another configuration. The new Configuration object contains
26   * every key from the parent Configuration that starts with prefix. The prefix
27   * is removed from the keys in the subset.</p>
28   * <p>It is usually not necessary to use this class directly. Instead the
29   * {@link Configuration#subset(String)} method should be used,
30   * which will return a correctly initialized instance.</p>
31   *
32   * @author Emmanuel Bourg
33   * @version $Id: SubsetConfiguration.java 1210202 2011-12-04 20:30:46Z oheger $
34   */
35  public class SubsetConfiguration extends AbstractConfiguration
36  {
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
50       * @param prefix The prefix used to select the properties
51       */
52      public SubsetConfiguration(Configuration parent, String prefix)
53      {
54          this.parent = parent;
55          this.prefix = prefix;
56      }
57  
58      /**
59       * Create a subset of the specified configuration
60       *
61       * @param parent    The parent configuration
62       * @param prefix    The prefix used to select the properties
63       * @param delimiter The prefix delimiter
64       */
65      public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
66      {
67          this.parent = parent;
68          this.prefix = prefix;
69          this.delimiter = delimiter;
70      }
71  
72      /**
73       * Return the key in the parent configuration associated to the specified
74       * key in this subset.
75       *
76       * @param key The key in the subset.
77       * @return the key as to be used by the parent
78       */
79      protected String getParentKey(String key)
80      {
81          if ("".equals(key) || key == null)
82          {
83              return prefix;
84          }
85          else
86          {
87              return delimiter == null ? prefix + key : prefix + delimiter + key;
88          }
89      }
90  
91      /**
92       * Return the key in the subset configuration associated to the specified
93       * key in the parent configuration.
94       *
95       * @param key The key in the parent configuration.
96       * @return the key in the context of this subset configuration
97       */
98      protected String getChildKey(String key)
99      {
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 }