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