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    }