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.configuration2;
019
020import java.util.Iterator;
021
022import org.apache.commons.configuration2.convert.ListDelimiterHandler;
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 1624601 2014-09-12 18:04:36Z 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 (must not be <b>null</b>)
050     * @param prefix The prefix used to select the properties
051     * @throws IllegalArgumentException if the parent configuration is <b>null</b>
052     */
053    public SubsetConfiguration(Configuration parent, String prefix)
054    {
055        this(parent, prefix, null);
056    }
057
058    /**
059     * Create a subset of the specified configuration
060     *
061     * @param parent The parent configuration (must not be <b>null</b>)
062     * @param prefix    The prefix used to select the properties
063     * @param delimiter The prefix delimiter
064     * @throws IllegalArgumentException if the parent configuration is <b>null</b>
065     */
066    public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
067    {
068        if (parent == null)
069        {
070            throw new IllegalArgumentException(
071                    "Parent configuration must not be null!");
072        }
073
074        this.parent = parent;
075        this.prefix = prefix;
076        this.delimiter = delimiter;
077        initInterpolator();
078    }
079
080    /**
081     * Return the key in the parent configuration associated to the specified
082     * key in this subset.
083     *
084     * @param key The key in the subset.
085     * @return the key as to be used by the parent
086     */
087    protected String getParentKey(String key)
088    {
089        if ("".equals(key) || key == null)
090        {
091            return prefix;
092        }
093        else
094        {
095            return delimiter == null ? prefix + key : prefix + delimiter + key;
096        }
097    }
098
099    /**
100     * Return the key in the subset configuration associated to the specified
101     * key in the parent configuration.
102     *
103     * @param key The key in the parent configuration.
104     * @return the key in the context of this subset configuration
105     */
106    protected String getChildKey(String key)
107    {
108        if (!key.startsWith(prefix))
109        {
110            throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
111        }
112        else
113        {
114            String modifiedKey = null;
115            if (key.length() == prefix.length())
116            {
117                modifiedKey = "";
118            }
119            else
120            {
121                int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
122                modifiedKey = key.substring(i);
123            }
124
125            return modifiedKey;
126        }
127    }
128
129    /**
130     * Return the parent configuration for this subset.
131     *
132     * @return the parent configuration
133     */
134    public Configuration getParent()
135    {
136        return parent;
137    }
138
139    /**
140     * Return the prefix used to select the properties in the parent configuration.
141     *
142     * @return the prefix used by this subset
143     */
144    public String getPrefix()
145    {
146        return prefix;
147    }
148
149    /**
150     * Set the prefix used to select the properties in the parent configuration.
151     *
152     * @param prefix the prefix
153     */
154    public void setPrefix(String prefix)
155    {
156        this.prefix = prefix;
157    }
158
159    @Override
160    public Configuration subset(String prefix)
161    {
162        return parent.subset(getParentKey(prefix));
163    }
164
165    @Override
166    protected boolean isEmptyInternal()
167    {
168        return !getKeysInternal().hasNext();
169    }
170
171    @Override
172    protected boolean containsKeyInternal(String key)
173    {
174        return parent.containsKey(getParentKey(key));
175    }
176
177    @Override
178    public void addPropertyDirect(String key, Object value)
179    {
180        parent.addProperty(getParentKey(key), value);
181    }
182
183    @Override
184    protected void clearPropertyDirect(String key)
185    {
186        parent.clearProperty(getParentKey(key));
187    }
188
189    @Override
190    protected Object getPropertyInternal(String key)
191    {
192        return parent.getProperty(getParentKey(key));
193    }
194
195    @Override
196    protected Iterator<String> getKeysInternal(String prefix)
197    {
198        return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
199    }
200
201    @Override
202    protected Iterator<String> getKeysInternal()
203    {
204        return new SubsetIterator(parent.getKeys(prefix));
205    }
206
207    /**
208     * {@inheritDoc}
209     *
210     * Change the behavior of the parent configuration if it supports this feature.
211     */
212    @Override
213    public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
214    {
215        if (parent instanceof AbstractConfiguration)
216        {
217            ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
218        }
219        else
220        {
221            super.setThrowExceptionOnMissing(throwExceptionOnMissing);
222        }
223    }
224
225    /**
226     * {@inheritDoc}
227     *
228     * The subset inherits this feature from its parent if it supports this feature.
229     */
230    @Override
231    public boolean isThrowExceptionOnMissing()
232    {
233        if (parent instanceof AbstractConfiguration)
234        {
235            return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
236        }
237        else
238        {
239            return super.isThrowExceptionOnMissing();
240        }
241    }
242
243    /**
244     * {@inheritDoc} If the parent configuration extends
245     * {@link AbstractConfiguration}, the list delimiter handler is obtained
246     * from there.
247     */
248    @Override
249    public ListDelimiterHandler getListDelimiterHandler()
250    {
251        return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
252                .getListDelimiterHandler() : super.getListDelimiterHandler();
253    }
254
255    /**
256     * {@inheritDoc} If the parent configuration extends
257     * {@link AbstractConfiguration}, the list delimiter handler is passed to
258     * the parent.
259     */
260    @Override
261    public void setListDelimiterHandler(
262            ListDelimiterHandler listDelimiterHandler)
263    {
264        if (parent instanceof AbstractConfiguration)
265        {
266            ((AbstractConfiguration) parent)
267                    .setListDelimiterHandler(listDelimiterHandler);
268        }
269        else
270        {
271            super.setListDelimiterHandler(listDelimiterHandler);
272        }
273    }
274
275    /**
276     * Initializes the {@code ConfigurationInterpolator} for this sub configuration.
277     * This is a standard {@code ConfigurationInterpolator} which also references
278     * the {@code ConfigurationInterpolator} of the parent configuration.
279     */
280    private void initInterpolator()
281    {
282        getInterpolator().setParentInterpolator(getParent().getInterpolator());
283    }
284
285    /**
286     * A specialized iterator to be returned by the {@code getKeys()}
287     * methods. This implementation wraps an iterator from the parent
288     * configuration. The keys returned by this iterator are correspondingly
289     * transformed.
290     */
291    private class SubsetIterator implements Iterator<String>
292    {
293        /** Stores the wrapped iterator. */
294        private final Iterator<String> parentIterator;
295
296        /**
297         * Creates a new instance of {@code SubsetIterator} and
298         * initializes it with the parent iterator.
299         *
300         * @param it the iterator of the parent configuration
301         */
302        public SubsetIterator(Iterator<String> it)
303        {
304            parentIterator = it;
305        }
306
307        /**
308         * Checks whether there are more elements. Delegates to the parent
309         * iterator.
310         *
311         * @return a flag whether there are more elements
312         */
313        @Override
314        public boolean hasNext()
315        {
316            return parentIterator.hasNext();
317        }
318
319        /**
320         * Returns the next element in the iteration. This is the next key from
321         * the parent configuration, transformed to correspond to the point of
322         * view of this subset configuration.
323         *
324         * @return the next element
325         */
326        @Override
327        public String next()
328        {
329            return getChildKey(parentIterator.next());
330        }
331
332        /**
333         * Removes the current element from the iteration. Delegates to the
334         * parent iterator.
335         */
336        @Override
337        public void remove()
338        {
339            parentIterator.remove();
340        }
341    }
342}