    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>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
    034 * objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
    035 * any properties shared will mean that the value defined by Configuration1
    036 * will be returned. If Configuration1 doesn't have the property, then
    037 * Configuration2 will be checked. You can add multiple different types or the
    038 * same type of properties file.</p>
    039 * <p>When querying properties the order in which child configurations have been
    040 * added is relevant. To deal with property updates, a so-called <em>in-memory
    041 * configuration</em> is used. Per default, such a configuration is created
    042 * automatically. All property writes target this special configuration. There
    043 * are constructors which allow you to provide a specific in-memory configuration.
    044 * If used that way, the in-memory configuration is always the last one in the
    045 * list of child configurations. This means that for query operations all other
    046 * configurations take precedence.</p>
    047 * <p>Alternatively it is possible to mark a child configuration as in-memory
    048 * configuration when it is added. In this case the treatment of the in-memory
    049 * configuration is slightly different: it remains in the list of child
    050 * configurations at the position it was added, i.e. its priority for property
    051 * queries can be defined by adding the child configurations in the correct
    052 * order.</p>
    053 * <p>
    054 * This configuration class uses a {@code Synchronizer} to control concurrent
    055 * access. While all methods for reading and writing configuration properties
    056 * make use of this {@code Synchronizer} per default, the methods for managing
    057 * the list of child configurations and the in-memory configuration
    058 * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
    059 * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because
    060 * most methods for accessing configuration data delegate to the list of child
    061 * configurations, the thread-safety of a {@code CompositeConfiguration}
    062 * object also depends on the {@code Synchronizer} objects used by these
    063 * children.
    064 * </p>
    065 *
    066 */
    067public class CompositeConfiguration extends AbstractConfiguration
    068implements Cloneable
    069{
    070    /** List holding all the configuration */
    071    private List<Configuration> configList = new LinkedList<>();
    072
    073    /**
    074     * Configuration that holds in memory stuff.  Inserted as first so any
    075     * setProperty() override anything else added.
    076     */
    077    private Configuration inMemoryConfiguration;
    078
    079    /**
    080     * Stores a flag whether the current in-memory configuration is also a
    081     * child configuration.
    082     */
    083    private boolean inMemoryConfigIsChild;
    084
    085    /**
    086     * Creates an empty CompositeConfiguration object which can then
    087     * be added some other Configuration files
    088     */
    089    public CompositeConfiguration()
    090    {
    091        clear();
    092    }
    093
    094    /**
    095     * Creates a CompositeConfiguration object with a specified <em>in-memory
    096     * configuration</em>. This configuration will store any changes made to the
    097     * {@code CompositeConfiguration}. Note: Use this constructor if you want to
    098     * set a special type of in-memory configuration. If you have a
    099     * configuration which should act as both a child configuration and as
    100     * in-memory configuration, use
    101     * {@link #addConfiguration(Configuration, boolean)} with a value of
    102     * <b>true</b> instead.
    103     *
    104     * @param inMemoryConfiguration the in memory configuration to use
    105     */
    106    public CompositeConfiguration(final Configuration inMemoryConfiguration)
    107    {
    108        configList.clear();
    109        this.inMemoryConfiguration = inMemoryConfiguration;
    110        configList.add(inMemoryConfiguration);
    111    }
    112
    113    /**
    114     * Create a CompositeConfiguration with an empty in memory configuration
    115     * and adds the collection of configurations specified.
    116     *
    117     * @param configurations the collection of configurations to add
    118     */
    119    public CompositeConfiguration(final Collection<? extends Configuration> configurations)
    120    {
    121        this(new BaseConfiguration(), configurations);
    122    }
    123
    124    /**
    125     * Creates a CompositeConfiguration with a specified <em>in-memory
    126     * configuration</em>, and then adds the given collection of configurations.
    127     *
    128     * @param inMemoryConfiguration the in memory configuration to use
    129     * @param configurations        the collection of configurations to add
    130     * @see #CompositeConfiguration(Configuration)
    131     */
    132    public CompositeConfiguration(final Configuration inMemoryConfiguration,
    133            final Collection<? extends Configuration> configurations)
    134    {
    135        this(inMemoryConfiguration);
    136
    137        if (configurations != null)
    138        {
    139            for (final Configuration c : configurations)
    140            {
    141                addConfiguration(c);
    142            }
    143        }
    144    }
    145
    146    /**
    147     * Add a configuration.
    148     *
    149     * @param config the configuration to add
    150     */
    151    public void addConfiguration(final Configuration config)
    152    {
    153        addConfiguration(config, false);
    154    }
    155
    156    /**
    157     * Adds a child configuration and optionally makes it the <em>in-memory
    158     * configuration</em>. This means that all future property write operations
    159     * are executed on this configuration. Note that the current in-memory
    160     * configuration is replaced by the new one. If it was created automatically
    161     * or passed to the constructor, it is removed from the list of child
    162     * configurations! Otherwise, it stays in the list of child configurations
    163     * at its current position, but it passes its role as in-memory
    164     * configuration to the new one.
    165     *
    166     * @param config the configuration to be added
    167     * @param asInMemory <b>true</b> if this configuration becomes the new
    168     *        <em>in-memory</em> configuration, <b>false</b> otherwise
    169     * @since 1.8
    170     */
    171    public void addConfiguration(final Configuration config, final boolean asInMemory)
    172    {
    173        beginWrite(false);
    174        try
    175        {
    176            if (!configList.contains(config))
    177            {
    178                if (asInMemory)
    179                {
    180                    replaceInMemoryConfiguration(config);
    181                    inMemoryConfigIsChild = true;
    182                }
    183
    184                if (!inMemoryConfigIsChild)
    185                {
    186                    // As the inMemoryConfiguration contains all manually added
    187                    // keys, we must make sure that it is always last. "Normal", non
    188                    // composed configurations add their keys at the end of the
    189                    // configuration and we want to mimic this behavior.
    190                    configList.add(configList.indexOf(inMemoryConfiguration),
    191                            config);
    192                }
    193                else
    194                {
    195                    // However, if the in-memory configuration is a regular child,
    196                    // only the order in which child configurations are added is relevant
    197                    configList.add(config);
    198                }
    199
    200                if (config instanceof AbstractConfiguration)
    201                {
    202                    ((AbstractConfiguration) config)
    203                            .setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    204                }
    205            }
    206        }
    207        finally
    208        {
    209            endWrite();
    210        }
    211    }
    212
    213    /**
    214     * Add a configuration to the start of the list of child configurations.
    215     *
    216     * @param config the configuration to add
    217     * @since 2.3
    218     */
    219    public void addConfigurationFirst(final Configuration config)
    220    {
    221        addConfigurationFirst(config, false);
    222    }
    223
    224    /**
    225     * Adds a child configuration to the start of the collection and optionally
    226     * makes it the <em>in-memory configuration</em>. This means that all future
    227     * property write operations are executed on this configuration. Note that
    228     * the current in-memory configuration is replaced by the new one. If it was
    229     * created automatically or passed to the constructor, it is removed from the
    230     * list of child configurations! Otherwise, it stays in the list of child configurations
    231     * at its current position, but it passes its role as in-memory configuration to the new one.
    232     *
    233     * @param config the configuration to be added
    234     * @param asInMemory <b>true</b> if this configuration becomes the new
    235     *        <em>in-memory</em> configuration, <b>false</b> otherwise
    236     * @since 2.3
    237     */
    238    public void addConfigurationFirst(final Configuration config, final boolean asInMemory)
    239    {
    240        beginWrite(false);
    241        try
    242        {
    243            if (!configList.contains(config))
    244            {
    245                if (asInMemory)
    246                {
    247                    replaceInMemoryConfiguration(config);
    248                    inMemoryConfigIsChild = true;
    249                }
    250                configList.add(0, config);
    251
    252                if (config instanceof AbstractConfiguration)
    253                {
    254                    ((AbstractConfiguration) config)
    255                            .setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    256                }
    257            }
    258        }
    259        finally
    260        {
    261            endWrite();
    262        }
    263    }
    264
    265    /**
    266     * Remove a configuration. The in memory configuration cannot be removed.
    267     *
    268     * @param config The configuration to remove
    269     */
    270    public void removeConfiguration(final Configuration config)
    271    {
    272        beginWrite(false);
    273        try
    274        {
    275            // Make sure that you can't remove the inMemoryConfiguration from
    276            // the CompositeConfiguration object
    277            if (!config.equals(inMemoryConfiguration))
    278            {
    279                configList.remove(config);
    280            }
    281        }
    282        finally
    283        {
    284            endWrite();
    285        }
    286    }
    287
    288    /**
    289     * Return the number of configurations.
    290     *
    291     * @return the number of configuration
    292     */
    293    public int getNumberOfConfigurations()
    294    {
    295        beginRead(false);
    296        try
    297        {
    298            return configList.size();
    299        }
    300        finally
    301        {
    302            endRead();
    303        }
    304    }
    305
    306    /**
    307     * Removes all child configurations and reinitializes the <em>in-memory
    308     * configuration</em>. <strong>Attention:</strong> A new in-memory
    309     * configuration is created; the old one is lost.
    310     */
    311    @Override
    312    protected void clearInternal()
    313    {
    314        configList.clear();
    315        // recreate the in memory configuration
    316        inMemoryConfiguration = new BaseConfiguration();
    317        ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
    318        ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
    319        configList.add(inMemoryConfiguration);
    320        inMemoryConfigIsChild = false;
    321    }
    322
    323    /**
    324     * Add this property to the in-memory Configuration.
    325     *
    326     * @param key The Key to add the property to.
    327     * @param token The Value to add.
    328     */
    329    @Override
    330    protected void addPropertyDirect(final String key, final Object token)
    331    {
    332        inMemoryConfiguration.addProperty(key, token);
    333    }
    334
    335    /**
    336     * Read property from underlying composite
    337     *
    338     * @param key key to use for mapping
    339     *
    340     * @return object associated with the given configuration key.
    341     */
    342    @Override
    343    protected Object getPropertyInternal(final String key)
    344    {
    345        Configuration firstMatchingConfiguration = null;
    346        for (final Configuration config : configList)
    347        {
    348            if (config.containsKey(key))
    349            {
    350                firstMatchingConfiguration = config;
    351                break;
    352            }
    353        }
    354
    355        if (firstMatchingConfiguration != null)
    356        {
    357            return firstMatchingConfiguration.getProperty(key);
    358        }
    359        return null;
    360    }
    361
    362    @Override
    363    protected Iterator<String> getKeysInternal()
    364    {
    365        final Set<String> keys = new LinkedHashSet<>();
    366        for (final Configuration config : configList)
    367        {
    368            for (final Iterator<String> it = config.getKeys(); it.hasNext();)
    369            {
    370                keys.add(it.next());
    371            }
    372        }
    373
    374        return keys.iterator();
    375    }
    376
    377    @Override
    378    protected Iterator<String> getKeysInternal(final String key)
    379    {
    380        final Set<String> keys = new LinkedHashSet<>();
    381        for (final Configuration config : configList)
    382        {
    383            for (final Iterator<String> it = config.getKeys(key); it.hasNext();)
    384            {
    385                keys.add(it.next());
    386            }
    387        }
    388
    389        return keys.iterator();
    390    }
    391
    392    @Override
    393    protected boolean isEmptyInternal()
    394    {
    395        for (final Configuration config : configList)
    396        {
    397            if (!config.isEmpty())
    398            {
    399                return false;
    400            }
    401        }
    402
    403        return true;
    404    }
    405
    406    @Override
    407    protected void clearPropertyDirect(final String key)
    408    {
    409        for (final Configuration config : configList)
    410        {
    411            config.clearProperty(key);
    412        }
    413    }
    414
    415    @Override
    416    protected boolean containsKeyInternal(final String key)
    417    {
    418        for (final Configuration config : configList)
    419        {
    420            if (config.containsKey(key))
    421            {
    422                return true;
    423            }
    424        }
    425        return false;
    426    }
    427
    428    @Override
    429    public List<Object> getList(final String key, final List<?> defaultValue)
    430    {
    431        final List<Object> list = new ArrayList<>();
    432
    433        // add all elements from the first configuration containing the requested key
    434        final Iterator<Configuration> it = configList.iterator();
    435        while (it.hasNext() && list.isEmpty())
    436        {
    437            final Configuration config = it.next();
    438            if (config != inMemoryConfiguration && config.containsKey(key))
    439            {
    440                appendListProperty(list, config, key);
    441            }
    442        }
    443
    444        // add all elements from the in memory configuration
    445        appendListProperty(list, inMemoryConfiguration, key);
    446
    447        if (list.isEmpty())
    448        {
    449            // This is okay because we just return this list to the caller
    450            @SuppressWarnings("unchecked")
    451            final
    452            List<Object> resultList = (List<Object>) defaultValue;
    453            return resultList;
    454        }
    455
    456        final ListIterator<Object> lit = list.listIterator();
    457        while (lit.hasNext())
    458        {
    459            lit.set(interpolate(lit.next()));
    460        }
    461
    462        return list;
    463    }
    464
    465    @Override
    466    public String[] getStringArray(final String key)
    467    {
    468        final List<Object> list = getList(key);
    469
    470        // transform property values into strings
    471        final String[] tokens = new String[list.size()];
    472
    473        for (int i = 0; i < tokens.length; i++)
    474        {
    475            tokens[i] = String.valueOf(list.get(i));
    476        }
    477
    478        return tokens;
    479    }
    480
    481    /**
    482     * Return the configuration at the specified index.
    483     *
    484     * @param index The index of the configuration to retrieve
    485     * @return the configuration at this index
    486     */
    487    public Configuration getConfiguration(final int index)
    488    {
    489        beginRead(false);
    490        try
    491        {
    492            return configList.get(index);
    493        }
    494        finally
    495        {
    496            endRead();
    497        }
    498    }
    499
    500    /**
    501     * Returns the &quot;in memory configuration&quot;. In this configuration
    502     * changes are stored.
    503     *
    504     * @return the in memory configuration
    505     */
    506    public Configuration getInMemoryConfiguration()
    507    {
    508        beginRead(false);
    509        try
    510        {
    511            return inMemoryConfiguration;
    512        }
    513        finally
    514        {
    515            endRead();
    516        }
    517    }
    518
    519    /**
    520     * Returns a copy of this object. This implementation will create a deep
    521     * clone, i.e. all configurations contained in this composite will also be
    522     * cloned. This only works if all contained configurations support cloning;
    523     * otherwise a runtime exception will be thrown. Registered event handlers
    524     * won't get cloned.
    525     *
    526     * @return the copy
    527     * @since 1.3
    528     */
    529    @Override
    530    public Object clone()
    531    {
    532        try
    533        {
    534            final CompositeConfiguration copy = (CompositeConfiguration) super
    535                    .clone();
    536            copy.configList = new LinkedList<>();
    537            copy.inMemoryConfiguration = ConfigurationUtils
    538                    .cloneConfiguration(getInMemoryConfiguration());
    539            copy.configList.add(copy.inMemoryConfiguration);
    540
    541            for (final Configuration config : configList)
    542            {
    543                if (config != getInMemoryConfiguration())
    544                {
    545                    copy.addConfiguration(ConfigurationUtils
    546                            .cloneConfiguration(config));
    547                }
    548            }
    549
    550            copy.cloneInterpolator(this);
    551            return copy;
    552        }
    553        catch (final CloneNotSupportedException cnex)
    554        {
    555            // cannot happen
    556            throw new ConfigurationRuntimeException(cnex);
    557        }
    558    }
    559
    560    /**
    561     * {@inheritDoc} This implementation ensures that the in memory
    562     * configuration is correctly initialized.
    563     */
    564    @Override
    565    public void setListDelimiterHandler(
    566            final ListDelimiterHandler listDelimiterHandler)
    567    {
    568        if (inMemoryConfiguration instanceof AbstractConfiguration)
    569        {
    570            ((AbstractConfiguration) inMemoryConfiguration)
    571                    .setListDelimiterHandler(listDelimiterHandler);
    572        }
    573        super.setListDelimiterHandler(listDelimiterHandler);
    574    }
    575
    576    /**
    577     * Returns the configuration source, in which the specified key is defined.
    578     * This method will iterate over all existing child configurations and check
    579     * whether they contain the specified key. The following constellations are
    580     * possible:
    581     * <ul>
    582     * <li>If exactly one child configuration contains the key, this
    583     * configuration is returned as the source configuration. This may be the
    584     * <em>in memory configuration</em> (this has to be explicitly checked by
    585     * the calling application).</li>
    586     * <li>If none of the child configurations contain the key, <b>null</b> is
    587     * returned.</li>
    588     * <li>If the key is contained in multiple child configurations or if the
    589     * key is <b>null</b>, a {@code IllegalArgumentException} is thrown.
    590     * In this case the source configuration cannot be determined.</li>
    591     * </ul>
    592     *
    593     * @param key the key to be checked
    594     * @return the source configuration of this key
    595     * @throws IllegalArgumentException if the source configuration cannot be
    596     * determined
    597     * @since 1.5
    598     */
    599    public Configuration getSource(final String key)
    600    {
    601        if (key == null)
    602        {
    603            throw new IllegalArgumentException("Key must not be null!");
    604        }
    605
    606        Configuration source = null;
    607        for (final Configuration conf : configList)
    608        {
    609            if (conf.containsKey(key))
    610            {
    611                if (source != null)
    612                {
    613                    throw new IllegalArgumentException("The key " + key
    614                            + " is defined by multiple sources!");
    615                }
    616                source = conf;
    617            }
    618        }
    619
    620        return source;
    621    }
    622
    623    /**
    624     * Replaces the current in-memory configuration by the given one.
    625     *
    626     * @param config the new in-memory configuration
    627     */
    628    private void replaceInMemoryConfiguration(final Configuration config)
    629    {
    630        if (!inMemoryConfigIsChild)
    631        {
    632            // remove current in-memory configuration
    633            configList.remove(inMemoryConfiguration);
    634        }
    635        inMemoryConfiguration = config;
    636    }
    637
    638    /**
    639     * Adds the value of a property to the given list. This method is used by
    640     * {@code getList()} for gathering property values from the child
    641     * configurations.
    642     *
    643     * @param dest the list for collecting the data
    644     * @param config the configuration to query
    645     * @param key the key of the property
    646     */
    647    private  void appendListProperty(final List<Object> dest, final Configuration config,
    648            final String key)
    649    {
    650        final Object value = interpolate(config.getProperty(key));
    651        if (value != null)
    652        {
    653            if (value instanceof Collection)
    654            {
    655                final Collection<?> col = (Collection<?>) value;
    656                dest.addAll(col);
    657            }
    658            else
    659            {
    660                dest.add(value);
    661            }
    662        }
    663    }
    664}