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