blob: f23eb9ba7d1cc2652b33ac4b3913cef369814b14 [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.apache.commons.configuration2.convert.ListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
/**
* <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
* objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
* any properties shared will mean that the value defined by Configuration1
* will be returned. If Configuration1 doesn't have the property, then
* Configuration2 will be checked. You can add multiple different types or the
* same type of properties file.</p>
* <p>When querying properties the order in which child configurations have been
* added is relevant. To deal with property updates, a so-called <em>in-memory
* configuration</em> is used. Per default, such a configuration is created
* automatically. All property writes target this special configuration. There
* are constructors which allow you to provide a specific in-memory configuration.
* If used that way, the in-memory configuration is always the last one in the
* list of child configurations. This means that for query operations all other
* configurations take precedence.</p>
* <p>Alternatively it is possible to mark a child configuration as in-memory
* configuration when it is added. In this case the treatment of the in-memory
* configuration is slightly different: it remains in the list of child
* configurations at the position it was added, i.e. its priority for property
* queries can be defined by adding the child configurations in the correct
* order.</p>
* <p>
* This configuration class uses a {@code Synchronizer} to control concurrent
* access. While all methods for reading and writing configuration properties
* make use of this {@code Synchronizer} per default, the methods for managing
* the list of child configurations and the in-memory configuration
* ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
* getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because
* most methods for accessing configuration data delegate to the list of child
* configurations, the thread-safety of a {@code CompositeConfiguration}
* object also depends on the {@code Synchronizer} objects used by these
* children.
* </p>
*
* @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @version $Id$
*/
public class CompositeConfiguration extends AbstractConfiguration
implements Cloneable
{
/** List holding all the configuration */
private List<Configuration> configList = new LinkedList<Configuration>();
/**
* Configuration that holds in memory stuff. Inserted as first so any
* setProperty() override anything else added.
*/
private Configuration inMemoryConfiguration;
/**
* Stores a flag whether the current in-memory configuration is also a
* child configuration.
*/
private boolean inMemoryConfigIsChild;
/**
* Creates an empty CompositeConfiguration object which can then
* be added some other Configuration files
*/
public CompositeConfiguration()
{
clear();
}
/**
* Creates a CompositeConfiguration object with a specified <em>in-memory
* configuration</em>. This configuration will store any changes made to the
* {@code CompositeConfiguration}. Note: Use this constructor if you want to
* set a special type of in-memory configuration. If you have a
* configuration which should act as both a child configuration and as
* in-memory configuration, use
* {@link #addConfiguration(Configuration, boolean)} with a value of
* <b>true</b> instead.
*
* @param inMemoryConfiguration the in memory configuration to use
*/
public CompositeConfiguration(Configuration inMemoryConfiguration)
{
configList.clear();
this.inMemoryConfiguration = inMemoryConfiguration;
configList.add(inMemoryConfiguration);
}
/**
* Create a CompositeConfiguration with an empty in memory configuration
* and adds the collection of configurations specified.
*
* @param configurations the collection of configurations to add
*/
public CompositeConfiguration(Collection<? extends Configuration> configurations)
{
this(new BaseConfiguration(), configurations);
}
/**
* Creates a CompositeConfiguration with a specified <em>in-memory
* configuration</em>, and then adds the given collection of configurations.
*
* @param inMemoryConfiguration the in memory configuration to use
* @param configurations the collection of configurations to add
* @see #CompositeConfiguration(Configuration)
*/
public CompositeConfiguration(Configuration inMemoryConfiguration,
Collection<? extends Configuration> configurations)
{
this(inMemoryConfiguration);
if (configurations != null)
{
for (Configuration c : configurations)
{
addConfiguration(c);
}
}
}
/**
* Add a configuration.
*
* @param config the configuration to add
*/
public void addConfiguration(Configuration config)
{
addConfiguration(config, false);
}
/**
* Adds a child configuration and optionally makes it the <em>in-memory
* configuration</em>. This means that all future property write operations
* are executed on this configuration. Note that the current in-memory
* configuration is replaced by the new one. If it was created automatically
* or passed to the constructor, it is removed from the list of child
* configurations! Otherwise, it stays in the list of child configurations
* at its current position, but it passes its role as in-memory
* configuration to the new one.
*
* @param config the configuration to be added
* @param asInMemory <b>true</b> if this configuration becomes the new
* <em>in-memory</em> configuration, <b>false</b> otherwise
* @since 1.8
*/
public void addConfiguration(Configuration config, boolean asInMemory)
{
beginWrite(false);
try
{
if (!configList.contains(config))
{
if (asInMemory)
{
replaceInMemoryConfiguration(config);
inMemoryConfigIsChild = true;
}
if (!inMemoryConfigIsChild)
{
// As the inMemoryConfiguration contains all manually added
// keys, we must make sure that it is always last. "Normal", non
// composed configurations add their keys at the end of the
// configuration and we want to mimic this behavior.
configList.add(configList.indexOf(inMemoryConfiguration),
config);
}
else
{
// However, if the in-memory configuration is a regular child,
// only the order in which child configurations are added is relevant
configList.add(config);
}
if (config instanceof AbstractConfiguration)
{
((AbstractConfiguration) config)
.setThrowExceptionOnMissing(isThrowExceptionOnMissing());
}
}
}
finally
{
endWrite();
}
}
/**
* Remove a configuration. The in memory configuration cannot be removed.
*
* @param config The configuration to remove
*/
public void removeConfiguration(Configuration config)
{
beginWrite(false);
try
{
// Make sure that you can't remove the inMemoryConfiguration from
// the CompositeConfiguration object
if (!config.equals(inMemoryConfiguration))
{
configList.remove(config);
}
}
finally
{
endWrite();
}
}
/**
* Return the number of configurations.
*
* @return the number of configuration
*/
public int getNumberOfConfigurations()
{
beginRead(false);
try
{
return configList.size();
}
finally
{
endRead();
}
}
/**
* Removes all child configurations and reinitializes the <em>in-memory
* configuration</em>. <strong>Attention:</strong> A new in-memory
* configuration is created; the old one is lost.
*/
@Override
protected void clearInternal()
{
configList.clear();
// recreate the in memory configuration
inMemoryConfiguration = new BaseConfiguration();
((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
configList.add(inMemoryConfiguration);
inMemoryConfigIsChild = false;
}
/**
* Add this property to the in-memory Configuration.
*
* @param key The Key to add the property to.
* @param token The Value to add.
*/
@Override
protected void addPropertyDirect(String key, Object token)
{
inMemoryConfiguration.addProperty(key, token);
}
/**
* Read property from underlying composite
*
* @param key key to use for mapping
*
* @return object associated with the given configuration key.
*/
@Override
protected Object getPropertyInternal(String key)
{
Configuration firstMatchingConfiguration = null;
for (Configuration config : configList)
{
if (config.containsKey(key))
{
firstMatchingConfiguration = config;
break;
}
}
if (firstMatchingConfiguration != null)
{
return firstMatchingConfiguration.getProperty(key);
}
else
{
return null;
}
}
@Override
protected Iterator<String> getKeysInternal()
{
Set<String> keys = new LinkedHashSet<String>();
for (Configuration config : configList)
{
for (Iterator<String> it = config.getKeys(); it.hasNext();)
{
keys.add(it.next());
}
}
return keys.iterator();
}
@Override
protected Iterator<String> getKeysInternal(String key)
{
Set<String> keys = new LinkedHashSet<String>();
for (Configuration config : configList)
{
for (Iterator<String> it = config.getKeys(key); it.hasNext();)
{
keys.add(it.next());
}
}
return keys.iterator();
}
@Override
protected boolean isEmptyInternal()
{
for (Configuration config : configList)
{
if (!config.isEmpty())
{
return false;
}
}
return true;
}
@Override
protected void clearPropertyDirect(String key)
{
for (Configuration config : configList)
{
config.clearProperty(key);
}
}
@Override
protected boolean containsKeyInternal(String key)
{
for (Configuration config : configList)
{
if (config.containsKey(key))
{
return true;
}
}
return false;
}
@Override
public List<Object> getList(String key, List<?> defaultValue)
{
List<Object> list = new ArrayList<Object>();
// add all elements from the first configuration containing the requested key
Iterator<Configuration> it = configList.iterator();
while (it.hasNext() && list.isEmpty())
{
Configuration config = it.next();
if (config != inMemoryConfiguration && config.containsKey(key))
{
appendListProperty(list, config, key);
}
}
// add all elements from the in memory configuration
appendListProperty(list, inMemoryConfiguration, key);
if (list.isEmpty())
{
// This is okay because we just return this list to the caller
@SuppressWarnings("unchecked")
List<Object> resultList = (List<Object>) defaultValue;
return resultList;
}
ListIterator<Object> lit = list.listIterator();
while (lit.hasNext())
{
lit.set(interpolate(lit.next()));
}
return list;
}
@Override
public String[] getStringArray(String key)
{
List<Object> list = getList(key);
// transform property values into strings
String[] tokens = new String[list.size()];
for (int i = 0; i < tokens.length; i++)
{
tokens[i] = String.valueOf(list.get(i));
}
return tokens;
}
/**
* Return the configuration at the specified index.
*
* @param index The index of the configuration to retrieve
* @return the configuration at this index
*/
public Configuration getConfiguration(int index)
{
beginRead(false);
try
{
return configList.get(index);
}
finally
{
endRead();
}
}
/**
* Returns the &quot;in memory configuration&quot;. In this configuration
* changes are stored.
*
* @return the in memory configuration
*/
public Configuration getInMemoryConfiguration()
{
beginRead(false);
try
{
return inMemoryConfiguration;
}
finally
{
endRead();
}
}
/**
* Returns a copy of this object. This implementation will create a deep
* clone, i.e. all configurations contained in this composite will also be
* cloned. This only works if all contained configurations support cloning;
* otherwise a runtime exception will be thrown. Registered event handlers
* won't get cloned.
*
* @return the copy
* @since 1.3
*/
@Override
public Object clone()
{
try
{
CompositeConfiguration copy = (CompositeConfiguration) super
.clone();
copy.configList = new LinkedList<Configuration>();
copy.inMemoryConfiguration = ConfigurationUtils
.cloneConfiguration(getInMemoryConfiguration());
copy.configList.add(copy.inMemoryConfiguration);
for (Configuration config : configList)
{
if (config != getInMemoryConfiguration())
{
copy.addConfiguration(ConfigurationUtils
.cloneConfiguration(config));
}
}
copy.cloneInterpolator(this);
return copy;
}
catch (CloneNotSupportedException cnex)
{
// cannot happen
throw new ConfigurationRuntimeException(cnex);
}
}
/**
* {@inheritDoc} This implementation ensures that the in memory
* configuration is correctly initialized.
*/
@Override
public void setListDelimiterHandler(
ListDelimiterHandler listDelimiterHandler)
{
if (inMemoryConfiguration instanceof AbstractConfiguration)
{
((AbstractConfiguration) inMemoryConfiguration)
.setListDelimiterHandler(listDelimiterHandler);
}
super.setListDelimiterHandler(listDelimiterHandler);
}
/**
* Returns the configuration source, in which the specified key is defined.
* This method will iterate over all existing child configurations and check
* whether they contain the specified key. The following constellations are
* possible:
* <ul>
* <li>If exactly one child configuration contains the key, this
* configuration is returned as the source configuration. This may be the
* <em>in memory configuration</em> (this has to be explicitly checked by
* the calling application).</li>
* <li>If none of the child configurations contain the key, <b>null</b> is
* returned.</li>
* <li>If the key is contained in multiple child configurations or if the
* key is <b>null</b>, a {@code IllegalArgumentException} is thrown.
* In this case the source configuration cannot be determined.</li>
* </ul>
*
* @param key the key to be checked
* @return the source configuration of this key
* @throws IllegalArgumentException if the source configuration cannot be
* determined
* @since 1.5
*/
public Configuration getSource(String key)
{
if (key == null)
{
throw new IllegalArgumentException("Key must not be null!");
}
Configuration source = null;
for (Configuration conf : configList)
{
if (conf.containsKey(key))
{
if (source != null)
{
throw new IllegalArgumentException("The key " + key
+ " is defined by multiple sources!");
}
source = conf;
}
}
return source;
}
/**
* Replaces the current in-memory configuration by the given one.
*
* @param config the new in-memory configuration
*/
private void replaceInMemoryConfiguration(Configuration config)
{
if (!inMemoryConfigIsChild)
{
// remove current in-memory configuration
configList.remove(inMemoryConfiguration);
}
inMemoryConfiguration = config;
}
/**
* Adds the value of a property to the given list. This method is used by
* {@code getList()} for gathering property values from the child
* configurations.
*
* @param dest the list for collecting the data
* @param config the configuration to query
* @param key the key of the property
*/
private static void appendListProperty(List<Object> dest, Configuration config,
String key)
{
Object value = config.getProperty(key);
if (value != null)
{
if (value instanceof Collection)
{
Collection<?> col = (Collection<?>) value;
dest.addAll(col);
}
else
{
dest.add(value);
}
}
}
}