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