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