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