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}