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 }