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