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