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