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 */
017package org.apache.commons.configuration2;
018
019import java.io.IOException;
020import java.io.Reader;
021import java.io.Writer;
022import java.math.BigDecimal;
023import java.math.BigInteger;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Objects;
029import java.util.Properties;
030
031import org.apache.commons.configuration2.event.Event;
032import org.apache.commons.configuration2.event.EventListener;
033import org.apache.commons.configuration2.event.EventType;
034import org.apache.commons.configuration2.ex.ConfigurationException;
035import org.apache.commons.configuration2.io.FileBased;
036import org.apache.commons.configuration2.tree.ExpressionEngine;
037import org.apache.commons.configuration2.tree.ImmutableNode;
038
039/**
040 * Wraps a BaseHierarchicalConfiguration and allows subtrees to be accessed via a configured path with replaceable
041 * tokens derived from the ConfigurationInterpolator. When used with injection frameworks such as Spring it allows
042 * components to be injected with subtrees of the configuration.
043 *
044 * @since 1.6
045 */
046public class PatternSubtreeConfigurationWrapper extends BaseHierarchicalConfiguration implements FileBasedConfiguration {
047    /** The wrapped configuration */
048    private final HierarchicalConfiguration<ImmutableNode> config;
049
050    /** The path to the subtree */
051    private final String path;
052
053    /** True if the path ends with '/', false otherwise */
054    private final boolean trailing;
055
056    /** True if the constructor has finished */
057    private final boolean init;
058
059    /**
060     * Constructor
061     *
062     * @param config The Configuration to be wrapped.
063     * @param path The base path pattern.
064     */
065    public PatternSubtreeConfigurationWrapper(final HierarchicalConfiguration<ImmutableNode> config, final String path) {
066        this.config = Objects.requireNonNull(config, "config");
067        this.path = path;
068        this.trailing = path.endsWith("/");
069        this.init = true;
070    }
071
072    @Override
073    protected void addPropertyInternal(final String key, final Object value) {
074        config.addProperty(makePath(key), value);
075    }
076
077    @Override
078    protected void clearInternal() {
079        getConfig().clear();
080    }
081
082    @Override
083    protected void clearPropertyDirect(final String key) {
084        config.clearProperty(makePath(key));
085    }
086
087    @Override
088    protected boolean containsKeyInternal(final String key) {
089        return config.containsKey(makePath(key));
090    }
091
092    @Override
093    public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
094        return config.getBigDecimal(makePath(key), defaultValue);
095    }
096
097    @Override
098    public BigDecimal getBigDecimal(final String key) {
099        return config.getBigDecimal(makePath(key));
100    }
101
102    @Override
103    public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
104        return config.getBigInteger(makePath(key), defaultValue);
105    }
106
107    @Override
108    public BigInteger getBigInteger(final String key) {
109        return config.getBigInteger(makePath(key));
110    }
111
112    @Override
113    public boolean getBoolean(final String key, final boolean defaultValue) {
114        return config.getBoolean(makePath(key), defaultValue);
115    }
116
117    @Override
118    public Boolean getBoolean(final String key, final Boolean defaultValue) {
119        return config.getBoolean(makePath(key), defaultValue);
120    }
121
122    @Override
123    public boolean getBoolean(final String key) {
124        return config.getBoolean(makePath(key));
125    }
126
127    @Override
128    public byte getByte(final String key, final byte defaultValue) {
129        return config.getByte(makePath(key), defaultValue);
130    }
131
132    @Override
133    public Byte getByte(final String key, final Byte defaultValue) {
134        return config.getByte(makePath(key), defaultValue);
135    }
136
137    @Override
138    public byte getByte(final String key) {
139        return config.getByte(makePath(key));
140    }
141
142    @Override
143    public double getDouble(final String key, final double defaultValue) {
144        return config.getDouble(makePath(key), defaultValue);
145    }
146
147    @Override
148    public Double getDouble(final String key, final Double defaultValue) {
149        return config.getDouble(makePath(key), defaultValue);
150    }
151
152    @Override
153    public double getDouble(final String key) {
154        return config.getDouble(makePath(key));
155    }
156
157    @Override
158    public float getFloat(final String key, final float defaultValue) {
159        return config.getFloat(makePath(key), defaultValue);
160    }
161
162    @Override
163    public Float getFloat(final String key, final Float defaultValue) {
164        return config.getFloat(makePath(key), defaultValue);
165    }
166
167    @Override
168    public float getFloat(final String key) {
169        return config.getFloat(makePath(key));
170    }
171
172    @Override
173    public int getInt(final String key, final int defaultValue) {
174        return config.getInt(makePath(key), defaultValue);
175    }
176
177    @Override
178    public int getInt(final String key) {
179        return config.getInt(makePath(key));
180    }
181
182    @Override
183    public Integer getInteger(final String key, final Integer defaultValue) {
184        return config.getInteger(makePath(key), defaultValue);
185    }
186
187    @Override
188    protected Iterator<String> getKeysInternal() {
189        return config.getKeys(makePath());
190    }
191
192    @Override
193    protected Iterator<String> getKeysInternal(final String prefix) {
194        return config.getKeys(makePath(prefix));
195    }
196
197    @Override
198    public List<Object> getList(final String key, final List<?> defaultValue) {
199        return config.getList(makePath(key), defaultValue);
200    }
201
202    @Override
203    public List<Object> getList(final String key) {
204        return config.getList(makePath(key));
205    }
206
207    @Override
208    public long getLong(final String key, final long defaultValue) {
209        return config.getLong(makePath(key), defaultValue);
210    }
211
212    @Override
213    public Long getLong(final String key, final Long defaultValue) {
214        return config.getLong(makePath(key), defaultValue);
215    }
216
217    @Override
218    public long getLong(final String key) {
219        return config.getLong(makePath(key));
220    }
221
222    @Override
223    public Properties getProperties(final String key) {
224        return config.getProperties(makePath(key));
225    }
226
227    @Override
228    protected Object getPropertyInternal(final String key) {
229        return config.getProperty(makePath(key));
230    }
231
232    @Override
233    public short getShort(final String key, final short defaultValue) {
234        return config.getShort(makePath(key), defaultValue);
235    }
236
237    @Override
238    public Short getShort(final String key, final Short defaultValue) {
239        return config.getShort(makePath(key), defaultValue);
240    }
241
242    @Override
243    public short getShort(final String key) {
244        return config.getShort(makePath(key));
245    }
246
247    @Override
248    public String getString(final String key, final String defaultValue) {
249        return config.getString(makePath(key), defaultValue);
250    }
251
252    @Override
253    public String getString(final String key) {
254        return config.getString(makePath(key));
255    }
256
257    @Override
258    public String[] getStringArray(final String key) {
259        return config.getStringArray(makePath(key));
260    }
261
262    @Override
263    protected boolean isEmptyInternal() {
264        return getConfig().isEmpty();
265    }
266
267    @Override
268    protected void setPropertyInternal(final String key, final Object value) {
269        getConfig().setProperty(key, value);
270    }
271
272    @Override
273    public Configuration subset(final String prefix) {
274        return getConfig().subset(prefix);
275    }
276
277    @Override
278    public ExpressionEngine getExpressionEngine() {
279        return config.getExpressionEngine();
280    }
281
282    @Override
283    public void setExpressionEngine(final ExpressionEngine expressionEngine) {
284        if (init) {
285            config.setExpressionEngine(expressionEngine);
286        } else {
287            super.setExpressionEngine(expressionEngine);
288        }
289    }
290
291    @Override
292    protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
293        getConfig().addNodes(key, nodes);
294    }
295
296    @Override
297    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
298        return config.configurationAt(makePath(key), supportUpdates);
299    }
300
301    @Override
302    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
303        return config.configurationAt(makePath(key));
304    }
305
306    @Override
307    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
308        return config.configurationsAt(makePath(key));
309    }
310
311    @Override
312    protected Object clearTreeInternal(final String key) {
313        config.clearTree(makePath(key));
314        return Collections.emptyList();
315    }
316
317    @Override
318    protected int getMaxIndexInternal(final String key) {
319        return config.getMaxIndex(makePath(key));
320    }
321
322    @Override
323    public Configuration interpolatedConfiguration() {
324        return getConfig().interpolatedConfiguration();
325    }
326
327    @Override
328    public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
329        getConfig().addEventListener(eventType, listener);
330    }
331
332    @Override
333    public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
334        return getConfig().removeEventListener(eventType, listener);
335    }
336
337    @Override
338    public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
339        return getConfig().getEventListeners(eventType);
340    }
341
342    @Override
343    public void clearEventListeners() {
344        getConfig().clearEventListeners();
345    }
346
347    @Override
348    public void clearErrorListeners() {
349        getConfig().clearErrorListeners();
350    }
351
352    @Override
353    public void write(final Writer writer) throws ConfigurationException, IOException {
354        fetchFileBased().write(writer);
355    }
356
357    @Override
358    public void read(final Reader reader) throws ConfigurationException, IOException {
359        fetchFileBased().read(reader);
360    }
361
362    private BaseHierarchicalConfiguration getConfig() {
363        return (BaseHierarchicalConfiguration) config.configurationAt(makePath());
364    }
365
366    private String makePath() {
367        final String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
368        return substitute(pathPattern);
369    }
370
371    /*
372     * Resolve the root expression and then add the item being retrieved. Insert a separator character as required.
373     */
374    private String makePath(final String item) {
375        final String pathPattern;
376        if ((item.isEmpty() || item.startsWith("/")) && trailing) {
377            pathPattern = path.substring(0, path.length() - 1);
378        } else if (!item.startsWith("/") || !trailing) {
379            pathPattern = path + "/";
380        } else {
381            pathPattern = path;
382        }
383        return substitute(pathPattern) + item;
384    }
385
386    /**
387     * Uses this configuration's {@code ConfigurationInterpolator} to perform variable substitution on the given pattern
388     * string.
389     *
390     * @param pattern the pattern string
391     * @return the string with variables replaced
392     */
393    private String substitute(final String pattern) {
394        return Objects.toString(getInterpolator().interpolate(pattern), null);
395    }
396
397    /**
398     * Returns the wrapped configuration as a {@code FileBased} object. If this cast is not possible, an exception is
399     * thrown.
400     *
401     * @return the wrapped configuration as {@code FileBased}
402     * @throws ConfigurationException if the wrapped configuration does not implement {@code FileBased}
403     */
404    private FileBased fetchFileBased() throws ConfigurationException {
405        if (!(config instanceof FileBased)) {
406            throw new ConfigurationException("Wrapped configuration does not implement FileBased!" + " No I/O operations are supported.");
407        }
408        return (FileBased) config;
409    }
410}