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