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