CompositeConfiguration.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.configuration2;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Iterator;
  21. import java.util.LinkedHashSet;
  22. import java.util.LinkedList;
  23. import java.util.List;
  24. import java.util.ListIterator;
  25. import java.util.Set;

  26. import org.apache.commons.configuration2.convert.ListDelimiterHandler;
  27. import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;

  28. /**
  29.  * <p>
  30.  * {@code CompositeConfiguration} allows you to add multiple {@code Configuration} objects to an aggregated
  31.  * configuration. If you add Configuration1, and then Configuration2, any properties shared will mean that the value
  32.  * defined by Configuration1 will be returned. If Configuration1 doesn't have the property, then Configuration2 will be
  33.  * checked. You can add multiple different types or the same type of properties file.
  34.  * </p>
  35.  * <p>
  36.  * When querying properties the order in which child configurations have been added is relevant. To deal with property
  37.  * updates, a so-called <em>in-memory configuration</em> is used. Per default, such a configuration is created
  38.  * automatically. All property writes target this special configuration. There are constructors which allow you to
  39.  * provide a specific in-memory configuration. If used that way, the in-memory configuration is always the last one in
  40.  * the list of child configurations. This means that for query operations all other configurations take precedence.
  41.  * </p>
  42.  * <p>
  43.  * Alternatively it is possible to mark a child configuration as in-memory configuration when it is added. In this case
  44.  * the treatment of the in-memory configuration is slightly different: it remains in the list of child configurations at
  45.  * the position it was added, i.e. its priority for property queries can be defined by adding the child configurations
  46.  * in the correct order.
  47.  * </p>
  48.  * <p>
  49.  * This configuration class uses a {@code Synchronizer} to control concurrent access. While all methods for reading and
  50.  * writing configuration properties make use of this {@code Synchronizer} per default, the methods for managing the list
  51.  * of child configurations and the in-memory configuration
  52.  * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
  53.  * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because most methods for accessing configuration
  54.  * data delegate to the list of child configurations, the thread-safety of a {@code CompositeConfiguration} object also
  55.  * depends on the {@code Synchronizer} objects used by these children.
  56.  * </p>
  57.  */
  58. public class CompositeConfiguration extends AbstractConfiguration implements Cloneable {

  59.     /** List holding all the configuration */
  60.     private List<Configuration> configList = new LinkedList<>();

  61.     /**
  62.      * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added.
  63.      */
  64.     private Configuration inMemoryConfiguration;

  65.     /**
  66.      * Stores a flag whether the current in-memory configuration is also a child configuration.
  67.      */
  68.     private boolean inMemoryConfigIsChild;

  69.     /**
  70.      * Creates an empty CompositeConfiguration object which can then be added some other Configuration files
  71.      */
  72.     public CompositeConfiguration() {
  73.         clear();
  74.     }

  75.     /**
  76.      * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations
  77.      * specified.
  78.      *
  79.      * @param configurations the collection of configurations to add
  80.      */
  81.     public CompositeConfiguration(final Collection<? extends Configuration> configurations) {
  82.         this(new BaseConfiguration(), configurations);
  83.     }

  84.     /**
  85.      * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will
  86.      * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special
  87.      * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as
  88.      * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <strong>true</strong> instead.
  89.      *
  90.      * @param inMemoryConfiguration the in memory configuration to use
  91.      */
  92.     public CompositeConfiguration(final Configuration inMemoryConfiguration) {
  93.         this.configList.clear();
  94.         this.inMemoryConfiguration = inMemoryConfiguration;
  95.         this.configList.add(inMemoryConfiguration);
  96.     }

  97.     /**
  98.      * Creates a CompositeConfiguration with a specified <em>in-memory configuration</em>, and then adds the given
  99.      * collection of configurations.
  100.      *
  101.      * @param inMemoryConfiguration the in memory configuration to use
  102.      * @param configurations the collection of configurations to add
  103.      * @see #CompositeConfiguration(Configuration)
  104.      */
  105.     public CompositeConfiguration(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) {
  106.         this(inMemoryConfiguration);
  107.         if (configurations != null) {
  108.             configurations.forEach(this::addConfiguration);
  109.         }
  110.     }

  111.     /**
  112.      * Add a configuration.
  113.      *
  114.      * @param config the configuration to add
  115.      */
  116.     public void addConfiguration(final Configuration config) {
  117.         addConfiguration(config, false);
  118.     }

  119.     /**
  120.      * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future
  121.      * property write operations are executed on this configuration. Note that the current in-memory configuration is
  122.      * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of
  123.      * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes
  124.      * its role as in-memory configuration to the new one.
  125.      *
  126.      * @param config the configuration to be added
  127.      * @param asInMemory <strong>true</strong> if this configuration becomes the new <em>in-memory</em> configuration, <strong>false</strong>
  128.      *        otherwise
  129.      * @since 1.8
  130.      */
  131.     public void addConfiguration(final Configuration config, final boolean asInMemory) {
  132.         syncWrite(() -> {
  133.             if (!configList.contains(config)) {
  134.                 if (asInMemory) {
  135.                     replaceInMemoryConfiguration(config);
  136.                     inMemoryConfigIsChild = true;
  137.                 }
  138.                 if (!inMemoryConfigIsChild) {
  139.                     // As the inMemoryConfiguration contains all manually added
  140.                     // keys, we must make sure that it is always last. "Normal", non
  141.                     // composed configurations add their keys at the end of the
  142.                     // configuration and we want to mimic this behavior.
  143.                     configList.add(configList.indexOf(inMemoryConfiguration), config);
  144.                 } else {
  145.                     // However, if the in-memory configuration is a regular child,
  146.                     // only the order in which child configurations are added is relevant
  147.                     configList.add(config);
  148.                 }
  149.                 if (config instanceof AbstractConfiguration) {
  150.                     ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
  151.                 }
  152.             }
  153.         }, false);
  154.     }

  155.     /**
  156.      * Add a configuration to the start of the list of child configurations.
  157.      *
  158.      * @param config the configuration to add
  159.      * @since 2.3
  160.      */
  161.     public void addConfigurationFirst(final Configuration config) {
  162.         addConfigurationFirst(config, false);
  163.     }

  164.     /**
  165.      * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory
  166.      * configuration</em>. This means that all future property write operations are executed on this configuration. Note
  167.      * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the
  168.      * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child
  169.      * configurations at its current position, but it passes its role as in-memory configuration to the new one.
  170.      *
  171.      * @param config the configuration to be added
  172.      * @param asInMemory <strong>true</strong> if this configuration becomes the new <em>in-memory</em> configuration, <strong>false</strong>
  173.      *        otherwise
  174.      * @since 2.3
  175.      */
  176.     public void addConfigurationFirst(final Configuration config, final boolean asInMemory) {
  177.         syncWrite(() -> {
  178.             if (!configList.contains(config)) {
  179.                 if (asInMemory) {
  180.                     replaceInMemoryConfiguration(config);
  181.                     inMemoryConfigIsChild = true;
  182.                 }
  183.                 configList.add(0, config);
  184.                 if (config instanceof AbstractConfiguration) {
  185.                     ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
  186.                 }
  187.             }
  188.         }, false);
  189.     }

  190.     /**
  191.      * Add this property to the in-memory Configuration.
  192.      *
  193.      * @param key The Key to add the property to.
  194.      * @param token The Value to add.
  195.      */
  196.     @Override
  197.     protected void addPropertyDirect(final String key, final Object token) {
  198.         inMemoryConfiguration.addProperty(key, token);
  199.     }

  200.     /**
  201.      * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property
  202.      * values from the child configurations.
  203.      *
  204.      * @param dest the list for collecting the data
  205.      * @param config the configuration to query
  206.      * @param key the key of the property
  207.      */
  208.     private void appendListProperty(final List<Object> dest, final Configuration config, final String key) {
  209.         final Object value = interpolate(config.getProperty(key));
  210.         if (value != null) {
  211.             if (value instanceof Collection) {
  212.                 final Collection<?> col = (Collection<?>) value;
  213.                 dest.addAll(col);
  214.             } else {
  215.                 dest.add(value);
  216.             }
  217.         }
  218.     }

  219.     /**
  220.      * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong>
  221.      * A new in-memory configuration is created; the old one is lost.
  222.      */
  223.     @Override
  224.     protected void clearInternal() {
  225.         configList.clear();
  226.         // recreate the in memory configuration
  227.         inMemoryConfiguration = new BaseConfiguration();
  228.         ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
  229.         ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
  230.         configList.add(inMemoryConfiguration);
  231.         inMemoryConfigIsChild = false;
  232.     }

  233.     @Override
  234.     protected void clearPropertyDirect(final String key) {
  235.         configList.forEach(config -> config.clearProperty(key));
  236.     }

  237.     /**
  238.      * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in
  239.      * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a
  240.      * runtime exception will be thrown. Registered event handlers won't get cloned.
  241.      *
  242.      * @return the copy
  243.      * @since 1.3
  244.      */
  245.     @Override
  246.     public Object clone() {
  247.         try {
  248.             final CompositeConfiguration copy = (CompositeConfiguration) super.clone();
  249.             copy.configList = new LinkedList<>();
  250.             copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration());
  251.             copy.configList.add(copy.inMemoryConfiguration);

  252.             configList.forEach(config -> {
  253.                 if (config != getInMemoryConfiguration()) {
  254.                     copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config));
  255.                 }
  256.             });

  257.             copy.cloneInterpolator(this);
  258.             return copy;
  259.         } catch (final CloneNotSupportedException cnex) {
  260.             // cannot happen
  261.             throw new ConfigurationRuntimeException(cnex);
  262.         }
  263.     }

  264.     @Override
  265.     protected boolean containsKeyInternal(final String key) {
  266.         return configList.stream().anyMatch(config -> config.containsKey(key));
  267.     }

  268.     /**
  269.      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
  270.      * match but may be more expensive than the containsKey method.
  271.      * @since 2.11.0
  272.      */
  273.     @Override
  274.     protected boolean containsValueInternal(final Object value) {
  275.         return configList.stream().anyMatch(config -> config.containsValue(value));
  276.     }

  277.     /**
  278.      * Gets the configuration at the specified index.
  279.      *
  280.      * @param index The index of the configuration to retrieve
  281.      * @return the configuration at this index
  282.      */
  283.     public Configuration getConfiguration(final int index) {
  284.         return syncRead(() -> configList.get(index), false);
  285.     }

  286.     /**
  287.      * Gets the &quot;in memory configuration&quot;. In this configuration changes are stored.
  288.      *
  289.      * @return the in memory configuration
  290.      */
  291.     public Configuration getInMemoryConfiguration() {
  292.         return syncReadValue(inMemoryConfiguration, false);
  293.     }

  294.     @Override
  295.     protected Iterator<String> getKeysInternal() {
  296.         final Set<String> keys = new LinkedHashSet<>();
  297.         configList.forEach(config -> config.getKeys().forEachRemaining(keys::add));
  298.         return keys.iterator();
  299.     }

  300.     @Override
  301.     protected Iterator<String> getKeysInternal(final String key) {
  302.         final Set<String> keys = new LinkedHashSet<>();
  303.         configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add));
  304.         return keys.iterator();
  305.     }

  306.     @Override
  307.     protected Iterator<String> getKeysInternal(final String key, final String delimiter) {
  308.         final Set<String> keys = new LinkedHashSet<>();
  309.         configList.forEach(config -> config.getKeys(key, delimiter).forEachRemaining(keys::add));
  310.         return keys.iterator();
  311.     }

  312.     @Override
  313.     public List<Object> getList(final String key, final List<?> defaultValue) {
  314.         final List<Object> list = new ArrayList<>();

  315.         // add all elements from the first configuration containing the requested key
  316.         final Iterator<Configuration> it = configList.iterator();
  317.         while (it.hasNext() && list.isEmpty()) {
  318.             final Configuration config = it.next();
  319.             if (config != inMemoryConfiguration && config.containsKey(key)) {
  320.                 appendListProperty(list, config, key);
  321.             }
  322.         }

  323.         // add all elements from the in memory configuration
  324.         appendListProperty(list, inMemoryConfiguration, key);

  325.         if (list.isEmpty()) {
  326.             // This is okay because we just return this list to the caller
  327.             @SuppressWarnings("unchecked")
  328.             final List<Object> resultList = (List<Object>) defaultValue;
  329.             return resultList;
  330.         }

  331.         final ListIterator<Object> lit = list.listIterator();
  332.         while (lit.hasNext()) {
  333.             lit.set(interpolate(lit.next()));
  334.         }

  335.         return list;
  336.     }

  337.     /**
  338.      * Gets the number of configurations.
  339.      *
  340.      * @return the number of configuration
  341.      */
  342.     public int getNumberOfConfigurations() {
  343.         return syncRead(configList::size, false);
  344.     }

  345.     /**
  346.      * Reads property from underlying composite
  347.      *
  348.      * @param key key to use for mapping
  349.      * @return object associated with the given configuration key.
  350.      */
  351.     @Override
  352.     protected Object getPropertyInternal(final String key) {
  353.         return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null);
  354.     }

  355.     /**
  356.      * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing
  357.      * child configurations and check whether they contain the specified key. The following constellations are possible:
  358.      * <ul>
  359.      * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration.
  360.      * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li>
  361.      * <li>If none of the child configurations contain the key, <strong>null</strong> is returned.</li>
  362.      * <li>If the key is contained in multiple child configurations or if the key is <strong>null</strong>, a
  363.      * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li>
  364.      * </ul>
  365.      *
  366.      * @param key the key to be checked
  367.      * @return the source configuration of this key
  368.      * @throws IllegalArgumentException if the source configuration cannot be determined
  369.      * @since 1.5
  370.      */
  371.     public Configuration getSource(final String key) {
  372.         if (key == null) {
  373.             throw new IllegalArgumentException("Key must not be null!");
  374.         }

  375.         Configuration source = null;
  376.         for (final Configuration conf : configList) {
  377.             if (conf.containsKey(key)) {
  378.                 if (source != null) {
  379.                     throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
  380.                 }
  381.                 source = conf;
  382.             }
  383.         }

  384.         return source;
  385.     }

  386.     @Override
  387.     public String[] getStringArray(final String key) {
  388.         final List<Object> list = getList(key);

  389.         // transform property values into strings
  390.         final String[] tokens = new String[list.size()];

  391.         for (int i = 0; i < tokens.length; i++) {
  392.             tokens[i] = String.valueOf(list.get(i));
  393.         }

  394.         return tokens;
  395.     }

  396.     @Override
  397.     protected boolean isEmptyInternal() {
  398.         return configList.stream().allMatch(Configuration::isEmpty);
  399.     }

  400.     /**
  401.      * Remove a configuration. The in memory configuration cannot be removed.
  402.      *
  403.      * @param config The configuration to remove
  404.      */
  405.     public void removeConfiguration(final Configuration config) {
  406.         syncWrite(() -> {
  407.             // Make sure that you can't remove the inMemoryConfiguration from
  408.             // the CompositeConfiguration object
  409.             if (!config.equals(inMemoryConfiguration)) {
  410.                 configList.remove(config);
  411.             }
  412.         }, false);
  413.     }

  414.     /**
  415.      * Replaces the current in-memory configuration by the given one.
  416.      *
  417.      * @param config the new in-memory configuration
  418.      */
  419.     private void replaceInMemoryConfiguration(final Configuration config) {
  420.         if (!inMemoryConfigIsChild) {
  421.             // remove current in-memory configuration
  422.             configList.remove(inMemoryConfiguration);
  423.         }
  424.         inMemoryConfiguration = config;
  425.     }

  426.     /**
  427.      * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized.
  428.      */
  429.     @Override
  430.     public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
  431.         if (inMemoryConfiguration instanceof AbstractConfiguration) {
  432.             ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler);
  433.         }
  434.         super.setListDelimiterHandler(listDelimiterHandler);
  435.     }
  436. }