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