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    *     https://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.configuration2;
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  import org.apache.commons.configuration2.convert.ListDelimiterHandler;
30  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
31  
32  /**
33   * <p>
34   * {@code CompositeConfiguration} allows you to add multiple {@code Configuration} objects to an aggregated
35   * configuration. If you add Configuration1, and then Configuration2, any properties shared will mean that the value
36   * defined by Configuration1 will be returned. If Configuration1 doesn't have the property, then Configuration2 will be
37   * checked. You can add multiple different types or the same type of properties file.
38   * </p>
39   * <p>
40   * When querying properties the order in which child configurations have been added is relevant. To deal with property
41   * updates, a so-called <em>in-memory configuration</em> is used. Per default, such a configuration is created
42   * automatically. All property writes target this special configuration. There are constructors which allow you to
43   * provide a specific in-memory configuration. If used that way, the in-memory configuration is always the last one in
44   * the list of child configurations. This means that for query operations all other configurations take precedence.
45   * </p>
46   * <p>
47   * Alternatively it is possible to mark a child configuration as in-memory configuration when it is added. In this case
48   * the treatment of the in-memory configuration is slightly different: it remains in the list of child configurations at
49   * the position it was added, i.e. its priority for property queries can be defined by adding the child configurations
50   * in the correct order.
51   * </p>
52   * <p>
53   * This configuration class uses a {@code Synchronizer} to control concurrent access. While all methods for reading and
54   * writing configuration properties make use of this {@code Synchronizer} per default, the methods for managing the list
55   * of child configurations and the in-memory configuration
56   * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(),
57   * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because most methods for accessing configuration
58   * data delegate to the list of child configurations, the thread-safety of a {@code CompositeConfiguration} object also
59   * depends on the {@code Synchronizer} objects used by these children.
60   * </p>
61   */
62  public class CompositeConfiguration extends AbstractConfiguration implements Cloneable {
63  
64      /** List holding all the configuration */
65      private List<Configuration> configList = new LinkedList<>();
66  
67      /**
68       * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added.
69       */
70      private Configuration inMemoryConfiguration;
71  
72      /**
73       * Stores a flag whether the current in-memory configuration is also a child configuration.
74       */
75      private boolean inMemoryConfigIsChild;
76  
77      /**
78       * Creates an empty CompositeConfiguration object which can then be added some other Configuration files
79       */
80      public CompositeConfiguration() {
81          clear();
82      }
83  
84      /**
85       * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations
86       * specified.
87       *
88       * @param configurations the collection of configurations to add
89       */
90      public CompositeConfiguration(final Collection<? extends Configuration> configurations) {
91          this(new BaseConfiguration(), configurations);
92      }
93  
94      /**
95       * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will
96       * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special
97       * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as
98       * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <strong>true</strong> instead.
99       *
100      * @param inMemoryConfiguration the in memory configuration to use
101      */
102     public CompositeConfiguration(final Configuration inMemoryConfiguration) {
103         this.configList.clear();
104         this.inMemoryConfiguration = inMemoryConfiguration;
105         this.configList.add(inMemoryConfiguration);
106     }
107 
108     /**
109      * Creates a CompositeConfiguration with a specified <em>in-memory configuration</em>, and then adds the given
110      * collection of configurations.
111      *
112      * @param inMemoryConfiguration the in memory configuration to use
113      * @param configurations the collection of configurations to add
114      * @see #CompositeConfiguration(Configuration)
115      */
116     public CompositeConfiguration(final Configuration inMemoryConfiguration, final Collection<? extends Configuration> configurations) {
117         this(inMemoryConfiguration);
118         if (configurations != null) {
119             configurations.forEach(this::addConfiguration);
120         }
121     }
122 
123     /**
124      * Add a configuration.
125      *
126      * @param config the configuration to add
127      */
128     public void addConfiguration(final Configuration config) {
129         addConfiguration(config, false);
130     }
131 
132     /**
133      * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future
134      * property write operations are executed on this configuration. Note that the current in-memory configuration is
135      * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of
136      * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes
137      * its role as in-memory configuration to the new one.
138      *
139      * @param config the configuration to be added
140      * @param asInMemory <strong>true</strong> if this configuration becomes the new <em>in-memory</em> configuration, <strong>false</strong>
141      *        otherwise
142      * @since 1.8
143      */
144     public void addConfiguration(final Configuration config, final boolean asInMemory) {
145         syncWrite(() -> {
146             if (!configList.contains(config)) {
147                 if (asInMemory) {
148                     replaceInMemoryConfiguration(config);
149                     inMemoryConfigIsChild = true;
150                 }
151                 if (!inMemoryConfigIsChild) {
152                     // As the inMemoryConfiguration contains all manually added
153                     // keys, we must make sure that it is always last. "Normal", non
154                     // composed configurations add their keys at the end of the
155                     // configuration and we want to mimic this behavior.
156                     configList.add(configList.indexOf(inMemoryConfiguration), config);
157                 } else {
158                     // However, if the in-memory configuration is a regular child,
159                     // only the order in which child configurations are added is relevant
160                     configList.add(config);
161                 }
162                 if (config instanceof AbstractConfiguration) {
163                     ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
164                 }
165             }
166         }, false);
167     }
168 
169     /**
170      * Add a configuration to the start of the list of child configurations.
171      *
172      * @param config the configuration to add
173      * @since 2.3
174      */
175     public void addConfigurationFirst(final Configuration config) {
176         addConfigurationFirst(config, false);
177     }
178 
179     /**
180      * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory
181      * configuration</em>. This means that all future property write operations are executed on this configuration. Note
182      * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the
183      * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child
184      * configurations at its current position, but it passes its role as in-memory configuration to the new one.
185      *
186      * @param config the configuration to be added
187      * @param asInMemory <strong>true</strong> if this configuration becomes the new <em>in-memory</em> configuration, <strong>false</strong>
188      *        otherwise
189      * @since 2.3
190      */
191     public void addConfigurationFirst(final Configuration config, final boolean asInMemory) {
192         syncWrite(() -> {
193             if (!configList.contains(config)) {
194                 if (asInMemory) {
195                     replaceInMemoryConfiguration(config);
196                     inMemoryConfigIsChild = true;
197                 }
198                 configList.add(0, config);
199                 if (config instanceof AbstractConfiguration) {
200                     ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
201                 }
202             }
203         }, false);
204     }
205 
206     /**
207      * Add this property to the in-memory Configuration.
208      *
209      * @param key The Key to add the property to.
210      * @param token The Value to add.
211      */
212     @Override
213     protected void addPropertyDirect(final String key, final Object token) {
214         inMemoryConfiguration.addProperty(key, token);
215     }
216 
217     /**
218      * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property
219      * values from the child configurations.
220      *
221      * @param dest the list for collecting the data
222      * @param config the configuration to query
223      * @param key the key of the property
224      */
225     private void appendListProperty(final List<Object> dest, final Configuration config, final String key) {
226         final Object value = interpolate(config.getProperty(key));
227         if (value != null) {
228             if (value instanceof Collection) {
229                 final Collection<?> col = (Collection<?>) value;
230                 dest.addAll(col);
231             } else {
232                 dest.add(value);
233             }
234         }
235     }
236 
237     /**
238      * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong>
239      * A new in-memory configuration is created; the old one is lost.
240      */
241     @Override
242     protected void clearInternal() {
243         configList.clear();
244         // recreate the in memory configuration
245         inMemoryConfiguration = new BaseConfiguration();
246         ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing());
247         ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler());
248         configList.add(inMemoryConfiguration);
249         inMemoryConfigIsChild = false;
250     }
251 
252     @Override
253     protected void clearPropertyDirect(final String key) {
254         configList.forEach(config -> config.clearProperty(key));
255     }
256 
257     /**
258      * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in
259      * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a
260      * runtime exception will be thrown. Registered event handlers won't get cloned.
261      *
262      * @return the copy
263      * @since 1.3
264      */
265     @Override
266     public Object clone() {
267         try {
268             final CompositeConfiguration copy = (CompositeConfiguration) super.clone();
269             copy.configList = new LinkedList<>();
270             copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration());
271             copy.configList.add(copy.inMemoryConfiguration);
272 
273             configList.forEach(config -> {
274                 if (config != getInMemoryConfiguration()) {
275                     copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config));
276                 }
277             });
278 
279             copy.cloneInterpolator(this);
280             return copy;
281         } catch (final CloneNotSupportedException cnex) {
282             // cannot happen
283             throw new ConfigurationRuntimeException(cnex);
284         }
285     }
286 
287     @Override
288     protected boolean containsKeyInternal(final String key) {
289         return configList.stream().anyMatch(config -> config.containsKey(key));
290     }
291 
292     /**
293      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
294      * match but may be more expensive than the containsKey method.
295      *
296      * @since 2.11.0
297      */
298     @Override
299     protected boolean containsValueInternal(final Object value) {
300         return configList.stream().anyMatch(config -> config.containsValue(value));
301     }
302 
303     /**
304      * Gets the configuration at the specified index.
305      *
306      * @param index The index of the configuration to retrieve
307      * @return the configuration at this index
308      */
309     public Configuration getConfiguration(final int index) {
310         return syncRead(() -> configList.get(index), false);
311     }
312 
313     /**
314      * Gets the &quot;in memory configuration&quot;. In this configuration changes are stored.
315      *
316      * @return the in memory configuration
317      */
318     public Configuration getInMemoryConfiguration() {
319         return syncReadValue(inMemoryConfiguration, false);
320     }
321 
322     @Override
323     protected Iterator<String> getKeysInternal() {
324         final Set<String> keys = new LinkedHashSet<>();
325         configList.forEach(config -> config.getKeys().forEachRemaining(keys::add));
326         return keys.iterator();
327     }
328 
329     @Override
330     protected Iterator<String> getKeysInternal(final String key) {
331         final Set<String> keys = new LinkedHashSet<>();
332         configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add));
333         return keys.iterator();
334     }
335 
336     @Override
337     protected Iterator<String> getKeysInternal(final String key, final String delimiter) {
338         final Set<String> keys = new LinkedHashSet<>();
339         configList.forEach(config -> config.getKeys(key, delimiter).forEachRemaining(keys::add));
340         return keys.iterator();
341     }
342 
343     @Override
344     public List<Object> getList(final String key, final List<?> defaultValue) {
345         final List<Object> list = new ArrayList<>();
346 
347         // add all elements from the first configuration containing the requested key
348         final Iterator<Configuration> it = configList.iterator();
349         while (it.hasNext() && list.isEmpty()) {
350             final Configuration config = it.next();
351             if (config != inMemoryConfiguration && config.containsKey(key)) {
352                 appendListProperty(list, config, key);
353             }
354         }
355 
356         // add all elements from the in memory configuration
357         appendListProperty(list, inMemoryConfiguration, key);
358 
359         if (list.isEmpty()) {
360             // This is okay because we just return this list to the caller
361             @SuppressWarnings("unchecked")
362             final List<Object> resultList = (List<Object>) defaultValue;
363             return resultList;
364         }
365 
366         final ListIterator<Object> lit = list.listIterator();
367         while (lit.hasNext()) {
368             lit.set(interpolate(lit.next()));
369         }
370 
371         return list;
372     }
373 
374     /**
375      * Gets the number of configurations.
376      *
377      * @return the number of configuration
378      */
379     public int getNumberOfConfigurations() {
380         return syncRead(configList::size, false);
381     }
382 
383     /**
384      * Reads property from underlying composite
385      *
386      * @param key key to use for mapping
387      * @return object associated with the given configuration key.
388      */
389     @Override
390     protected Object getPropertyInternal(final String key) {
391         return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null);
392     }
393 
394     /**
395      * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing
396      * child configurations and check whether they contain the specified key. The following constellations are possible:
397      * <ul>
398      * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration.
399      * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li>
400      * <li>If none of the child configurations contain the key, <strong>null</strong> is returned.</li>
401      * <li>If the key is contained in multiple child configurations or if the key is <strong>null</strong>, a
402      * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li>
403      * </ul>
404      *
405      * @param key the key to be checked
406      * @return the source configuration of this key
407      * @throws IllegalArgumentException if the source configuration cannot be determined
408      * @since 1.5
409      */
410     public Configuration getSource(final String key) {
411         if (key == null) {
412             throw new IllegalArgumentException("Key must not be null.");
413         }
414 
415         Configuration source = null;
416         for (final Configuration conf : configList) {
417             if (conf.containsKey(key)) {
418                 if (source != null) {
419                     throw new IllegalArgumentException("The key " + key + " is defined by multiple sources.");
420                 }
421                 source = conf;
422             }
423         }
424 
425         return source;
426     }
427 
428     @Override
429     public String[] getStringArray(final String key) {
430         final List<Object> list = getList(key);
431 
432         // transform property values into strings
433         final String[] tokens = new String[list.size()];
434 
435         for (int i = 0; i < tokens.length; i++) {
436             tokens[i] = String.valueOf(list.get(i));
437         }
438 
439         return tokens;
440     }
441 
442     @Override
443     protected boolean isEmptyInternal() {
444         return configList.stream().allMatch(Configuration::isEmpty);
445     }
446 
447     /**
448      * Remove a configuration. The in memory configuration cannot be removed.
449      *
450      * @param config The configuration to remove
451      */
452     public void removeConfiguration(final Configuration config) {
453         syncWrite(() -> {
454             // Make sure that you can't remove the inMemoryConfiguration from
455             // the CompositeConfiguration object
456             if (!config.equals(inMemoryConfiguration)) {
457                 configList.remove(config);
458             }
459         }, false);
460     }
461 
462     /**
463      * Replaces the current in-memory configuration by the given one.
464      *
465      * @param config the new in-memory configuration
466      */
467     private void replaceInMemoryConfiguration(final Configuration config) {
468         if (!inMemoryConfigIsChild) {
469             // remove current in-memory configuration
470             configList.remove(inMemoryConfiguration);
471         }
472         inMemoryConfiguration = config;
473     }
474 
475     /**
476      * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized.
477      */
478     @Override
479     public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
480         if (inMemoryConfiguration instanceof AbstractConfiguration) {
481             ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler);
482         }
483         super.setListDelimiterHandler(listDelimiterHandler);
484     }
485 }