| /* |
| * 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<Object> 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()) |
| { |
| return defaultValue; |
| } |
| |
| 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 "in memory configuration". 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); |
| } |
| } |
| } |
| } |