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