    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}