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