View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.ListIterator;
26  
27  /***
28   * This Configuration class allows you to add multiple different types of Configuration
29   * to this CompositeConfiguration.  If you add Configuration1, and then Configuration2,
30   * any properties shared will mean that Configuration1 will be returned.
31   * You can add multiple different types or the same type of properties file.
32   * If Configuration1 doesn't have the property, then Configuration2 will be checked.
33   *
34   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
35   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
36   * @version $Id: CompositeConfiguration.java 705028 2008-10-15 20:33:35Z oheger $
37   */
38  public class CompositeConfiguration extends AbstractConfiguration
39  implements Cloneable
40  {
41      /*** List holding all the configuration */
42      private List configList = new LinkedList();
43  
44      /***
45       * Configuration that holds in memory stuff.  Inserted as first so any
46       * setProperty() override anything else added.
47       */
48      private Configuration inMemoryConfiguration;
49  
50      /***
51       * Creates an empty CompositeConfiguration object which can then
52       * be added some other Configuration files
53       */
54      public CompositeConfiguration()
55      {
56          clear();
57      }
58  
59      /***
60       * Creates a CompositeConfiguration object with a specified in memory
61       * configuration. This configuration will store any changes made to
62       * the CompositeConfiguration.
63       *
64       * @param inMemoryConfiguration the in memory configuration to use
65       */
66      public CompositeConfiguration(Configuration inMemoryConfiguration)
67      {
68          configList.clear();
69          this.inMemoryConfiguration = inMemoryConfiguration;
70          configList.add(inMemoryConfiguration);
71      }
72  
73      /***
74       * Create a CompositeConfiguration with an empty in memory configuration
75       * and adds the collection of configurations specified.
76       *
77       * @param configurations the collection of configurations to add
78       */
79      public CompositeConfiguration(Collection configurations)
80      {
81          this(new BaseConfiguration(), configurations);
82      }
83  
84      /***
85       * Creates a CompositeConfiguration with a specified in memory
86       * configuration, and then adds the given collection of configurations.
87       *
88       * @param inMemoryConfiguration the in memory configuration to use
89       * @param configurations        the collection of configurations to add
90       */
91      public CompositeConfiguration(Configuration inMemoryConfiguration, Collection configurations)
92      {
93          this(inMemoryConfiguration);
94  
95          if (configurations != null)
96          {
97              Iterator it = configurations.iterator();
98              while (it.hasNext())
99              {
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 }