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 "in memory configuration". 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 }