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}