PatternSubtreeConfigurationWrapper.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration2;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import org.apache.commons.configuration2.event.Event;
import org.apache.commons.configuration2.event.EventListener;
import org.apache.commons.configuration2.event.EventType;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileBased;
import org.apache.commons.configuration2.tree.ExpressionEngine;
import org.apache.commons.configuration2.tree.ImmutableNode;
/**
* Wraps a BaseHierarchicalConfiguration and allows subtrees to be accessed via a configured path with replaceable
* tokens derived from the ConfigurationInterpolator. When used with injection frameworks such as Spring it allows
* components to be injected with subtrees of the configuration.
*
* @since 1.6
*/
public class PatternSubtreeConfigurationWrapper extends BaseHierarchicalConfiguration implements FileBasedConfiguration {
/** The wrapped configuration */
private final HierarchicalConfiguration<ImmutableNode> config;
/** The path to the subtree */
private final String path;
/** True if the path ends with '/', false otherwise */
private final boolean trailing;
/** True if the constructor has finished */
private final boolean init;
/**
* Constructor
*
* @param config The Configuration to be wrapped.
* @param path The base path pattern.
*/
public PatternSubtreeConfigurationWrapper(final HierarchicalConfiguration<ImmutableNode> config, final String path) {
this.config = Objects.requireNonNull(config, "config");
this.path = path;
this.trailing = path.endsWith("/");
this.init = true;
}
@Override
public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
getConfig().addEventListener(eventType, listener);
}
@Override
protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
getConfig().addNodes(key, nodes);
}
@Override
protected void addPropertyInternal(final String key, final Object value) {
config.addProperty(makePath(key), value);
}
@Override
public void clearErrorListeners() {
getConfig().clearErrorListeners();
}
@Override
public void clearEventListeners() {
getConfig().clearEventListeners();
}
@Override
protected void clearInternal() {
getConfig().clear();
}
@Override
protected void clearPropertyDirect(final String key) {
config.clearProperty(makePath(key));
}
@Override
protected Object clearTreeInternal(final String key) {
config.clearTree(makePath(key));
return Collections.emptyList();
}
@Override
public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
return config.configurationAt(makePath(key));
}
@Override
public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
return config.configurationAt(makePath(key), supportUpdates);
}
@Override
public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
return config.configurationsAt(makePath(key));
}
@Override
protected boolean containsKeyInternal(final String key) {
return config.containsKey(makePath(key));
}
/**
* Tests whether this configuration contains one or more matches to this value. This operation stops at first
* match but may be more expensive than the containsKey method.
* @since 2.11.0
*/
@Override
protected boolean containsValueInternal(final Object value) {
return config.containsValue(value);
}
/**
* Returns the wrapped configuration as a {@code FileBased} object. If this cast is not possible, an exception is
* thrown.
*
* @return the wrapped configuration as {@code FileBased}
* @throws ConfigurationException if the wrapped configuration does not implement {@code FileBased}
*/
private FileBased fetchFileBased() throws ConfigurationException {
if (!(config instanceof FileBased)) {
throw new ConfigurationException("Wrapped configuration does not implement FileBased!" + " No I/O operations are supported.");
}
return (FileBased) config;
}
@Override
public BigDecimal getBigDecimal(final String key) {
return config.getBigDecimal(makePath(key));
}
@Override
public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
return config.getBigDecimal(makePath(key), defaultValue);
}
@Override
public BigInteger getBigInteger(final String key) {
return config.getBigInteger(makePath(key));
}
@Override
public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
return config.getBigInteger(makePath(key), defaultValue);
}
@Override
public boolean getBoolean(final String key) {
return config.getBoolean(makePath(key));
}
@Override
public boolean getBoolean(final String key, final boolean defaultValue) {
return config.getBoolean(makePath(key), defaultValue);
}
@Override
public Boolean getBoolean(final String key, final Boolean defaultValue) {
return config.getBoolean(makePath(key), defaultValue);
}
@Override
public byte getByte(final String key) {
return config.getByte(makePath(key));
}
@Override
public byte getByte(final String key, final byte defaultValue) {
return config.getByte(makePath(key), defaultValue);
}
@Override
public Byte getByte(final String key, final Byte defaultValue) {
return config.getByte(makePath(key), defaultValue);
}
private BaseHierarchicalConfiguration getConfig() {
return (BaseHierarchicalConfiguration) config.configurationAt(makePath());
}
@Override
public double getDouble(final String key) {
return config.getDouble(makePath(key));
}
@Override
public double getDouble(final String key, final double defaultValue) {
return config.getDouble(makePath(key), defaultValue);
}
@Override
public Double getDouble(final String key, final Double defaultValue) {
return config.getDouble(makePath(key), defaultValue);
}
@Override
public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
return getConfig().getEventListeners(eventType);
}
@Override
public ExpressionEngine getExpressionEngine() {
return config.getExpressionEngine();
}
@Override
public float getFloat(final String key) {
return config.getFloat(makePath(key));
}
@Override
public float getFloat(final String key, final float defaultValue) {
return config.getFloat(makePath(key), defaultValue);
}
@Override
public Float getFloat(final String key, final Float defaultValue) {
return config.getFloat(makePath(key), defaultValue);
}
@Override
public int getInt(final String key) {
return config.getInt(makePath(key));
}
@Override
public int getInt(final String key, final int defaultValue) {
return config.getInt(makePath(key), defaultValue);
}
@Override
public Integer getInteger(final String key, final Integer defaultValue) {
return config.getInteger(makePath(key), defaultValue);
}
@Override
protected Iterator<String> getKeysInternal() {
return config.getKeys(makePath());
}
@Override
protected Iterator<String> getKeysInternal(final String prefix) {
return config.getKeys(makePath(prefix));
}
@Override
public List<Object> getList(final String key) {
return config.getList(makePath(key));
}
@Override
public List<Object> getList(final String key, final List<?> defaultValue) {
return config.getList(makePath(key), defaultValue);
}
@Override
public long getLong(final String key) {
return config.getLong(makePath(key));
}
@Override
public long getLong(final String key, final long defaultValue) {
return config.getLong(makePath(key), defaultValue);
}
@Override
public Long getLong(final String key, final Long defaultValue) {
return config.getLong(makePath(key), defaultValue);
}
@Override
protected int getMaxIndexInternal(final String key) {
return config.getMaxIndex(makePath(key));
}
@Override
public Properties getProperties(final String key) {
return config.getProperties(makePath(key));
}
@Override
protected Object getPropertyInternal(final String key) {
return config.getProperty(makePath(key));
}
@Override
public short getShort(final String key) {
return config.getShort(makePath(key));
}
@Override
public short getShort(final String key, final short defaultValue) {
return config.getShort(makePath(key), defaultValue);
}
@Override
public Short getShort(final String key, final Short defaultValue) {
return config.getShort(makePath(key), defaultValue);
}
@Override
public String getString(final String key) {
return config.getString(makePath(key));
}
@Override
public String getString(final String key, final String defaultValue) {
return config.getString(makePath(key), defaultValue);
}
@Override
public String[] getStringArray(final String key) {
return config.getStringArray(makePath(key));
}
@Override
public Configuration interpolatedConfiguration() {
return getConfig().interpolatedConfiguration();
}
@Override
protected boolean isEmptyInternal() {
return getConfig().isEmpty();
}
private String makePath() {
final String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
return substitute(pathPattern);
}
/*
* Resolve the root expression and then add the item being retrieved. Insert a separator character as required.
*/
private String makePath(final String item) {
final String pathPattern;
if ((item.isEmpty() || item.startsWith("/")) && trailing) {
pathPattern = path.substring(0, path.length() - 1);
} else if (!item.startsWith("/") || !trailing) {
pathPattern = path + "/";
} else {
pathPattern = path;
}
return substitute(pathPattern) + item;
}
@Override
public void read(final Reader reader) throws ConfigurationException, IOException {
fetchFileBased().read(reader);
}
@Override
public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
return getConfig().removeEventListener(eventType, listener);
}
@Override
public void setExpressionEngine(final ExpressionEngine expressionEngine) {
if (init) {
config.setExpressionEngine(expressionEngine);
} else {
super.setExpressionEngine(expressionEngine);
}
}
@Override
protected void setPropertyInternal(final String key, final Object value) {
getConfig().setProperty(key, value);
}
@Override
public Configuration subset(final String prefix) {
return getConfig().subset(prefix);
}
/**
* Uses this configuration's {@code ConfigurationInterpolator} to perform variable substitution on the given pattern
* string.
*
* @param pattern the pattern string
* @return the string with variables replaced
*/
private String substitute(final String pattern) {
return Objects.toString(getInterpolator().interpolate(pattern), null);
}
@Override
public void write(final Writer writer) throws ConfigurationException, IOException {
fetchFileBased().write(writer);
}
}