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