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 <b>null</b>)
097     * @param prefix The prefix used to select the properties
098     * @throws IllegalArgumentException if the parent configuration is <b>null</b>
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 <b>null</b>)
108     * @param prefix The prefix used to select the properties
109     * @param delimiter The prefix delimiter
110     * @throws NullPointerException if the parent configuration is <b>null</b>
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}