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