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.configuration;
018
019import java.io.Reader;
020import java.io.Writer;
021import java.math.BigDecimal;
022import java.math.BigInteger;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Properties;
027
028import org.apache.commons.configuration.event.ConfigurationErrorListener;
029import org.apache.commons.configuration.event.ConfigurationListener;
030import org.apache.commons.configuration.tree.ConfigurationNode;
031import org.apache.commons.configuration.tree.ExpressionEngine;
032
033/**
034 * Wraps a HierarchicalConfiguration and allows subtrees to be access via a configured path with
035 * replaceable tokens derived from the ConfigurationInterpolator. When used with injection frameworks
036 * such as Spring it allows components to be injected with subtrees of the configuration.
037 * @since 1.6
038 * @author <a
039 * href="http://commons.apache.org/configuration/team-list.html">Commons
040 * Configuration team</a>
041 * @version $Id: PatternSubtreeConfigurationWrapper.java 1534064 2013-10-21 08:44:33Z henning $
042 */
043public class PatternSubtreeConfigurationWrapper extends AbstractHierarchicalFileConfiguration
044{
045    /**
046     * Prevent recursion while resolving unprefixed properties.
047     */
048    private static ThreadLocal<Boolean> recursive = new ThreadLocal<Boolean>()
049    {
050        @Override
051        protected synchronized Boolean initialValue()
052        {
053            return Boolean.FALSE;
054        }
055    };
056
057    /** The wrapped configuration */
058    private final AbstractHierarchicalFileConfiguration config;
059
060    /** The path to the subtree */
061    private final String path;
062
063    /** True if the path ends with '/', false otherwise */
064    private final boolean trailing;
065
066    /** True if the constructor has finished */
067    private boolean init;
068
069    /**
070     * Constructor
071     * @param config The Configuration to be wrapped.
072     * @param path The base path pattern.
073     */
074    public PatternSubtreeConfigurationWrapper(AbstractHierarchicalFileConfiguration config, String path)
075    {
076        this.config = config;
077        this.path = path;
078        this.trailing = path.endsWith("/");
079        this.init = true;
080    }
081
082    @Override
083    public Object getReloadLock()
084    {
085        return config.getReloadLock();
086    }
087
088    @Override
089    public void addProperty(String key, Object value)
090    {
091        config.addProperty(makePath(key), value);
092    }
093
094    @Override
095    public void clear()
096    {
097        getConfig().clear();
098    }
099
100    @Override
101    public void clearProperty(String key)
102    {
103        config.clearProperty(makePath(key));
104    }
105
106    @Override
107    public boolean containsKey(String key)
108    {
109        return config.containsKey(makePath(key));
110    }
111
112    @Override
113    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
114    {
115        return config.getBigDecimal(makePath(key), defaultValue);
116    }
117
118    @Override
119    public BigDecimal getBigDecimal(String key)
120    {
121        return config.getBigDecimal(makePath(key));
122    }
123
124    @Override
125    public BigInteger getBigInteger(String key, BigInteger defaultValue)
126    {
127        return config.getBigInteger(makePath(key), defaultValue);
128    }
129
130    @Override
131    public BigInteger getBigInteger(String key)
132    {
133        return config.getBigInteger(makePath(key));
134    }
135
136    @Override
137    public boolean getBoolean(String key, boolean defaultValue)
138    {
139        return config.getBoolean(makePath(key), defaultValue);
140    }
141
142    @Override
143    public Boolean getBoolean(String key, Boolean defaultValue)
144    {
145        return config.getBoolean(makePath(key), defaultValue);
146    }
147
148    @Override
149    public boolean getBoolean(String key)
150    {
151        return config.getBoolean(makePath(key));
152    }
153
154    @Override
155    public byte getByte(String key, byte defaultValue)
156    {
157        return config.getByte(makePath(key), defaultValue);
158    }
159
160    @Override
161    public Byte getByte(String key, Byte defaultValue)
162    {
163        return config.getByte(makePath(key), defaultValue);
164    }
165
166    @Override
167    public byte getByte(String key)
168    {
169        return config.getByte(makePath(key));
170    }
171
172    @Override
173    public double getDouble(String key, double defaultValue)
174    {
175        return config.getDouble(makePath(key), defaultValue);
176    }
177
178    @Override
179    public Double getDouble(String key, Double defaultValue)
180    {
181        return config.getDouble(makePath(key), defaultValue);
182    }
183
184    @Override
185    public double getDouble(String key)
186    {
187        return config.getDouble(makePath(key));
188    }
189
190    @Override
191    public float getFloat(String key, float defaultValue)
192    {
193        return config.getFloat(makePath(key), defaultValue);
194    }
195
196    @Override
197    public Float getFloat(String key, Float defaultValue)
198    {
199        return config.getFloat(makePath(key), defaultValue);
200    }
201
202    @Override
203    public float getFloat(String key)
204    {
205        return config.getFloat(makePath(key));
206    }
207
208    @Override
209    public int getInt(String key, int defaultValue)
210    {
211        return config.getInt(makePath(key), defaultValue);
212    }
213
214    @Override
215    public int getInt(String key)
216    {
217        return config.getInt(makePath(key));
218    }
219
220    @Override
221    public Integer getInteger(String key, Integer defaultValue)
222    {
223        return config.getInteger(makePath(key), defaultValue);
224    }
225
226    @Override
227    public Iterator<String> getKeys()
228    {
229        return config.getKeys(makePath());
230    }
231
232    @Override
233    public Iterator<String> getKeys(String prefix)
234    {
235        return config.getKeys(makePath(prefix));
236    }
237
238    @Override
239    public List<Object> getList(String key, List<?> defaultValue)
240    {
241        return config.getList(makePath(key), defaultValue);
242    }
243
244    @Override
245    public List<Object> getList(String key)
246    {
247        return config.getList(makePath(key));
248    }
249
250    @Override
251    public long getLong(String key, long defaultValue)
252    {
253        return config.getLong(makePath(key), defaultValue);
254    }
255
256    @Override
257    public Long getLong(String key, Long defaultValue)
258    {
259        return config.getLong(makePath(key), defaultValue);
260    }
261
262    @Override
263    public long getLong(String key)
264    {
265        return config.getLong(makePath(key));
266    }
267
268    @Override
269    public Properties getProperties(String key)
270    {
271        return config.getProperties(makePath(key));
272    }
273
274    @Override
275    public Object getProperty(String key)
276    {
277        return config.getProperty(makePath(key));
278    }
279
280    @Override
281    public short getShort(String key, short defaultValue)
282    {
283        return config.getShort(makePath(key), defaultValue);
284    }
285
286    @Override
287    public Short getShort(String key, Short defaultValue)
288    {
289        return config.getShort(makePath(key), defaultValue);
290    }
291
292    @Override
293    public short getShort(String key)
294    {
295        return config.getShort(makePath(key));
296    }
297
298    @Override
299    public String getString(String key, String defaultValue)
300    {
301        return config.getString(makePath(key), defaultValue);
302    }
303
304    @Override
305    public String getString(String key)
306    {
307        return config.getString(makePath(key));
308    }
309
310    @Override
311    public String[] getStringArray(String key)
312    {
313        return config.getStringArray(makePath(key));
314    }
315
316    @Override
317    public boolean isEmpty()
318    {
319        return getConfig().isEmpty();
320    }
321
322    @Override
323    public void setProperty(String key, Object value)
324    {
325        getConfig().setProperty(key, value);
326    }
327
328    @Override
329    public Configuration subset(String prefix)
330    {
331        return getConfig().subset(prefix);
332    }
333
334    @Override
335    public Node getRoot()
336    {
337        return getConfig().getRoot();
338    }
339
340    @Override
341    public void setRoot(Node node)
342    {
343        if (init)
344        {
345            getConfig().setRoot(node);
346        }
347        else
348        {
349            super.setRoot(node);
350        }
351    }
352
353    @Override
354    public ConfigurationNode getRootNode()
355    {
356        return getConfig().getRootNode();
357    }
358
359    @Override
360    public void setRootNode(ConfigurationNode rootNode)
361    {
362        if (init)
363        {
364            getConfig().setRootNode(rootNode);
365        }
366        else
367        {
368            super.setRootNode(rootNode);
369        }
370    }
371
372    @Override
373    public ExpressionEngine getExpressionEngine()
374    {
375        return config.getExpressionEngine();
376    }
377
378    @Override
379    public void setExpressionEngine(ExpressionEngine expressionEngine)
380    {
381        if (init)
382        {
383            config.setExpressionEngine(expressionEngine);
384        }
385        else
386        {
387            super.setExpressionEngine(expressionEngine);
388        }
389    }
390
391    @Override
392    public void addNodes(String key, Collection<? extends ConfigurationNode> nodes)
393    {
394        getConfig().addNodes(key, nodes);
395    }
396
397    @Override
398    public SubnodeConfiguration configurationAt(String key, boolean supportUpdates)
399    {
400        return config.configurationAt(makePath(key), supportUpdates);
401    }
402
403    @Override
404    public SubnodeConfiguration configurationAt(String key)
405    {
406        return config.configurationAt(makePath(key));
407    }
408
409    @Override
410    public List<HierarchicalConfiguration> configurationsAt(String key)
411    {
412        return config.configurationsAt(makePath(key));
413    }
414
415    @Override
416    public void clearTree(String key)
417    {
418        config.clearTree(makePath(key));
419    }
420
421    @Override
422    public int getMaxIndex(String key)
423    {
424        return config.getMaxIndex(makePath(key));
425    }
426
427    @Override
428    public Configuration interpolatedConfiguration()
429    {
430        return getConfig().interpolatedConfiguration();
431    }
432
433    @Override
434    public void addConfigurationListener(ConfigurationListener l)
435    {
436        getConfig().addConfigurationListener(l);
437    }
438
439    @Override
440    public boolean removeConfigurationListener(ConfigurationListener l)
441    {
442        return getConfig().removeConfigurationListener(l);
443    }
444
445    @Override
446    public Collection<ConfigurationListener> getConfigurationListeners()
447    {
448        return getConfig().getConfigurationListeners();
449    }
450
451    @Override
452    public void clearConfigurationListeners()
453    {
454        getConfig().clearConfigurationListeners();
455    }
456
457    @Override
458    public void addErrorListener(ConfigurationErrorListener l)
459    {
460        getConfig().addErrorListener(l);
461    }
462
463    @Override
464    public boolean removeErrorListener(ConfigurationErrorListener l)
465    {
466        return getConfig().removeErrorListener(l);
467    }
468
469    @Override
470    public void clearErrorListeners()
471    {
472        getConfig().clearErrorListeners();
473    }
474
475    public void save(Writer writer) throws ConfigurationException
476    {
477        config.save(writer);
478    }
479
480    public void load(Reader reader) throws ConfigurationException
481    {
482        config.load(reader);
483    }
484
485    @Override
486    public Collection<ConfigurationErrorListener> getErrorListeners()
487    {
488        return getConfig().getErrorListeners();
489    }
490
491    @Override
492    protected Object resolveContainerStore(String key)
493    {
494        if (recursive.get().booleanValue())
495        {
496            return null;
497        }
498        recursive.set(Boolean.TRUE);
499        try
500        {
501            return super.resolveContainerStore(key);
502        }
503        finally
504        {
505            recursive.set(Boolean.FALSE);
506        }
507    }
508
509    private HierarchicalConfiguration getConfig()
510    {
511        return config.configurationAt(makePath());
512    }
513
514    private String makePath()
515    {
516        String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
517        return getSubstitutor().replace(pathPattern);
518    }
519
520    /*
521     * Resolve the root expression and then add the item being retrieved. Insert a
522     * separator character as required.
523     */
524    private String makePath(String item)
525    {
526        String pathPattern;
527        if ((item.length() == 0 || item.startsWith("/")) && trailing)
528        {
529            pathPattern = path.substring(0, path.length() - 1);
530        }
531        else  if (!item.startsWith("/") || !trailing)
532        {
533            pathPattern = path + "/";
534        }
535        else
536        {
537            pathPattern = path;
538        }
539        return getSubstitutor().replace(pathPattern) + item;
540    }
541}