Java tutorial
/* * 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 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(final 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(final 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(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) { this(inMemoryConfiguration); if (configurations != null) { for (final Configuration c : configurations) { addConfiguration(c); } } } /** * Add a configuration. * * @param config the configuration to add */ public void addConfiguration(final 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(final Configuration config, final 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(); } } /** * Add a configuration to the start of the list of child configurations. * * @param config the configuration to add * @since 2.3 */ public void addConfigurationFirst(final Configuration config) { addConfigurationFirst(config, false); } /** * Adds a child configuration to the start of the collection 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 2.3 */ public void addConfigurationFirst(final Configuration config, final boolean asInMemory) { beginWrite(false); try { if (!configList.contains(config)) { if (asInMemory) { replaceInMemoryConfiguration(config); inMemoryConfigIsChild = true; } configList.add(0, 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(final 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(final String key, final 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(final String key) { Configuration firstMatchingConfiguration = null; for (final Configuration config : configList) { if (config.containsKey(key)) { firstMatchingConfiguration = config; break; } } if (firstMatchingConfiguration != null) { return firstMatchingConfiguration.getProperty(key); } return null; } @Override protected Iterator<String> getKeysInternal() { final Set<String> keys = new LinkedHashSet<>(); for (final Configuration config : configList) { for (final Iterator<String> it = config.getKeys(); it.hasNext();) { keys.add(it.next()); } } return keys.iterator(); } @Override protected Iterator<String> getKeysInternal(final String key) { final Set<String> keys = new LinkedHashSet<>(); for (final Configuration config : configList) { for (final Iterator<String> it = config.getKeys(key); it.hasNext();) { keys.add(it.next()); } } return keys.iterator(); } @Override protected boolean isEmptyInternal() { for (final Configuration config : configList) { if (!config.isEmpty()) { return false; } } return true; } @Override protected void clearPropertyDirect(final String key) { for (final Configuration config : configList) { config.clearProperty(key); } } @Override protected boolean containsKeyInternal(final String key) { for (final Configuration config : configList) { if (config.containsKey(key)) { return true; } } return false; } @Override public List<Object> getList(final String key, final List<?> defaultValue) { final List<Object> list = new ArrayList<>(); // add all elements from the first configuration containing the requested key final Iterator<Configuration> it = configList.iterator(); while (it.hasNext() && list.isEmpty()) { final 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") final List<Object> resultList = (List<Object>) defaultValue; return resultList; } final ListIterator<Object> lit = list.listIterator(); while (lit.hasNext()) { lit.set(interpolate(lit.next())); } return list; } @Override public String[] getStringArray(final String key) { final List<Object> list = getList(key); // transform property values into strings final 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(final 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 { final CompositeConfiguration copy = (CompositeConfiguration) super.clone(); copy.configList = new LinkedList<>(); copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration()); copy.configList.add(copy.inMemoryConfiguration); for (final Configuration config : configList) { if (config != getInMemoryConfiguration()) { copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config)); } } copy.cloneInterpolator(this); return copy; } catch (final CloneNotSupportedException cnex) { // cannot happen throw new ConfigurationRuntimeException(cnex); } } /** * {@inheritDoc} This implementation ensures that the in memory * configuration is correctly initialized. */ @Override public void setListDelimiterHandler(final 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(final String key) { if (key == null) { throw new IllegalArgumentException("Key must not be null!"); } Configuration source = null; for (final 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(final 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 void appendListProperty(final List<Object> dest, final Configuration config, final String key) { final Object value = interpolate(config.getProperty(key)); if (value != null) { if (value instanceof Collection) { final Collection<?> col = (Collection<?>) value; dest.addAll(col); } else { dest.add(value); } } } }