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