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    
018    package org.apache.commons.configuration;
019    
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.Iterator;
023    import java.util.LinkedHashSet;
024    import java.util.LinkedList;
025    import java.util.List;
026    import java.util.ListIterator;
027    import 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 1233058 2012-01-18 20:49:12Z oheger $
054     */
055    public class CompositeConfiguration extends AbstractConfiguration
056    implements 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<Object> 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 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    }