001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.configuration2;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.LinkedHashSet;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.ListIterator;
027import java.util.Set;
028
029import org.apache.commons.configuration2.convert.ListDelimiterHandler;
030import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
031
032/**
033 * <p>
034 * {@code CompositeConfiguration} allows you to add multiple {@code Configuration} objects to an aggregated
035 * configuration. If you add Configuration1, and then Configuration2, any properties shared will mean that the value
036 * defined by Configuration1 will be returned. If Configuration1 doesn't have the property, then Configuration2 will be
037 * checked. You can add multiple different types or the same type of properties file.
038 * </p>
039 * <p>
040 * When querying properties the order in which child configurations have been added is relevant. To deal with property
041 * updates, a so-called <em>in-memory configuration</em> is used. Per default, such a configuration is created
042 * automatically. All property writes target this special configuration. There are constructors which allow you to
043 * provide a specific in-memory configuration. If used that way, the in-memory configuration is always the last one in
044 * the list of child configurations. This means that for query operations all other configurations take precedence.
045 * </p>
046 * <p>
047 * Alternatively it is possible to mark a child configuration as in-memory configuration when it is added. In this case
048 * the treatment of the in-memory configuration is slightly different: it remains in the list of child configurations at
049 * the position it was added, i.e. its priority for property queries can be defined by adding the child configurations
050 * in the correct order.
051 * </p>
052 * <p>
053 * This configuration class uses a {@code Synchronizer} to control concurrent access. While all methods for reading and
054 * writing configuration properties make use of this {@code Synchronizer} per default, the methods for managing the list
055 * of child configurations and the in-memory configuration
056 * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
057 * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because most methods for accessing configuration
058 * data delegate to the list of child configurations, the thread-safety of a {@code CompositeConfiguration} object also
059 * depends on the {@code Synchronizer} objects used by these children.
060 * </p>
061 */
062public class CompositeConfiguration extends AbstractConfiguration implements Cloneable {
063    /** List holding all the configuration */
064    private List<Configuration> configList = new LinkedList<>();
065
066    /**
067     * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added.
068     */
069    private Configuration inMemoryConfiguration;
070
071    /**
072     * Stores a flag whether the current in-memory configuration is also a child configuration.
073     */
074    private boolean inMemoryConfigIsChild;
075
076    /**
077     * Creates an empty CompositeConfiguration object which can then be added some other Configuration files
078     */
079    public CompositeConfiguration() {
080        clear();
081    }
082
083    /**
084     * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will
085     * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special
086     * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as
087     * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <b>true</b> instead.
088     *
089     * @param inMemoryConfiguration the in memory configuration to use
090     */
091    public CompositeConfiguration(final Configuration inMemoryConfiguration) {
092        configList.clear();
093        this.inMemoryConfiguration = inMemoryConfiguration;
094        configList.add(inMemoryConfiguration);
095    }
096
097    /**
098     * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations
099     * specified.
100     *
101     * @param configurations the collection of configurations to add
102     */
103    public CompositeConfiguration(final Collection<? extends Configuration> configurations) {
104        this(new BaseConfiguration(), configurations);
105    }
106
107    /**
108     * Creates a CompositeConfiguration with a specified <em>in-memory configuration</em>, and then adds the given
109     * collection of configurations.
110     *
111     * @param inMemoryConfiguration the in memory configuration to use
112     * @param configurations the collection of configurations to add
113     * @see #CompositeConfiguration(Configuration)
114     */
115    public CompositeConfiguration(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) {
116        this(inMemoryConfiguration);
117        if (configurations != null) {
118            configurations.forEach(this::addConfiguration);
119        }
120    }
121
122    /**
123     * Add a configuration.
124     *
125     * @param config the configuration to add
126     */
127    public void addConfiguration(final Configuration config) {
128        addConfiguration(config, false);
129    }
130
131    /**
132     * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future
133     * property write operations are executed on this configuration. Note that the current in-memory configuration is
134     * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of
135     * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes
136     * its role as in-memory configuration to the new one.
137     *
138     * @param config the configuration to be added
139     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
140     *        otherwise
141     * @since 1.8
142     */
143    public void addConfiguration(final Configuration config, final boolean asInMemory) {
144        beginWrite(false);
145        try {
146            if (!configList.contains(config)) {
147                if (asInMemory) {
148                    replaceInMemoryConfiguration(config);
149                    inMemoryConfigIsChild = true;
150                }
151
152                if (!inMemoryConfigIsChild) {
153                    // As the inMemoryConfiguration contains all manually added
154                    // keys, we must make sure that it is always last. "Normal", non
155                    // composed configurations add their keys at the end of the
156                    // configuration and we want to mimic this behavior.
157                    configList.add(configList.indexOf(inMemoryConfiguration), config);
158                } else {
159                    // However, if the in-memory configuration is a regular child,
160                    // only the order in which child configurations are added is relevant
161                    configList.add(config);
162                }
163
164                if (config instanceof AbstractConfiguration) {
165                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
166                }
167            }
168        } finally {
169            endWrite();
170        }
171    }
172
173    /**
174     * Add a configuration to the start of the list of child configurations.
175     *
176     * @param config the configuration to add
177     * @since 2.3
178     */
179    public void addConfigurationFirst(final Configuration config) {
180        addConfigurationFirst(config, false);
181    }
182
183    /**
184     * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory
185     * configuration</em>. This means that all future property write operations are executed on this configuration. Note
186     * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the
187     * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child
188     * configurations at its current position, but it passes its role as in-memory configuration to the new one.
189     *
190     * @param config the configuration to be added
191     * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b>
192     *        otherwise
193     * @since 2.3
194     */
195    public void addConfigurationFirst(final Configuration config, final boolean asInMemory) {
196        beginWrite(false);
197        try {
198            if (!configList.contains(config)) {
199                if (asInMemory) {
200                    replaceInMemoryConfiguration(config);
201                    inMemoryConfigIsChild = true;
202                }
203                configList.add(0, config);
204
205                if (config instanceof AbstractConfiguration) {
206                    ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
207                }
208            }
209        } finally {
210            endWrite();
211        }
212    }
213
214    /**
215     * Remove a configuration. The in memory configuration cannot be removed.
216     *
217     * @param config The configuration to remove
218     */
219    public void removeConfiguration(final Configuration config) {
220        beginWrite(false);
221        try {
222            // Make sure that you can't remove the inMemoryConfiguration from
223            // the CompositeConfiguration object
224            if (!config.equals(inMemoryConfiguration)) {
225                configList.remove(config);
226            }
227        } finally {
228            endWrite();
229        }
230    }
231
232    /**
233     * Gets the number of configurations.
234     *
235     * @return the number of configuration
236     */
237    public int getNumberOfConfigurations() {
238        beginRead(false);
239        try {
240            return configList.size();
241        } finally {
242            endRead();
243        }
244    }
245
246    /**
247     * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong>
248     * A new in-memory configuration is created; the old one is lost.
249     */
250    @Override
251    protected void clearInternal() {
252        configList.clear();
253        // recreate the in memory configuration
254        inMemoryConfiguration = new BaseConfiguration();
255        ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
256        ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
257        configList.add(inMemoryConfiguration);
258        inMemoryConfigIsChild = false;
259    }
260
261    /**
262     * Add this property to the in-memory Configuration.
263     *
264     * @param key The Key to add the property to.
265     * @param token The Value to add.
266     */
267    @Override
268    protected void addPropertyDirect(final String key, final Object token) {
269        inMemoryConfiguration.addProperty(key, token);
270    }
271
272    /**
273     * Read property from underlying composite
274     *
275     * @param key key to use for mapping
276     *
277     * @return object associated with the given configuration key.
278     */
279    @Override
280    protected Object getPropertyInternal(final String key) {
281        return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null);
282    }
283
284    @Override
285    protected Iterator<String> getKeysInternal() {
286        final Set<String> keys = new LinkedHashSet<>();
287        configList.forEach(config -> config.getKeys().forEachRemaining(keys::add));
288        return keys.iterator();
289    }
290
291    @Override
292    protected Iterator<String> getKeysInternal(final String key) {
293        final Set<String> keys = new LinkedHashSet<>();
294        configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add));
295        return keys.iterator();
296    }
297
298    @Override
299    protected boolean isEmptyInternal() {
300        return configList.stream().allMatch(Configuration::isEmpty);
301    }
302
303    @Override
304    protected void clearPropertyDirect(final String key) {
305        configList.forEach(config -> config.clearProperty(key));
306    }
307
308    @Override
309    protected boolean containsKeyInternal(final String key) {
310        return configList.stream().anyMatch(config -> config.containsKey(key));
311    }
312
313    @Override
314    public List<Object> getList(final String key, final List<?> defaultValue) {
315        final List<Object> list = new ArrayList<>();
316
317        // add all elements from the first configuration containing the requested key
318        final Iterator<Configuration> it = configList.iterator();
319        while (it.hasNext() && list.isEmpty()) {
320            final Configuration config = it.next();
321            if (config != inMemoryConfiguration && config.containsKey(key)) {
322                appendListProperty(list, config, key);
323            }
324        }
325
326        // add all elements from the in memory configuration
327        appendListProperty(list, inMemoryConfiguration, key);
328
329        if (list.isEmpty()) {
330            // This is okay because we just return this list to the caller
331            @SuppressWarnings("unchecked")
332            final List<Object> resultList = (List<Object>) defaultValue;
333            return resultList;
334        }
335
336        final ListIterator<Object> lit = list.listIterator();
337        while (lit.hasNext()) {
338            lit.set(interpolate(lit.next()));
339        }
340
341        return list;
342    }
343
344    @Override
345    public String[] getStringArray(final String key) {
346        final List<Object> list = getList(key);
347
348        // transform property values into strings
349        final String[] tokens = new String[list.size()];
350
351        for (int i = 0; i < tokens.length; i++) {
352            tokens[i] = String.valueOf(list.get(i));
353        }
354
355        return tokens;
356    }
357
358    /**
359     * Gets the configuration at the specified index.
360     *
361     * @param index The index of the configuration to retrieve
362     * @return the configuration at this index
363     */
364    public Configuration getConfiguration(final int index) {
365        beginRead(false);
366        try {
367            return configList.get(index);
368        } finally {
369            endRead();
370        }
371    }
372
373    /**
374     * Gets the &quot;in memory configuration&quot;. In this configuration changes are stored.
375     *
376     * @return the in memory configuration
377     */
378    public Configuration getInMemoryConfiguration() {
379        beginRead(false);
380        try {
381            return inMemoryConfiguration;
382        } finally {
383            endRead();
384        }
385    }
386
387    /**
388     * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in
389     * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a
390     * runtime exception will be thrown. Registered event handlers won't get cloned.
391     *
392     * @return the copy
393     * @since 1.3
394     */
395    @Override
396    public Object clone() {
397        try {
398            final CompositeConfiguration copy = (CompositeConfiguration) super.clone();
399            copy.configList = new LinkedList<>();
400            copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration());
401            copy.configList.add(copy.inMemoryConfiguration);
402
403            configList.forEach(config -> {
404                if (config != getInMemoryConfiguration()) {
405                    copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config));
406                }
407            });
408
409            copy.cloneInterpolator(this);
410            return copy;
411        } catch (final CloneNotSupportedException cnex) {
412            // cannot happen
413            throw new ConfigurationRuntimeException(cnex);
414        }
415    }
416
417    /**
418     * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized.
419     */
420    @Override
421    public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
422        if (inMemoryConfiguration instanceof AbstractConfiguration) {
423            ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler);
424        }
425        super.setListDelimiterHandler(listDelimiterHandler);
426    }
427
428    /**
429     * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing
430     * child configurations and check whether they contain the specified key. The following constellations are possible:
431     * <ul>
432     * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration.
433     * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li>
434     * <li>If none of the child configurations contain the key, <b>null</b> is returned.</li>
435     * <li>If the key is contained in multiple child configurations or if the key is <b>null</b>, a
436     * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li>
437     * </ul>
438     *
439     * @param key the key to be checked
440     * @return the source configuration of this key
441     * @throws IllegalArgumentException if the source configuration cannot be determined
442     * @since 1.5
443     */
444    public Configuration getSource(final String key) {
445        if (key == null) {
446            throw new IllegalArgumentException("Key must not be null!");
447        }
448
449        Configuration source = null;
450        for (final Configuration conf : configList) {
451            if (conf.containsKey(key)) {
452                if (source != null) {
453                    throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!");
454                }
455                source = conf;
456            }
457        }
458
459        return source;
460    }
461
462    /**
463     * Replaces the current in-memory configuration by the given one.
464     *
465     * @param config the new in-memory configuration
466     */
467    private void replaceInMemoryConfiguration(final Configuration config) {
468        if (!inMemoryConfigIsChild) {
469            // remove current in-memory configuration
470            configList.remove(inMemoryConfiguration);
471        }
472        inMemoryConfiguration = config;
473    }
474
475    /**
476     * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property
477     * values from the child configurations.
478     *
479     * @param dest the list for collecting the data
480     * @param config the configuration to query
481     * @param key the key of the property
482     */
483    private void appendListProperty(final List<Object> dest, final Configuration config, final String key) {
484        final Object value = interpolate(config.getProperty(key));
485        if (value != null) {
486            if (value instanceof Collection) {
487                final Collection<?> col = (Collection<?>) value;
488                dest.addAll(col);
489            } else {
490                dest.add(value);
491            }
492        }
493    }
494}