    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 */
    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            final HierarchicalConfiguration<ImmutableNode> config, final 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(final String key, final 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(final String key)
    091    {
    092        config.clearProperty(makePath(key));
    093    }
    094
    095    @Override
    096    protected boolean containsKeyInternal(final String key)
    097    {
    098        return config.containsKey(makePath(key));
    099    }
    100
    101    @Override
    102    public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue)
    103    {
    104        return config.getBigDecimal(makePath(key), defaultValue);
    105    }
    106
    107    @Override
    108    public BigDecimal getBigDecimal(final String key)
    109    {
    110        return config.getBigDecimal(makePath(key));
    111    }
    112
    113    @Override
    114    public BigInteger getBigInteger(final String key, final BigInteger defaultValue)
    115    {
    116        return config.getBigInteger(makePath(key), defaultValue);
    117    }
    118
    119    @Override
    120    public BigInteger getBigInteger(final String key)
    121    {
    122        return config.getBigInteger(makePath(key));
    123    }
    124
    125    @Override
    126    public boolean getBoolean(final String key, final boolean defaultValue)
    127    {
    128        return config.getBoolean(makePath(key), defaultValue);
    129    }
    130
    131    @Override
    132    public Boolean getBoolean(final String key, final Boolean defaultValue)
    133    {
    134        return config.getBoolean(makePath(key), defaultValue);
    135    }
    136
    137    @Override
    138    public boolean getBoolean(final String key)
    139    {
    140        return config.getBoolean(makePath(key));
    141    }
    142
    143    @Override
    144    public byte getByte(final String key, final byte defaultValue)
    145    {
    146        return config.getByte(makePath(key), defaultValue);
    147    }
    148
    149    @Override
    150    public Byte getByte(final String key, final Byte defaultValue)
    151    {
    152        return config.getByte(makePath(key), defaultValue);
    153    }
    154
    155    @Override
    156    public byte getByte(final String key)
    157    {
    158        return config.getByte(makePath(key));
    159    }
    160
    161    @Override
    162    public double getDouble(final String key, final double defaultValue)
    163    {
    164        return config.getDouble(makePath(key), defaultValue);
    165    }
    166
    167    @Override
    168    public Double getDouble(final String key, final Double defaultValue)
    169    {
    170        return config.getDouble(makePath(key), defaultValue);
    171    }
    172
    173    @Override
    174    public double getDouble(final String key)
    175    {
    176        return config.getDouble(makePath(key));
    177    }
    178
    179    @Override
    180    public float getFloat(final String key, final float defaultValue)
    181    {
    182        return config.getFloat(makePath(key), defaultValue);
    183    }
    184
    185    @Override
    186    public Float getFloat(final String key, final Float defaultValue)
    187    {
    188        return config.getFloat(makePath(key), defaultValue);
    189    }
    190
    191    @Override
    192    public float getFloat(final String key)
    193    {
    194        return config.getFloat(makePath(key));
    195    }
    196
    197    @Override
    198    public int getInt(final String key, final int defaultValue)
    199    {
    200        return config.getInt(makePath(key), defaultValue);
    201    }
    202
    203    @Override
    204    public int getInt(final String key)
    205    {
    206        return config.getInt(makePath(key));
    207    }
    208
    209    @Override
    210    public Integer getInteger(final String key, final 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(final String prefix)
    223    {
    224        return config.getKeys(makePath(prefix));
    225    }
    226
    227    @Override
    228    public List<Object> getList(final String key, final List<?> defaultValue)
    229    {
    230        return config.getList(makePath(key), defaultValue);
    231    }
    232
    233    @Override
    234    public List<Object> getList(final String key)
    235    {
    236        return config.getList(makePath(key));
    237    }
    238
    239    @Override
    240    public long getLong(final String key, final long defaultValue)
    241    {
    242        return config.getLong(makePath(key), defaultValue);
    243    }
    244
    245    @Override
    246    public Long getLong(final String key, final Long defaultValue)
    247    {
    248        return config.getLong(makePath(key), defaultValue);
    249    }
    250
    251    @Override
    252    public long getLong(final String key)
    253    {
    254        return config.getLong(makePath(key));
    255    }
    256
    257    @Override
    258    public Properties getProperties(final String key)
    259    {
    260        return config.getProperties(makePath(key));
    261    }
    262
    263    @Override
    264    protected Object getPropertyInternal(final String key)
    265    {
    266        return config.getProperty(makePath(key));
    267    }
    268
    269    @Override
    270    public short getShort(final String key, final short defaultValue)
    271    {
    272        return config.getShort(makePath(key), defaultValue);
    273    }
    274
    275    @Override
    276    public Short getShort(final String key, final Short defaultValue)
    277    {
    278        return config.getShort(makePath(key), defaultValue);
    279    }
    280
    281    @Override
    282    public short getShort(final String key)
    283    {
    284        return config.getShort(makePath(key));
    285    }
    286
    287    @Override
    288    public String getString(final String key, final String defaultValue)
    289    {
    290        return config.getString(makePath(key), defaultValue);
    291    }
    292
    293    @Override
    294    public String getString(final String key)
    295    {
    296        return config.getString(makePath(key));
    297    }
    298
    299    @Override
    300    public String[] getStringArray(final 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(final String key, final Object value)
    313    {
    314        getConfig().setProperty(key, value);
    315    }
    316
    317    @Override
    318    public Configuration subset(final 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(final 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(final String key, final Collection<? extends ImmutableNode> nodes)
    344    {
    345        getConfig().addNodes(key, nodes);
    346    }
    347
    348    @Override
    349    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates)
    350    {
    351        return config.configurationAt(makePath(key), supportUpdates);
    352    }
    353
    354    @Override
    355    public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key)
    356    {
    357        return config.configurationAt(makePath(key));
    358    }
    359
    360    @Override
    361    public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key)
    362    {
    363        return config.configurationsAt(makePath(key));
    364    }
    365
    366    @Override
    367    protected Object clearTreeInternal(final String key)
    368    {
    369        config.clearTree(makePath(key));
    370        return Collections.emptyList();
    371    }
    372
    373    @Override
    374    protected int getMaxIndexInternal(final 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(final EventType<T> eventType,
    387            final EventListener<? super T> listener)
    388    {
    389        getConfig().addEventListener(eventType, listener);
    390    }
    391
    392    @Override
    393    public <T extends Event> boolean removeEventListener(
    394            final EventType<T> eventType, final 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            final 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(final Writer writer) throws ConfigurationException, IOException
    420    {
    421        fetchFileBased().write(writer);
    422    }
    423
    424    @Override
    425    public void read(final 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        final 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(final 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(final String pattern)
    471    {
    472        return Objects.toString(getInterpolator().interpolate(pattern), null);
    473    }
    474
    475    /**
    476     * Returns the wrapped configuration as a {@code FileBased} object. If this
    477     * cast is not possible, an exception is thrown.
    478     *
    479     * @return the wrapped configuration as {@code FileBased}
    480     * @throws ConfigurationException if the wrapped configuration does not
    481     *         implement {@code FileBased}
    482     */
    483    private FileBased fetchFileBased() throws ConfigurationException
    484    {
    485        if (!(config instanceof FileBased))
    486        {
    487            throw new ConfigurationException(
    488                    "Wrapped configuration does not implement FileBased!"
    489                            + " No I/O operations are supported.");
    490        }
    491        return (FileBased) config;
    492    }
    493}