    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2;
    019
    020import java.util.ArrayList;
    021import java.util.Collection;
    022import java.util.Iterator;
    023import java.util.LinkedHashSet;
    024import java.util.LinkedList;
    025import java.util.List;
    026import java.util.ListIterator;
    027import java.util.Set;
    028
    029import org.apache.commons.configuration2.convert.ListDelimiterHandler;
    030import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
    031
    032/**
    033 * <p>
    034 * {@code CompositeConfiguration} allows you to add multiple {@code Configuration} objects to an aggregated
    035 * configuration. If you add Configuration1, and then Configuration2, any properties shared will mean that the value
    036 * defined by Configuration1 will be returned. If Configuration1 doesn't have the property, then Configuration2 will be
    037 * checked. You can add multiple different types or the same type of properties file.
    038 * </p>
    039 * <p>
    040 * When querying properties the order in which child configurations have been added is relevant. To deal with property
    041 * updates, a so-called <em>in-memory configuration</em> is used. Per default, such a configuration is created
    042 * automatically. All property writes target this special configuration. There are constructors which allow you to
    043 * provide a specific in-memory configuration. If used that way, the in-memory configuration is always the last one in
    044 * the list of child configurations. This means that for query operations all other configurations take precedence.
    045 * </p>
    046 * <p>
    047 * Alternatively it is possible to mark a child configuration as in-memory configuration when it is added. In this case
    048 * the treatment of the in-memory configuration is slightly different: it remains in the list of child configurations at
    049 * the position it was added, i.e. its priority for property queries can be defined by adding the child configurations
    050 * in the correct order.
    051 * </p>
    052 * <p>
    053 * This configuration class uses a {@code Synchronizer} to control concurrent access. While all methods for reading and
    054 * writing configuration properties make use of this {@code Synchronizer} per default, the methods for managing the list
    055 * of child configurations and the in-memory configuration
    056 * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
    057 * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because most methods for accessing configuration
    058 * data delegate to the list of child configurations, the thread-safety of a {@code CompositeConfiguration} object also
    059 * depends on the {@code Synchronizer} objects used by these children.
    060 * </p>
    061 */
    062public class CompositeConfiguration extends AbstractConfiguration implements Cloneable {
    063
    064    /** List holding all the configuration */
    065    private List<Configuration> configList = new LinkedList<>();
    066
    067    /**
    068     * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added.
    069     */
    070    private Configuration inMemoryConfiguration;
    071
    072    /**
    073     * Stores a flag whether the current in-memory configuration is also a child configuration.
    074     */
    075    private boolean inMemoryConfigIsChild;
    076
    077    /**
    078     * Creates an empty CompositeConfiguration object which can then be added some other Configuration files
    079     */
    080    public CompositeConfiguration() {
    081        clear();
    082    }
    083
    084    /**
    085     * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations
    086     * specified.
    087     *
    088     * @param configurations the collection of configurations to add
    089     */
    090    public CompositeConfiguration(final Collection<? extends Configuration> configurations) {
    091        this(new BaseConfiguration(), configurations);
    092    }
    093
    094    /**
    095     * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will
    096     * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special
    097     * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as
    098     * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <b>true</b> instead.
    099     *
    100     * @param inMemoryConfiguration the in memory configuration to use
    101     */
    102    public CompositeConfiguration(final Configuration inMemoryConfiguration) {
    103        this.configList.clear();
    104        this.inMemoryConfiguration = inMemoryConfiguration;
    105        this.configList.add(inMemoryConfiguration);
    106    }
    107
    108    /**
    109     * Creates a CompositeConfiguration with a specified <em>in-memory configuration</em>, and then adds the given
    110     * collection of configurations.
    111     *
    112     * @param inMemoryConfiguration the in memory configuration to use
    113     * @param configurations the collection of configurations to add
    114     * @see #CompositeConfiguration(Configuration)
    115     */
    116    public CompositeConfiguration(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) {
    117        this(inMemoryConfiguration);
    118        if (configurations != null) {
    119            configurations.forEach(this::addConfiguration);
    120        }
    121    }
    122
    123    /**
    124     * Add a configuration.
    125     *
    126     * @param config the configuration to add
    127     */
    128    public void addConfiguration(final Configuration config) {
    129        addConfiguration(config, false);
    130    }
    131
    132    /**
    133     * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future
    134     * property write operations are executed on this configuration. Note that the current in-memory configuration is
    135     * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of
    136     * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes
    137     * its role as in-memory configuration to the new one.
    138     *
    139     * @param config the configuration to be added
    140     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
    141     *        otherwise
    142     * @since 1.8
    143     */
    144    public void addConfiguration(final Configuration config, final boolean asInMemory) {
    145        beginWrite(false);
    146        try {
    147            if (!configList.contains(config)) {
    148                if (asInMemory) {
    149                    replaceInMemoryConfiguration(config);
    150                    inMemoryConfigIsChild = true;
    151                }
    152
    153                if (!inMemoryConfigIsChild) {
    154                    // As the inMemoryConfiguration contains all manually added
    155                    // keys, we must make sure that it is always last. "Normal", non
    156                    // composed configurations add their keys at the end of the
    157                    // configuration and we want to mimic this behavior.
    158                    configList.add(configList.indexOf(inMemoryConfiguration), config);
    159                } else {
    160                    // However, if the in-memory configuration is a regular child,
    161                    // only the order in which child configurations are added is relevant
    162                    configList.add(config);
    163                }
    164
    165                if (config instanceof AbstractConfiguration) {
    166                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    167                }
    168            }
    169        } finally {
    170            endWrite();
    171        }
    172    }
    173
    174    /**
    175     * Add a configuration to the start of the list of child configurations.
    176     *
    177     * @param config the configuration to add
    178     * @since 2.3
    179     */
    180    public void addConfigurationFirst(final Configuration config) {
    181        addConfigurationFirst(config, false);
    182    }
    183
    184    /**
    185     * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory
    186     * configuration</em>. This means that all future property write operations are executed on this configuration. Note
    187     * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the
    188     * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child
    189     * configurations at its current position, but it passes its role as in-memory configuration to the new one.
    190     *
    191     * @param config the configuration to be added
    192     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
    193     *        otherwise
    194     * @since 2.3
    195     */
    196    public void addConfigurationFirst(final Configuration config, final boolean asInMemory) {
    197        beginWrite(false);
    198        try {
    199            if (!configList.contains(config)) {
    200                if (asInMemory) {
    201                    replaceInMemoryConfiguration(config);
    202                    inMemoryConfigIsChild = true;
    203                }
    204                configList.add(0, config);
    205
    206                if (config instanceof AbstractConfiguration) {
    207                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    208                }
    209            }
    210        } finally {
    211            endWrite();
    212        }
    213    }
    214
    215    /**
    216     * Add this property to the in-memory Configuration.
    217     *
    218     * @param key The Key to add the property to.
    219     * @param token The Value to add.
    220     */
    221    @Override
    222    protected void addPropertyDirect(final String key, final Object token) {
    223        inMemoryConfiguration.addProperty(key, token);
    224    }
    225
    226    /**
    227     * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property
    228     * values from the child configurations.
    229     *
    230     * @param dest the list for collecting the data
    231     * @param config the configuration to query
    232     * @param key the key of the property
    233     */
    234    private void appendListProperty(final List<Object> dest, final Configuration config, final String key) {
    235        final Object value = interpolate(config.getProperty(key));
    236        if (value != null) {
    237            if (value instanceof Collection) {
    238                final Collection<?> col = (Collection<?>) value;
    239                dest.addAll(col);
    240            } else {
    241                dest.add(value);
    242            }
    243        }
    244    }
    245
    246    /**
    247     * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong>
    248     * A new in-memory configuration is created; the old one is lost.
    249     */
    250    @Override
    251    protected void clearInternal() {
    252        configList.clear();
    253        // recreate the in memory configuration
    254        inMemoryConfiguration = new BaseConfiguration();
    255        ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    256        ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
    257        configList.add(inMemoryConfiguration);
    258        inMemoryConfigIsChild = false;
    259    }
    260
    261    @Override
    262    protected void clearPropertyDirect(final String key) {
    263        configList.forEach(config -> config.clearProperty(key));
    264    }
    265
    266    /**
    267     * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in
    268     * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a
    269     * runtime exception will be thrown. Registered event handlers won't get cloned.
    270     *
    271     * @return the copy
    272     * @since 1.3
    273     */
    274    @Override
    275    public Object clone() {
    276        try {
    277            final CompositeConfiguration copy = (CompositeConfiguration) super.clone();
    278            copy.configList = new LinkedList<>();
    279            copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration());
    280            copy.configList.add(copy.inMemoryConfiguration);
    281
    282            configList.forEach(config -> {
    283                if (config != getInMemoryConfiguration()) {
    284                    copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config));
    285                }
    286            });
    287
    288            copy.cloneInterpolator(this);
    289            return copy;
    290        } catch (final CloneNotSupportedException cnex) {
    291            // cannot happen
    292            throw new ConfigurationRuntimeException(cnex);
    293        }
    294    }
    295
    296    @Override
    297    protected boolean containsKeyInternal(final String key) {
    298        return configList.stream().anyMatch(config -> config.containsKey(key));
    299    }
    300
    301    /**
    302     * Tests whether this configuration contains one or more matches to this value. This operation stops at first
    303     * match but may be more expensive than the containsKey method.
    304     * @since 2.11.0
    305     */
    306    @Override
    307    protected boolean containsValueInternal(final Object value) {
    308        return configList.stream().anyMatch(config -> config.containsValue(value));
    309    }
    310
    311    /**
    312     * Gets the configuration at the specified index.
    313     *
    314     * @param index The index of the configuration to retrieve
    315     * @return the configuration at this index
    316     */
    317    public Configuration getConfiguration(final int index) {
    318        beginRead(false);
    319        try {
    320            return configList.get(index);
    321        } finally {
    322            endRead();
    323        }
    324    }
    325
    326    /**
    327     * Gets the &quot;in memory configuration&quot;. In this configuration changes are stored.
    328     *
    329     * @return the in memory configuration
    330     */
    331    public Configuration getInMemoryConfiguration() {
    332        beginRead(false);
    333        try {
    334            return inMemoryConfiguration;
    335        } finally {
    336            endRead();
    337        }
    338    }
    339
    340    @Override
    341    protected Iterator<String> getKeysInternal() {
    342        final Set<String> keys = new LinkedHashSet<>();
    343        configList.forEach(config -> config.getKeys().forEachRemaining(keys::add));
    344        return keys.iterator();
    345    }
    346
    347    @Override
    348    protected Iterator<String> getKeysInternal(final String key) {
    349        final Set<String> keys = new LinkedHashSet<>();
    350        configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add));
    351        return keys.iterator();
    352    }
    353
    354    @Override
    355    public List<Object> getList(final String key, final List<?> defaultValue) {
    356        final List<Object> list = new ArrayList<>();
    357
    358        // add all elements from the first configuration containing the requested key
    359        final Iterator<Configuration> it = configList.iterator();
    360        while (it.hasNext() && list.isEmpty()) {
    361            final Configuration config = it.next();
    362            if (config != inMemoryConfiguration && config.containsKey(key)) {
    363                appendListProperty(list, config, key);
    364            }
    365        }
    366
    367        // add all elements from the in memory configuration
    368        appendListProperty(list, inMemoryConfiguration, key);
    369
    370        if (list.isEmpty()) {
    371            // This is okay because we just return this list to the caller
    372            @SuppressWarnings("unchecked")
    373            final List<Object> resultList = (List<Object>) defaultValue;
    374            return resultList;
    375        }
    376
    377        final ListIterator<Object> lit = list.listIterator();
    378        while (lit.hasNext()) {
    379            lit.set(interpolate(lit.next()));
    380        }
    381
    382        return list;
    383    }
    384
    385    /**
    386     * Gets the number of configurations.
    387     *
    388     * @return the number of configuration
    389     */
    390    public int getNumberOfConfigurations() {
    391        beginRead(false);
    392        try {
    393            return configList.size();
    394        } finally {
    395            endRead();
    396        }
    397    }
    398
    399    /**
    400     * Read property from underlying composite
    401     *
    402     * @param key key to use for mapping
    403     *
    404     * @return object associated with the given configuration key.
    405     */
    406    @Override
    407    protected Object getPropertyInternal(final String key) {
    408        return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null);
    409    }
    410
    411    /**
    412     * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing
    413     * child configurations and check whether they contain the specified key. The following constellations are possible:
    414     * <ul>
    415     * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration.
    416     * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li>
    417     * <li>If none of the child configurations contain the key, <b>null</b> is returned.</li>
    418     * <li>If the key is contained in multiple child configurations or if the key is <b>null</b>, a
    419     * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li>
    420     * </ul>
    421     *
    422     * @param key the key to be checked
    423     * @return the source configuration of this key
    424     * @throws IllegalArgumentException if the source configuration cannot be determined
    425     * @since 1.5
    426     */
    427    public Configuration getSource(final String key) {
    428        if (key == null) {
    429            throw new IllegalArgumentException("Key must not be null!");
    430        }
    431
    432        Configuration source = null;
    433        for (final Configuration conf : configList) {
    434            if (conf.containsKey(key)) {
    435                if (source != null) {
    436                    throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
    437                }
    438                source = conf;
    439            }
    440        }
    441
    442        return source;
    443    }
    444
    445    @Override
    446    public String[] getStringArray(final String key) {
    447        final List<Object> list = getList(key);
    448
    449        // transform property values into strings
    450        final String[] tokens = new String[list.size()];
    451
    452        for (int i = 0; i < tokens.length; i++) {
    453            tokens[i] = String.valueOf(list.get(i));
    454        }
    455
    456        return tokens;
    457    }
    458
    459    @Override
    460    protected boolean isEmptyInternal() {
    461        return configList.stream().allMatch(Configuration::isEmpty);
    462    }
    463
    464    /**
    465     * Remove a configuration. The in memory configuration cannot be removed.
    466     *
    467     * @param config The configuration to remove
    468     */
    469    public void removeConfiguration(final Configuration config) {
    470        beginWrite(false);
    471        try {
    472            // Make sure that you can't remove the inMemoryConfiguration from
    473            // the CompositeConfiguration object
    474            if (!config.equals(inMemoryConfiguration)) {
    475                configList.remove(config);
    476            }
    477        } finally {
    478            endWrite();
    479        }
    480    }
    481
    482    /**
    483     * Replaces the current in-memory configuration by the given one.
    484     *
    485     * @param config the new in-memory configuration
    486     */
    487    private void replaceInMemoryConfiguration(final Configuration config) {
    488        if (!inMemoryConfigIsChild) {
    489            // remove current in-memory configuration
    490            configList.remove(inMemoryConfiguration);
    491        }
    492        inMemoryConfiguration = config;
    493    }
    494
    495    /**
    496     * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized.
    497     */
    498    @Override
    499    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
    500        if (inMemoryConfiguration instanceof AbstractConfiguration) {
    501            ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler);
    502        }
    503        super.setListDelimiterHandler(listDelimiterHandler);
    504    }
    505}