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