    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}