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
018package org.apache.commons.configuration;
019
020import java.util.Iterator;
021
022import 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 */
035public 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}