    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}