    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}