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