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.configuration;
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
029/**
030 * <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
031 * objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
032 * any properties shared will mean that the value defined by Configuration1
033 * will be returned. If Configuration1 doesn't have the property, then
034 * Configuration2 will be checked. You can add multiple different types or the
035 * same type of properties file.</p>
036 * <p>When querying properties the order in which child configurations have been
037 * added is relevant. To deal with property updates, a so-called <em>in-memory
038 * configuration</em> is used. Per default, such a configuration is created
039 * automatically. All property writes target this special configuration. There
040 * are constructors which allow you to provide a specific in-memory configuration.
041 * If used that way, the in-memory configuration is always the last one in the
042 * list of child configurations. This means that for query operations all other
043 * configurations take precedence.</p>
044 * <p>Alternatively it is possible to mark a child configuration as in-memory
045 * configuration when it is added. In this case the treatment of the in-memory
046 * configuration is slightly different: it remains in the list of child
047 * configurations at the position it was added, i.e. its priority for property
048 * queries can be defined by adding the child configurations in the correct
049 * order.</p>
050 *
051 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
052 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
053 * @version $Id: CompositeConfiguration.java 1534064 2013-10-21 08:44:33Z henning $
054 */
055public class CompositeConfiguration extends AbstractConfiguration
056implements Cloneable
057{
058    /** List holding all the configuration */
059    private List<Configuration> configList = new LinkedList<Configuration>();
060
061    /**
062     * Configuration that holds in memory stuff.  Inserted as first so any
063     * setProperty() override anything else added.
064     */
065    private Configuration inMemoryConfiguration;
066
067    /**
068     * Stores a flag whether the current in-memory configuration is also a
069     * child configuration.
070     */
071    private boolean inMemoryConfigIsChild;
072
073    /**
074     * Creates an empty CompositeConfiguration object which can then
075     * be added some other Configuration files
076     */
077    public CompositeConfiguration()
078    {
079        clear();
080    }
081
082    /**
083     * Creates a CompositeConfiguration object with a specified <em>in-memory
084     * configuration</em>. This configuration will store any changes made to the
085     * {@code CompositeConfiguration}. Note: Use this constructor if you want to
086     * set a special type of in-memory configuration. If you have a
087     * configuration which should act as both a child configuration and as
088     * in-memory configuration, use
089     * {@link #addConfiguration(Configuration, boolean)} with a value of
090     * <b>true</b> instead.
091     *
092     * @param inMemoryConfiguration the in memory configuration to use
093     */
094    public CompositeConfiguration(Configuration inMemoryConfiguration)
095    {
096        configList.clear();
097        this.inMemoryConfiguration = inMemoryConfiguration;
098        configList.add(inMemoryConfiguration);
099    }
100
101    /**
102     * Create a CompositeConfiguration with an empty in memory configuration
103     * and adds the collection of configurations specified.
104     *
105     * @param configurations the collection of configurations to add
106     */
107    public CompositeConfiguration(Collection<? extends Configuration> configurations)
108    {
109        this(new BaseConfiguration(), configurations);
110    }
111
112    /**
113     * Creates a CompositeConfiguration with a specified <em>in-memory
114     * configuration</em>, and then adds the given collection of configurations.
115     *
116     * @param inMemoryConfiguration the in memory configuration to use
117     * @param configurations        the collection of configurations to add
118     * @see #CompositeConfiguration(Configuration)
119     */
120    public CompositeConfiguration(Configuration inMemoryConfiguration,
121            Collection<? extends Configuration> configurations)
122    {
123        this(inMemoryConfiguration);
124
125        if (configurations != null)
126        {
127            for (Configuration c : configurations)
128            {
129                addConfiguration(c);
130            }
131        }
132    }
133
134    /**
135     * Add a configuration.
136     *
137     * @param config the configuration to add
138     */
139    public void addConfiguration(Configuration config)
140    {
141        addConfiguration(config, false);
142    }
143
144    /**
145     * Adds a child configuration and optionally makes it the <em>in-memory
146     * configuration</em>. This means that all future property write operations
147     * are executed on this configuration. Note that the current in-memory
148     * configuration is replaced by the new one. If it was created automatically
149     * or passed to the constructor, it is removed from the list of child
150     * configurations! Otherwise, it stays in the list of child configurations
151     * at its current position, but it passes its role as in-memory
152     * configuration to the new one.
153     *
154     * @param config the configuration to be added
155     * @param asInMemory <b>true</b> if this configuration becomes the new
156     *        <em>in-memory</em> configuration, <b>false</b> otherwise
157     * @since 1.8
158     */
159    public void addConfiguration(Configuration config, boolean asInMemory)
160    {
161        if (!configList.contains(config))
162        {
163            if (asInMemory)
164            {
165                replaceInMemoryConfiguration(config);
166                inMemoryConfigIsChild = true;
167            }
168
169            if (!inMemoryConfigIsChild)
170            {
171                // As the inMemoryConfiguration contains all manually added
172                // keys, we must make sure that it is always last. "Normal", non
173                // composed configurations add their keys at the end of the
174                // configuration and we want to mimic this behavior.
175                configList.add(configList.indexOf(inMemoryConfiguration),
176                        config);
177            }
178            else
179            {
180                // However, if the in-memory configuration is a regular child,
181                // only the order in which child configurations are added is
182                // relevant
183                configList.add(config);
184            }
185
186            if (config instanceof AbstractConfiguration)
187            {
188                ((AbstractConfiguration) config)
189                        .setThrowExceptionOnMissing(isThrowExceptionOnMissing());
190            }
191        }
192    }
193
194    /**
195     * Remove a configuration. The in memory configuration cannot be removed.
196     *
197     * @param config The configuration to remove
198     */
199    public void removeConfiguration(Configuration config)
200    {
201        // Make sure that you can't remove the inMemoryConfiguration from
202        // the CompositeConfiguration object
203        if (!config.equals(inMemoryConfiguration))
204        {
205            configList.remove(config);
206        }
207    }
208
209    /**
210     * Return the number of configurations.
211     *
212     * @return the number of configuration
213     */
214    public int getNumberOfConfigurations()
215    {
216        return configList.size();
217    }
218
219    /**
220     * Removes all child configurations and reinitializes the <em>in-memory
221     * configuration</em>. <strong>Attention:</strong> A new in-memory
222     * configuration is created; the old one is lost.
223     */
224    @Override
225    public void clear()
226    {
227        configList.clear();
228        // recreate the in memory configuration
229        inMemoryConfiguration = new BaseConfiguration();
230        ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
231        ((BaseConfiguration) inMemoryConfiguration).setListDelimiter(getListDelimiter());
232        ((BaseConfiguration) inMemoryConfiguration).setDelimiterParsingDisabled(isDelimiterParsingDisabled());
233        configList.add(inMemoryConfiguration);
234        inMemoryConfigIsChild = false;
235    }
236
237    /**
238     * Add this property to the inmemory Configuration.
239     *
240     * @param key The Key to add the property to.
241     * @param token The Value to add.
242     */
243    @Override
244    protected void addPropertyDirect(String key, Object token)
245    {
246        inMemoryConfiguration.addProperty(key, token);
247    }
248
249    /**
250     * Read property from underlying composite
251     *
252     * @param key key to use for mapping
253     *
254     * @return object associated with the given configuration key.
255     */
256    public Object getProperty(String key)
257    {
258        Configuration firstMatchingConfiguration = null;
259        for (Configuration config : configList)
260        {
261            if (config.containsKey(key))
262            {
263                firstMatchingConfiguration = config;
264                break;
265            }
266        }
267
268        if (firstMatchingConfiguration != null)
269        {
270            return firstMatchingConfiguration.getProperty(key);
271        }
272        else
273        {
274            return null;
275        }
276    }
277
278    public Iterator<String> getKeys()
279    {
280        Set<String> keys = new LinkedHashSet<String>();
281        for (Configuration config : configList)
282        {
283            for (Iterator<String> it = config.getKeys(); it.hasNext();)
284            {
285                keys.add(it.next());
286            }
287        }
288
289        return keys.iterator();
290    }
291
292    @Override
293    public Iterator<String> getKeys(String key)
294    {
295        Set<String> keys = new LinkedHashSet<String>();
296        for (Configuration config : configList)
297        {
298            for (Iterator<String> it = config.getKeys(key); it.hasNext();)
299            {
300                keys.add(it.next());
301            }
302        }
303
304        return keys.iterator();
305    }
306
307    public boolean isEmpty()
308    {
309        for (Configuration config : configList)
310        {
311            if (!config.isEmpty())
312            {
313                return false;
314            }
315        }
316
317        return true;
318    }
319
320    @Override
321    protected void clearPropertyDirect(String key)
322    {
323        for (Configuration config : configList)
324        {
325            config.clearProperty(key);
326        }
327    }
328
329    public boolean containsKey(String key)
330    {
331        for (Configuration config : configList)
332        {
333            if (config.containsKey(key))
334            {
335                return true;
336            }
337        }
338        return false;
339    }
340
341    @Override
342    public List<Object> getList(String key, List<?> defaultValue)
343    {
344        List<Object> list = new ArrayList<Object>();
345
346        // add all elements from the first configuration containing the requested key
347        Iterator<Configuration> it = configList.iterator();
348        while (it.hasNext() && list.isEmpty())
349        {
350            Configuration config = it.next();
351            if (config != inMemoryConfiguration && config.containsKey(key))
352            {
353                appendListProperty(list, config, key);
354            }
355        }
356
357        // add all elements from the in memory configuration
358        appendListProperty(list, inMemoryConfiguration, key);
359
360        if (list.isEmpty())
361        {
362            return (List<Object>) defaultValue;
363        }
364
365        ListIterator<Object> lit = list.listIterator();
366        while (lit.hasNext())
367        {
368            lit.set(interpolate(lit.next()));
369        }
370
371        return list;
372    }
373
374    @Override
375    public String[] getStringArray(String key)
376    {
377        List<Object> list = getList(key);
378
379        // transform property values into strings
380        String[] tokens = new String[list.size()];
381
382        for (int i = 0; i < tokens.length; i++)
383        {
384            tokens[i] = String.valueOf(list.get(i));
385        }
386
387        return tokens;
388    }
389
390    /**
391     * Return the configuration at the specified index.
392     *
393     * @param index The index of the configuration to retrieve
394     * @return the configuration at this index
395     */
396    public Configuration getConfiguration(int index)
397    {
398        return configList.get(index);
399    }
400
401    /**
402     * Returns the &quot;in memory configuration&quot;. In this configuration
403     * changes are stored.
404     *
405     * @return the in memory configuration
406     */
407    public Configuration getInMemoryConfiguration()
408    {
409        return inMemoryConfiguration;
410    }
411
412    /**
413     * Returns a copy of this object. This implementation will create a deep
414     * clone, i.e. all configurations contained in this composite will also be
415     * cloned. This only works if all contained configurations support cloning;
416     * otherwise a runtime exception will be thrown. Registered event handlers
417     * won't get cloned.
418     *
419     * @return the copy
420     * @since 1.3
421     */
422    @Override
423    public Object clone()
424    {
425        try
426        {
427            CompositeConfiguration copy = (CompositeConfiguration) super
428                    .clone();
429            copy.clearConfigurationListeners();
430            copy.configList = new LinkedList<Configuration>();
431            copy.inMemoryConfiguration = ConfigurationUtils
432                    .cloneConfiguration(getInMemoryConfiguration());
433            copy.configList.add(copy.inMemoryConfiguration);
434
435            for (Configuration config : configList)
436            {
437                if (config != getInMemoryConfiguration())
438                {
439                    copy.addConfiguration(ConfigurationUtils
440                            .cloneConfiguration(config));
441                }
442            }
443
444            return copy;
445        }
446        catch (CloneNotSupportedException cnex)
447        {
448            // cannot happen
449            throw new ConfigurationRuntimeException(cnex);
450        }
451    }
452
453    /**
454     * Sets a flag whether added values for string properties should be checked
455     * for the list delimiter. This implementation ensures that the in memory
456     * configuration is correctly initialized.
457     *
458     * @param delimiterParsingDisabled the new value of the flag
459     * @since 1.4
460     */
461    @Override
462    public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
463    {
464        if (inMemoryConfiguration instanceof AbstractConfiguration)
465        {
466            ((AbstractConfiguration) inMemoryConfiguration)
467                    .setDelimiterParsingDisabled(delimiterParsingDisabled);
468        }
469        super.setDelimiterParsingDisabled(delimiterParsingDisabled);
470    }
471
472    /**
473     * Sets the character that is used as list delimiter. This implementation
474     * ensures that the in memory configuration is correctly initialized.
475     *
476     * @param listDelimiter the new list delimiter character
477     * @since 1.4
478     */
479    @Override
480    public void setListDelimiter(char listDelimiter)
481    {
482        if (inMemoryConfiguration instanceof AbstractConfiguration)
483        {
484            ((AbstractConfiguration) inMemoryConfiguration)
485                    .setListDelimiter(listDelimiter);
486        }
487        super.setListDelimiter(listDelimiter);
488    }
489
490    /**
491     * Returns the configuration source, in which the specified key is defined.
492     * This method will iterate over all existing child configurations and check
493     * whether they contain the specified key. The following constellations are
494     * possible:
495     * <ul>
496     * <li>If exactly one child configuration contains the key, this
497     * configuration is returned as the source configuration. This may be the
498     * <em>in memory configuration</em> (this has to be explicitly checked by
499     * the calling application).</li>
500     * <li>If none of the child configurations contain the key, <b>null</b> is
501     * returned.</li>
502     * <li>If the key is contained in multiple child configurations or if the
503     * key is <b>null</b>, a {@code IllegalArgumentException} is thrown.
504     * In this case the source configuration cannot be determined.</li>
505     * </ul>
506     *
507     * @param key the key to be checked
508     * @return the source configuration of this key
509     * @throws IllegalArgumentException if the source configuration cannot be
510     * determined
511     * @since 1.5
512     */
513    public Configuration getSource(String key)
514    {
515        if (key == null)
516        {
517            throw new IllegalArgumentException("Key must not be null!");
518        }
519
520        Configuration source = null;
521        for (Configuration conf : configList)
522        {
523            if (conf.containsKey(key))
524            {
525                if (source != null)
526                {
527                    throw new IllegalArgumentException("The key " + key
528                            + " is defined by multiple sources!");
529                }
530                source = conf;
531            }
532        }
533
534        return source;
535    }
536
537    /**
538     * Replaces the current in-memory configuration by the given one.
539     *
540     * @param config the new in-memory configuration
541     */
542    private void replaceInMemoryConfiguration(Configuration config)
543    {
544        if (!inMemoryConfigIsChild)
545        {
546            // remove current in-memory configuration
547            configList.remove(inMemoryConfiguration);
548        }
549        inMemoryConfiguration = config;
550    }
551
552    /**
553     * Adds the value of a property to the given list. This method is used by
554     * {@code getList()} for gathering property values from the child
555     * configurations.
556     *
557     * @param dest the list for collecting the data
558     * @param config the configuration to query
559     * @param key the key of the property
560     */
561    private static void appendListProperty(List<Object> dest, Configuration config,
562            String key)
563    {
564        Object value = config.getProperty(key);
565        if (value != null)
566        {
567            if (value instanceof Collection)
568            {
569                Collection<?> col = (Collection<?>) value;
570                dest.addAll(col);
571            }
572            else
573            {
574                dest.add(value);
575            }
576        }
577    }
578}