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.LinkedList;
024 import java.util.List;
025 import java.util.ListIterator;
026
027 /**
028 * This Configuration class allows you to add multiple different types of Configuration
029 * to this CompositeConfiguration. If you add Configuration1, and then Configuration2,
030 * any properties shared will mean that Configuration1 will be returned.
031 * You can add multiple different types or the same type of properties file.
032 * If Configuration1 doesn't have the property, then Configuration2 will be checked.
033 *
034 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
035 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
036 * @version $Id: CompositeConfiguration.java 705028 2008-10-15 20:33:35Z oheger $
037 */
038 public class CompositeConfiguration extends AbstractConfiguration
039 implements Cloneable
040 {
041 /** List holding all the configuration */
042 private List configList = new LinkedList();
043
044 /**
045 * Configuration that holds in memory stuff. Inserted as first so any
046 * setProperty() override anything else added.
047 */
048 private Configuration inMemoryConfiguration;
049
050 /**
051 * Creates an empty CompositeConfiguration object which can then
052 * be added some other Configuration files
053 */
054 public CompositeConfiguration()
055 {
056 clear();
057 }
058
059 /**
060 * Creates a CompositeConfiguration object with a specified in memory
061 * configuration. This configuration will store any changes made to
062 * the CompositeConfiguration.
063 *
064 * @param inMemoryConfiguration the in memory configuration to use
065 */
066 public CompositeConfiguration(Configuration inMemoryConfiguration)
067 {
068 configList.clear();
069 this.inMemoryConfiguration = inMemoryConfiguration;
070 configList.add(inMemoryConfiguration);
071 }
072
073 /**
074 * Create a CompositeConfiguration with an empty in memory configuration
075 * and adds the collection of configurations specified.
076 *
077 * @param configurations the collection of configurations to add
078 */
079 public CompositeConfiguration(Collection configurations)
080 {
081 this(new BaseConfiguration(), configurations);
082 }
083
084 /**
085 * Creates a CompositeConfiguration with a specified in memory
086 * configuration, and then adds the given collection of configurations.
087 *
088 * @param inMemoryConfiguration the in memory configuration to use
089 * @param configurations the collection of configurations to add
090 */
091 public CompositeConfiguration(Configuration inMemoryConfiguration, Collection configurations)
092 {
093 this(inMemoryConfiguration);
094
095 if (configurations != null)
096 {
097 Iterator it = configurations.iterator();
098 while (it.hasNext())
099 {
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 "in memory configuration". 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 }