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