    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}