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