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.LinkedHashSet;
024 import java.util.LinkedList;
025 import java.util.List;
026 import java.util.ListIterator;
027 import java.util.Set;
028
029 /**
030 * <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration}
031 * objects to an aggregated configuration. If you add Configuration1, and then Configuration2,
032 * any properties shared will mean that the value defined by Configuration1
033 * will be returned. If Configuration1 doesn't have the property, then
034 * Configuration2 will be checked. You can add multiple different types or the
035 * same type of properties file.</p>
036 * <p>When querying properties the order in which child configurations have been
037 * added is relevant. To deal with property updates, a so-called <em>in-memory
038 * configuration</em> is used. Per default, such a configuration is created
039 * automatically. All property writes target this special configuration. There
040 * are constructors which allow you to provide a specific in-memory configuration.
041 * If used that way, the in-memory configuration is always the last one in the
042 * list of child configurations. This means that for query operations all other
043 * configurations take precedence.</p>
044 * <p>Alternatively it is possible to mark a child configuration as in-memory
045 * configuration when it is added. In this case the treatment of the in-memory
046 * configuration is slightly different: it remains in the list of child
047 * configurations at the position it was added, i.e. its priority for property
048 * queries can be defined by adding the child configurations in the correct
049 * order.</p>
050 *
051 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
052 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
053 * @version $Id: CompositeConfiguration.java 1233058 2012-01-18 20:49:12Z oheger $
054 */
055 public class CompositeConfiguration extends AbstractConfiguration
056 implements Cloneable
057 {
058 /** List holding all the configuration */
059 private List<Configuration> configList = new LinkedList<Configuration>();
060
061 /**
062 * Configuration that holds in memory stuff. Inserted as first so any
063 * setProperty() override anything else added.
064 */
065 private Configuration inMemoryConfiguration;
066
067 /**
068 * Stores a flag whether the current in-memory configuration is also a
069 * child configuration.
070 */
071 private boolean inMemoryConfigIsChild;
072
073 /**
074 * Creates an empty CompositeConfiguration object which can then
075 * be added some other Configuration files
076 */
077 public CompositeConfiguration()
078 {
079 clear();
080 }
081
082 /**
083 * Creates a CompositeConfiguration object with a specified <em>in-memory
084 * configuration</em>. This configuration will store any changes made to the
085 * {@code CompositeConfiguration}. Note: Use this constructor if you want to
086 * set a special type of in-memory configuration. If you have a
087 * configuration which should act as both a child configuration and as
088 * in-memory configuration, use
089 * {@link #addConfiguration(Configuration, boolean)} with a value of
090 * <b>true</b> instead.
091 *
092 * @param inMemoryConfiguration the in memory configuration to use
093 */
094 public CompositeConfiguration(Configuration inMemoryConfiguration)
095 {
096 configList.clear();
097 this.inMemoryConfiguration = inMemoryConfiguration;
098 configList.add(inMemoryConfiguration);
099 }
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 "in memory configuration". 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 }