    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;
    023import org.apache.commons.lang3.StringUtils;
    024
    025/**
    026 * <p>
    027 * A subset of another configuration. The new Configuration object contains every key from the parent Configuration that
    028 * starts with prefix. The prefix is removed from the keys in the subset.
    029 * </p>
    030 * <p>
    031 * It is usually not necessary to use this class directly. Instead the {@link Configuration#subset(String)} method
    032 * should be used, which will return a correctly initialized instance.
    033 * </p>
    034 */
    035public class SubsetConfiguration extends AbstractConfiguration {
    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        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        if (parent == null) {
    066            throw new IllegalArgumentException("Parent configuration must not be null!");
    067        }
    068
    069        this.parent = parent;
    070        this.prefix = prefix;
    071        this.delimiter = delimiter;
    072        initInterpolator();
    073    }
    074
    075    /**
    076     * Gets the key in the parent configuration associated to the specified key in this subset.
    077     *
    078     * @param key The key in the subset.
    079     * @return the key as to be used by the parent
    080     */
    081    protected String getParentKey(final String key) {
    082        if (StringUtils.isEmpty(key)) {
    083            return prefix;
    084        }
    085        return delimiter == null ? prefix + key : prefix + delimiter + key;
    086    }
    087
    088    /**
    089     * Gets the key in the subset configuration associated to the specified key in the parent configuration.
    090     *
    091     * @param key The key in the parent configuration.
    092     * @return the key in the context of this subset configuration
    093     */
    094    protected String getChildKey(final String key) {
    095        if (!key.startsWith(prefix)) {
    096            throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
    097        }
    098        String modifiedKey = null;
    099        if (key.length() == prefix.length()) {
    100            modifiedKey = "";
    101        } else {
    102            final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
    103            modifiedKey = key.substring(i);
    104        }
    105
    106        return modifiedKey;
    107    }
    108
    109    /**
    110     * Gets the parent configuration for this subset.
    111     *
    112     * @return the parent configuration
    113     */
    114    public Configuration getParent() {
    115        return parent;
    116    }
    117
    118    /**
    119     * Gets the prefix used to select the properties in the parent configuration.
    120     *
    121     * @return the prefix used by this subset
    122     */
    123    public String getPrefix() {
    124        return prefix;
    125    }
    126
    127    /**
    128     * Sets the prefix used to select the properties in the parent configuration.
    129     *
    130     * @param prefix the prefix
    131     */
    132    public void setPrefix(final String prefix) {
    133        this.prefix = prefix;
    134    }
    135
    136    @Override
    137    public Configuration subset(final String prefix) {
    138        return parent.subset(getParentKey(prefix));
    139    }
    140
    141    @Override
    142    protected boolean isEmptyInternal() {
    143        return !getKeysInternal().hasNext();
    144    }
    145
    146    @Override
    147    protected boolean containsKeyInternal(final String key) {
    148        return parent.containsKey(getParentKey(key));
    149    }
    150
    151    @Override
    152    public void addPropertyDirect(final String key, final Object value) {
    153        parent.addProperty(getParentKey(key), value);
    154    }
    155
    156    @Override
    157    protected void clearPropertyDirect(final String key) {
    158        parent.clearProperty(getParentKey(key));
    159    }
    160
    161    @Override
    162    protected Object getPropertyInternal(final String key) {
    163        return parent.getProperty(getParentKey(key));
    164    }
    165
    166    @Override
    167    protected Iterator<String> getKeysInternal(final String prefix) {
    168        return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
    169    }
    170
    171    @Override
    172    protected Iterator<String> getKeysInternal() {
    173        return new SubsetIterator(parent.getKeys(prefix, delimiter));
    174    }
    175
    176    /**
    177     * {@inheritDoc}
    178     *
    179     * Change the behavior of the parent configuration if it supports this feature.
    180     */
    181    @Override
    182    public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) {
    183        if (parent instanceof AbstractConfiguration) {
    184            ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
    185        } else {
    186            super.setThrowExceptionOnMissing(throwExceptionOnMissing);
    187        }
    188    }
    189
    190    /**
    191     * {@inheritDoc}
    192     *
    193     * The subset inherits this feature from its parent if it supports this feature.
    194     */
    195    @Override
    196    public boolean isThrowExceptionOnMissing() {
    197        if (parent instanceof AbstractConfiguration) {
    198            return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
    199        }
    200        return super.isThrowExceptionOnMissing();
    201    }
    202
    203    /**
    204     * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is
    205     * obtained from there.
    206     */
    207    @Override
    208    public ListDelimiterHandler getListDelimiterHandler() {
    209        return parent instanceof AbstractConfiguration ? ((AbstractConfiguration) parent).getListDelimiterHandler() : super.getListDelimiterHandler();
    210    }
    211
    212    /**
    213     * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is passed
    214     * to the parent.
    215     */
    216    @Override
    217    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
    218        if (parent instanceof AbstractConfiguration) {
    219            ((AbstractConfiguration) parent).setListDelimiterHandler(listDelimiterHandler);
    220        } else {
    221            super.setListDelimiterHandler(listDelimiterHandler);
    222        }
    223    }
    224
    225    /**
    226     * Initializes the {@code ConfigurationInterpolator} for this sub configuration. This is a standard
    227     * {@code ConfigurationInterpolator} which also references the {@code ConfigurationInterpolator} of the parent
    228     * configuration.
    229     */
    230    private void initInterpolator() {
    231        getInterpolator().setParentInterpolator(getParent().getInterpolator());
    232    }
    233
    234    /**
    235     * A specialized iterator to be returned by the {@code getKeys()} methods. This implementation wraps an iterator from
    236     * the parent configuration. The keys returned by this iterator are correspondingly transformed.
    237     */
    238    private final class SubsetIterator implements Iterator<String> {
    239        /** Stores the wrapped iterator. */
    240        private final Iterator<String> parentIterator;
    241
    242        /**
    243         * Creates a new instance of {@code SubsetIterator} and initializes it with the parent iterator.
    244         *
    245         * @param it the iterator of the parent configuration
    246         */
    247        public SubsetIterator(final Iterator<String> it) {
    248            parentIterator = it;
    249        }
    250
    251        /**
    252         * Checks whether there are more elements. Delegates to the parent iterator.
    253         *
    254         * @return a flag whether there are more elements
    255         */
    256        @Override
    257        public boolean hasNext() {
    258            return parentIterator.hasNext();
    259        }
    260
    261        /**
    262         * Returns the next element in the iteration. This is the next key from the parent configuration, transformed to
    263         * correspond to the point of view of this subset configuration.
    264         *
    265         * @return the next element
    266         */
    267        @Override
    268        public String next() {
    269            return getChildKey(parentIterator.next());
    270        }
    271
    272        /**
    273         * Removes the current element from the iteration. Delegates to the parent iterator.
    274         */
    275        @Override
    276        public void remove() {
    277            parentIterator.remove();
    278        }
    279    }
    280}