    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    /** List holding all the configuration */
    064    private List<Configuration> configList = new LinkedList<>();
    065
    066    /**
    067     * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added.
    068     */
    069    private Configuration inMemoryConfiguration;
    070
    071    /**
    072     * Stores a flag whether the current in-memory configuration is also a child configuration.
    073     */
    074    private boolean inMemoryConfigIsChild;
    075
    076    /**
    077     * Creates an empty CompositeConfiguration object which can then be added some other Configuration files
    078     */
    079    public CompositeConfiguration() {
    080        clear();
    081    }
    082
    083    /**
    084     * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will
    085     * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special
    086     * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as
    087     * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <b>true</b> instead.
    088     *
    089     * @param inMemoryConfiguration the in memory configuration to use
    090     */
    091    public CompositeConfiguration(final Configuration inMemoryConfiguration) {
    092        configList.clear();
    093        this.inMemoryConfiguration = inMemoryConfiguration;
    094        configList.add(inMemoryConfiguration);
    095    }
    096
    097    /**
    098     * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations
    099     * specified.
    100     *
    101     * @param configurations the collection of configurations to add
    102     */
    103    public CompositeConfiguration(final Collection<? extends Configuration> configurations) {
    104        this(new BaseConfiguration(), configurations);
    105    }
    106
    107    /**
    108     * Creates a CompositeConfiguration with a specified <em>in-memory configuration</em>, and then adds the given
    109     * collection of configurations.
    110     *
    111     * @param inMemoryConfiguration the in memory configuration to use
    112     * @param configurations the collection of configurations to add
    113     * @see #CompositeConfiguration(Configuration)
    114     */
    115    public CompositeConfiguration(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) {
    116        this(inMemoryConfiguration);
    117        if (configurations != null) {
    118            configurations.forEach(this::addConfiguration);
    119        }
    120    }
    121
    122    /**
    123     * Add a configuration.
    124     *
    125     * @param config the configuration to add
    126     */
    127    public void addConfiguration(final Configuration config) {
    128        addConfiguration(config, false);
    129    }
    130
    131    /**
    132     * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future
    133     * property write operations are executed on this configuration. Note that the current in-memory configuration is
    134     * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of
    135     * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes
    136     * its role as in-memory configuration to the new one.
    137     *
    138     * @param config the configuration to be added
    139     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
    140     *        otherwise
    141     * @since 1.8
    142     */
    143    public void addConfiguration(final Configuration config, final boolean asInMemory) {
    144        beginWrite(false);
    145        try {
    146            if (!configList.contains(config)) {
    147                if (asInMemory) {
    148                    replaceInMemoryConfiguration(config);
    149                    inMemoryConfigIsChild = true;
    150                }
    151
    152                if (!inMemoryConfigIsChild) {
    153                    // As the inMemoryConfiguration contains all manually added
    154                    // keys, we must make sure that it is always last. "Normal", non
    155                    // composed configurations add their keys at the end of the
    156                    // configuration and we want to mimic this behavior.
    157                    configList.add(configList.indexOf(inMemoryConfiguration), config);
    158                } else {
    159                    // However, if the in-memory configuration is a regular child,
    160                    // only the order in which child configurations are added is relevant
    161                    configList.add(config);
    162                }
    163
    164                if (config instanceof AbstractConfiguration) {
    165                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    166                }
    167            }
    168        } finally {
    169            endWrite();
    170        }
    171    }
    172
    173    /**
    174     * Add a configuration to the start of the list of child configurations.
    175     *
    176     * @param config the configuration to add
    177     * @since 2.3
    178     */
    179    public void addConfigurationFirst(final Configuration config) {
    180        addConfigurationFirst(config, false);
    181    }
    182
    183    /**
    184     * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory
    185     * configuration</em>. This means that all future property write operations are executed on this configuration. Note
    186     * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the
    187     * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child
    188     * configurations at its current position, but it passes its role as in-memory configuration to the new one.
    189     *
    190     * @param config the configuration to be added
    191     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
    192     *        otherwise
    193     * @since 2.3
    194     */
    195    public void addConfigurationFirst(final Configuration config, final boolean asInMemory) {
    196        beginWrite(false);
    197        try {
    198            if (!configList.contains(config)) {
    199                if (asInMemory) {
    200                    replaceInMemoryConfiguration(config);
    201                    inMemoryConfigIsChild = true;
    202                }
    203                configList.add(0, config);
    204
    205                if (config instanceof AbstractConfiguration) {
    206                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    207                }
    208            }
    209        } finally {
    210            endWrite();
    211        }
    212    }
    213
    214    /**
    215     * Remove a configuration. The in memory configuration cannot be removed.
    216     *
    217     * @param config The configuration to remove
    218     */
    219    public void removeConfiguration(final Configuration config) {
    220        beginWrite(false);
    221        try {
    222            // Make sure that you can't remove the inMemoryConfiguration from
    223            // the CompositeConfiguration object
    224            if (!config.equals(inMemoryConfiguration)) {
    225                configList.remove(config);
    226            }
    227        } finally {
    228            endWrite();
    229        }
    230    }
    231
    232    /**
    233     * Gets the number of configurations.
    234     *
    235     * @return the number of configuration
    236     */
    237    public int getNumberOfConfigurations() {
    238        beginRead(false);
    239        try {
    240            return configList.size();
    241        } finally {
    242            endRead();
    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    /**
    262     * Add this property to the in-memory Configuration.
    263     *
    264     * @param key The Key to add the property to.
    265     * @param token The Value to add.
    266     */
    267    @Override
    268    protected void addPropertyDirect(final String key, final Object token) {
    269        inMemoryConfiguration.addProperty(key, token);
    270    }
    271
    272    /**
    273     * Read property from underlying composite
    274     *
    275     * @param key key to use for mapping
    276     *
    277     * @return object associated with the given configuration key.
    278     */
    279    @Override
    280    protected Object getPropertyInternal(final String key) {
    281        return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null);
    282    }
    283
    284    @Override
    285    protected Iterator<String> getKeysInternal() {
    286        final Set<String> keys = new LinkedHashSet<>();
    287        configList.forEach(config -> config.getKeys().forEachRemaining(keys::add));
    288        return keys.iterator();
    289    }
    290
    291    @Override
    292    protected Iterator<String> getKeysInternal(final String key) {
    293        final Set<String> keys = new LinkedHashSet<>();
    294        configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add));
    295        return keys.iterator();
    296    }
    297
    298    @Override
    299    protected boolean isEmptyInternal() {
    300        return configList.stream().allMatch(Configuration::isEmpty);
    301    }
    302
    303    @Override
    304    protected void clearPropertyDirect(final String key) {
    305        configList.forEach(config -> config.clearProperty(key));
    306    }
    307
    308    @Override
    309    protected boolean containsKeyInternal(final String key) {
    310        return configList.stream().anyMatch(config -> config.containsKey(key));
    311    }
    312
    313    @Override
    314    public List<Object> getList(final String key, final List<?> defaultValue) {
    315        final List<Object> list = new ArrayList<>();
    316
    317        // add all elements from the first configuration containing the requested key
    318        final Iterator<Configuration> it = configList.iterator();
    319        while (it.hasNext() && list.isEmpty()) {
    320            final Configuration config = it.next();
    321            if (config != inMemoryConfiguration && config.containsKey(key)) {
    322                appendListProperty(list, config, key);
    323            }
    324        }
    325
    326        // add all elements from the in memory configuration
    327        appendListProperty(list, inMemoryConfiguration, key);
    328
    329        if (list.isEmpty()) {
    330            // This is okay because we just return this list to the caller
    331            @SuppressWarnings("unchecked")
    332            final List<Object> resultList = (List<Object>) defaultValue;
    333            return resultList;
    334        }
    335
    336        final ListIterator<Object> lit = list.listIterator();
    337        while (lit.hasNext()) {
    338            lit.set(interpolate(lit.next()));
    339        }
    340
    341        return list;
    342    }
    343
    344    @Override
    345    public String[] getStringArray(final String key) {
    346        final List<Object> list = getList(key);
    347
    348        // transform property values into strings
    349        final String[] tokens = new String[list.size()];
    350
    351        for (int i = 0; i < tokens.length; i++) {
    352            tokens[i] = String.valueOf(list.get(i));
    353        }
    354
    355        return tokens;
    356    }
    357
    358    /**
    359     * Gets the configuration at the specified index.
    360     *
    361     * @param index The index of the configuration to retrieve
    362     * @return the configuration at this index
    363     */
    364    public Configuration getConfiguration(final int index) {
    365        beginRead(false);
    366        try {
    367            return configList.get(index);
    368        } finally {
    369            endRead();
    370        }
    371    }
    372
    373    /**
    374     * Gets the &quot;in memory configuration&quot;. In this configuration changes are stored.
    375     *
    376     * @return the in memory configuration
    377     */
    378    public Configuration getInMemoryConfiguration() {
    379        beginRead(false);
    380        try {
    381            return inMemoryConfiguration;
    382        } finally {
    383            endRead();
    384        }
    385    }
    386
    387    /**
    388     * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in
    389     * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a
    390     * runtime exception will be thrown. Registered event handlers won't get cloned.
    391     *
    392     * @return the copy
    393     * @since 1.3
    394     */
    395    @Override
    396    public Object clone() {
    397        try {
    398            final CompositeConfiguration copy = (CompositeConfiguration) super.clone();
    399            copy.configList = new LinkedList<>();
    400            copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration());
    401            copy.configList.add(copy.inMemoryConfiguration);
    402
    403            configList.forEach(config -> {
    404                if (config != getInMemoryConfiguration()) {
    405                    copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config));
    406                }
    407            });
    408
    409            copy.cloneInterpolator(this);
    410            return copy;
    411        } catch (final CloneNotSupportedException cnex) {
    412            // cannot happen
    413            throw new ConfigurationRuntimeException(cnex);
    414        }
    415    }
    416
    417    /**
    418     * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized.
    419     */
    420    @Override
    421    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
    422        if (inMemoryConfiguration instanceof AbstractConfiguration) {
    423            ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler);
    424        }
    425        super.setListDelimiterHandler(listDelimiterHandler);
    426    }
    427
    428    /**
    429     * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing
    430     * child configurations and check whether they contain the specified key. The following constellations are possible:
    431     * <ul>
    432     * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration.
    433     * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li>
    434     * <li>If none of the child configurations contain the key, <b>null</b> is returned.</li>
    435     * <li>If the key is contained in multiple child configurations or if the key is <b>null</b>, a
    436     * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li>
    437     * </ul>
    438     *
    439     * @param key the key to be checked
    440     * @return the source configuration of this key
    441     * @throws IllegalArgumentException if the source configuration cannot be determined
    442     * @since 1.5
    443     */
    444    public Configuration getSource(final String key) {
    445        if (key == null) {
    446            throw new IllegalArgumentException("Key must not be null!");
    447        }
    448
    449        Configuration source = null;
    450        for (final Configuration conf : configList) {
    451            if (conf.containsKey(key)) {
    452                if (source != null) {
    453                    throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
    454                }
    455                source = conf;
    456            }
    457        }
    458
    459        return source;
    460    }
    461
    462    /**
    463     * Replaces the current in-memory configuration by the given one.
    464     *
    465     * @param config the new in-memory configuration
    466     */
    467    private void replaceInMemoryConfiguration(final Configuration config) {
    468        if (!inMemoryConfigIsChild) {
    469            // remove current in-memory configuration
    470            configList.remove(inMemoryConfiguration);
    471        }
    472        inMemoryConfiguration = config;
    473    }
    474
    475    /**
    476     * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property
    477     * values from the child configurations.
    478     *
    479     * @param dest the list for collecting the data
    480     * @param config the configuration to query
    481     * @param key the key of the property
    482     */
    483    private void appendListProperty(final List<Object> dest, final Configuration config, final String key) {
    484        final Object value = interpolate(config.getProperty(key));
    485        if (value != null) {
    486            if (value instanceof Collection) {
    487                final Collection<?> col = (Collection<?>) value;
    488                dest.addAll(col);
    489            } else {
    490                dest.add(value);
    491            }
    492        }
    493    }
    494}