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