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.LinkedHashSet;
24  import java.util.LinkedList;
25  import java.util.List;
26  import java.util.ListIterator;
27  import java.util.Set;
28  
29  /**
30   * <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
31   * objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
32   * any properties shared will mean that the value defined by Configuration1
33   * will be returned. If Configuration1 doesn't have the property, then
34   * Configuration2 will be checked. You can add multiple different types or the
35   * same type of properties file.</p>
36   * <p>When querying properties the order in which child configurations have been
37   * added is relevant. To deal with property updates, a so-called <em>in-memory
38   * configuration</em> is used. Per default, such a configuration is created
39   * automatically. All property writes target this special configuration. There
40   * are constructors which allow you to provide a specific in-memory configuration.
41   * If used that way, the in-memory configuration is always the last one in the
42   * list of child configurations. This means that for query operations all other
43   * configurations take precedence.</p>
44   * <p>Alternatively it is possible to mark a child configuration as in-memory
45   * configuration when it is added. In this case the treatment of the in-memory
46   * configuration is slightly different: it remains in the list of child
47   * configurations at the position it was added, i.e. its priority for property
48   * queries can be defined by adding the child configurations in the correct
49   * order.</p>
50   *
51   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
52   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
53   * @version $Id: CompositeConfiguration.java 1233058 2012-01-18 20:49:12Z oheger $
54   */
55  public class CompositeConfiguration extends AbstractConfiguration
56  implements Cloneable
57  {
58      /** List holding all the configuration */
59      private List<Configuration> configList = new LinkedList<Configuration>();
60  
61      /**
62       * Configuration that holds in memory stuff.  Inserted as first so any
63       * setProperty() override anything else added.
64       */
65      private Configuration inMemoryConfiguration;
66  
67      /**
68       * Stores a flag whether the current in-memory configuration is also a
69       * child configuration.
70       */
71      private boolean inMemoryConfigIsChild;
72  
73      /**
74       * Creates an empty CompositeConfiguration object which can then
75       * be added some other Configuration files
76       */
77      public CompositeConfiguration()
78      {
79          clear();
80      }
81  
82      /**
83       * Creates a CompositeConfiguration object with a specified <em>in-memory
84       * configuration</em>. This configuration will store any changes made to the
85       * {@code CompositeConfiguration}. Note: Use this constructor if you want to
86       * set a special type of in-memory configuration. If you have a
87       * configuration which should act as both a child configuration and as
88       * in-memory configuration, use
89       * {@link #addConfiguration(Configuration, boolean)} with a value of
90       * <b>true</b> instead.
91       *
92       * @param inMemoryConfiguration the in memory configuration to use
93       */
94      public CompositeConfiguration(Configuration inMemoryConfiguration)
95      {
96          configList.clear();
97          this.inMemoryConfiguration = inMemoryConfiguration;
98          configList.add(inMemoryConfiguration);
99      }
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 }