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 018package org.apache.commons.configuration2; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Iterator; 023import java.util.LinkedHashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.ListIterator; 027import java.util.Set; 028 029import org.apache.commons.configuration2.convert.ListDelimiterHandler; 030import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 031 032/** 033 * <p>{@code CompositeConfiguration} allows you to add multiple {@code Configuration} 034 * objects to an aggregated configuration. If you add Configuration1, and then Configuration2, 035 * any properties shared will mean that the value defined by Configuration1 036 * will be returned. If Configuration1 doesn't have the property, then 037 * Configuration2 will be checked. You can add multiple different types or the 038 * same type of properties file.</p> 039 * <p>When querying properties the order in which child configurations have been 040 * added is relevant. To deal with property updates, a so-called <em>in-memory 041 * configuration</em> is used. Per default, such a configuration is created 042 * automatically. All property writes target this special configuration. There 043 * are constructors which allow you to provide a specific in-memory configuration. 044 * If used that way, the in-memory configuration is always the last one in the 045 * list of child configurations. This means that for query operations all other 046 * configurations take precedence.</p> 047 * <p>Alternatively it is possible to mark a child configuration as in-memory 048 * configuration when it is added. In this case the treatment of the in-memory 049 * configuration is slightly different: it remains in the list of child 050 * configurations at the position it was added, i.e. its priority for property 051 * queries can be defined by adding the child configurations in the correct 052 * order.</p> 053 * <p> 054 * This configuration class uses a {@code Synchronizer} to control concurrent 055 * access. While all methods for reading and writing configuration properties 056 * make use of this {@code Synchronizer} per default, the methods for managing 057 * the list of child configurations and the in-memory configuration 058 * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(), 059 * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because 060 * most methods for accessing configuration data delegate to the list of child 061 * configurations, the thread-safety of a {@code CompositeConfiguration} 062 * object also depends on the {@code Synchronizer} objects used by these 063 * children. 064 * </p> 065 * 066 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 067 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 068 */ 069public class CompositeConfiguration extends AbstractConfiguration 070implements Cloneable 071{ 072 /** List holding all the configuration */ 073 private List<Configuration> configList = new LinkedList<>(); 074 075 /** 076 * Configuration that holds in memory stuff. Inserted as first so any 077 * setProperty() override anything else added. 078 */ 079 private Configuration inMemoryConfiguration; 080 081 /** 082 * Stores a flag whether the current in-memory configuration is also a 083 * child configuration. 084 */ 085 private boolean inMemoryConfigIsChild; 086 087 /** 088 * Creates an empty CompositeConfiguration object which can then 089 * be added some other Configuration files 090 */ 091 public CompositeConfiguration() 092 { 093 clear(); 094 } 095 096 /** 097 * Creates a CompositeConfiguration object with a specified <em>in-memory 098 * configuration</em>. This configuration will store any changes made to the 099 * {@code CompositeConfiguration}. Note: Use this constructor if you want to 100 * set a special type of in-memory configuration. If you have a 101 * configuration which should act as both a child configuration and as 102 * in-memory configuration, use 103 * {@link #addConfiguration(Configuration, boolean)} with a value of 104 * <b>true</b> instead. 105 * 106 * @param inMemoryConfiguration the in memory configuration to use 107 */ 108 public CompositeConfiguration(final Configuration inMemoryConfiguration) 109 { 110 configList.clear(); 111 this.inMemoryConfiguration = inMemoryConfiguration; 112 configList.add(inMemoryConfiguration); 113 } 114 115 /** 116 * Create a CompositeConfiguration with an empty in memory configuration 117 * and adds the collection of configurations specified. 118 * 119 * @param configurations the collection of configurations to add 120 */ 121 public CompositeConfiguration(final Collection<? extends Configuration> configurations) 122 { 123 this(new BaseConfiguration(), configurations); 124 } 125 126 /** 127 * Creates a CompositeConfiguration with a specified <em>in-memory 128 * configuration</em>, and then adds the given collection of configurations. 129 * 130 * @param inMemoryConfiguration the in memory configuration to use 131 * @param configurations the collection of configurations to add 132 * @see #CompositeConfiguration(Configuration) 133 */ 134 public CompositeConfiguration(final Configuration inMemoryConfiguration, 135 final Collection<? extends Configuration> configurations) 136 { 137 this(inMemoryConfiguration); 138 139 if (configurations != null) 140 { 141 for (final Configuration c : configurations) 142 { 143 addConfiguration(c); 144 } 145 } 146 } 147 148 /** 149 * Add a configuration. 150 * 151 * @param config the configuration to add 152 */ 153 public void addConfiguration(final Configuration config) 154 { 155 addConfiguration(config, false); 156 } 157 158 /** 159 * Adds a child configuration and optionally makes it the <em>in-memory 160 * configuration</em>. This means that all future property write operations 161 * are executed on this configuration. Note that the current in-memory 162 * configuration is replaced by the new one. If it was created automatically 163 * or passed to the constructor, it is removed from the list of child 164 * configurations! Otherwise, it stays in the list of child configurations 165 * at its current position, but it passes its role as in-memory 166 * configuration to the new one. 167 * 168 * @param config the configuration to be added 169 * @param asInMemory <b>true</b> if this configuration becomes the new 170 * <em>in-memory</em> configuration, <b>false</b> otherwise 171 * @since 1.8 172 */ 173 public void addConfiguration(final Configuration config, final boolean asInMemory) 174 { 175 beginWrite(false); 176 try 177 { 178 if (!configList.contains(config)) 179 { 180 if (asInMemory) 181 { 182 replaceInMemoryConfiguration(config); 183 inMemoryConfigIsChild = true; 184 } 185 186 if (!inMemoryConfigIsChild) 187 { 188 // As the inMemoryConfiguration contains all manually added 189 // keys, we must make sure that it is always last. "Normal", non 190 // composed configurations add their keys at the end of the 191 // configuration and we want to mimic this behavior. 192 configList.add(configList.indexOf(inMemoryConfiguration), 193 config); 194 } 195 else 196 { 197 // However, if the in-memory configuration is a regular child, 198 // only the order in which child configurations are added is relevant 199 configList.add(config); 200 } 201 202 if (config instanceof AbstractConfiguration) 203 { 204 ((AbstractConfiguration) config) 205 .setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 206 } 207 } 208 } 209 finally 210 { 211 endWrite(); 212 } 213 } 214 215 /** 216 * Add a configuration to the start of the list of child configurations. 217 * 218 * @param config the configuration to add 219 * @since 2.3 220 */ 221 public void addConfigurationFirst(final Configuration config) 222 { 223 addConfigurationFirst(config, false); 224 } 225 226 /** 227 * Adds a child configuration to the start of the collection and optionally 228 * makes it the <em>in-memory configuration</em>. This means that all future 229 * property write operations are executed on this configuration. Note that 230 * the current in-memory configuration is replaced by the new one. If it was 231 * created automatically or passed to the constructor, it is removed from the 232 * list of child configurations! Otherwise, it stays in the list of child configurations 233 * at its current position, but it passes its role as in-memory configuration to the new one. 234 * 235 * @param config the configuration to be added 236 * @param asInMemory <b>true</b> if this configuration becomes the new 237 * <em>in-memory</em> configuration, <b>false</b> otherwise 238 * @since 2.3 239 */ 240 public void addConfigurationFirst(final Configuration config, final boolean asInMemory) 241 { 242 beginWrite(false); 243 try 244 { 245 if (!configList.contains(config)) 246 { 247 if (asInMemory) 248 { 249 replaceInMemoryConfiguration(config); 250 inMemoryConfigIsChild = true; 251 } 252 configList.add(0, config); 253 254 if (config instanceof AbstractConfiguration) 255 { 256 ((AbstractConfiguration) config) 257 .setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 258 } 259 } 260 } 261 finally 262 { 263 endWrite(); 264 } 265 } 266 267 /** 268 * Remove a configuration. The in memory configuration cannot be removed. 269 * 270 * @param config The configuration to remove 271 */ 272 public void removeConfiguration(final Configuration config) 273 { 274 beginWrite(false); 275 try 276 { 277 // Make sure that you can't remove the inMemoryConfiguration from 278 // the CompositeConfiguration object 279 if (!config.equals(inMemoryConfiguration)) 280 { 281 configList.remove(config); 282 } 283 } 284 finally 285 { 286 endWrite(); 287 } 288 } 289 290 /** 291 * Return the number of configurations. 292 * 293 * @return the number of configuration 294 */ 295 public int getNumberOfConfigurations() 296 { 297 beginRead(false); 298 try 299 { 300 return configList.size(); 301 } 302 finally 303 { 304 endRead(); 305 } 306 } 307 308 /** 309 * Removes all child configurations and reinitializes the <em>in-memory 310 * configuration</em>. <strong>Attention:</strong> A new in-memory 311 * configuration is created; the old one is lost. 312 */ 313 @Override 314 protected void clearInternal() 315 { 316 configList.clear(); 317 // recreate the in memory configuration 318 inMemoryConfiguration = new BaseConfiguration(); 319 ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 320 ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler()); 321 configList.add(inMemoryConfiguration); 322 inMemoryConfigIsChild = false; 323 } 324 325 /** 326 * Add this property to the in-memory Configuration. 327 * 328 * @param key The Key to add the property to. 329 * @param token The Value to add. 330 */ 331 @Override 332 protected void addPropertyDirect(final String key, final Object token) 333 { 334 inMemoryConfiguration.addProperty(key, token); 335 } 336 337 /** 338 * Read property from underlying composite 339 * 340 * @param key key to use for mapping 341 * 342 * @return object associated with the given configuration key. 343 */ 344 @Override 345 protected Object getPropertyInternal(final String key) 346 { 347 Configuration firstMatchingConfiguration = null; 348 for (final Configuration config : configList) 349 { 350 if (config.containsKey(key)) 351 { 352 firstMatchingConfiguration = config; 353 break; 354 } 355 } 356 357 if (firstMatchingConfiguration != null) 358 { 359 return firstMatchingConfiguration.getProperty(key); 360 } 361 return null; 362 } 363 364 @Override 365 protected Iterator<String> getKeysInternal() 366 { 367 final Set<String> keys = new LinkedHashSet<>(); 368 for (final Configuration config : configList) 369 { 370 for (final Iterator<String> it = config.getKeys(); it.hasNext();) 371 { 372 keys.add(it.next()); 373 } 374 } 375 376 return keys.iterator(); 377 } 378 379 @Override 380 protected Iterator<String> getKeysInternal(final String key) 381 { 382 final Set<String> keys = new LinkedHashSet<>(); 383 for (final Configuration config : configList) 384 { 385 for (final Iterator<String> it = config.getKeys(key); it.hasNext();) 386 { 387 keys.add(it.next()); 388 } 389 } 390 391 return keys.iterator(); 392 } 393 394 @Override 395 protected boolean isEmptyInternal() 396 { 397 for (final Configuration config : configList) 398 { 399 if (!config.isEmpty()) 400 { 401 return false; 402 } 403 } 404 405 return true; 406 } 407 408 @Override 409 protected void clearPropertyDirect(final String key) 410 { 411 for (final Configuration config : configList) 412 { 413 config.clearProperty(key); 414 } 415 } 416 417 @Override 418 protected boolean containsKeyInternal(final String key) 419 { 420 for (final Configuration config : configList) 421 { 422 if (config.containsKey(key)) 423 { 424 return true; 425 } 426 } 427 return false; 428 } 429 430 @Override 431 public List<Object> getList(final String key, final List<?> defaultValue) 432 { 433 final List<Object> list = new ArrayList<>(); 434 435 // add all elements from the first configuration containing the requested key 436 final Iterator<Configuration> it = configList.iterator(); 437 while (it.hasNext() && list.isEmpty()) 438 { 439 final Configuration config = it.next(); 440 if (config != inMemoryConfiguration && config.containsKey(key)) 441 { 442 appendListProperty(list, config, key); 443 } 444 } 445 446 // add all elements from the in memory configuration 447 appendListProperty(list, inMemoryConfiguration, key); 448 449 if (list.isEmpty()) 450 { 451 // This is okay because we just return this list to the caller 452 @SuppressWarnings("unchecked") 453 final 454 List<Object> resultList = (List<Object>) defaultValue; 455 return resultList; 456 } 457 458 final ListIterator<Object> lit = list.listIterator(); 459 while (lit.hasNext()) 460 { 461 lit.set(interpolate(lit.next())); 462 } 463 464 return list; 465 } 466 467 @Override 468 public String[] getStringArray(final String key) 469 { 470 final List<Object> list = getList(key); 471 472 // transform property values into strings 473 final String[] tokens = new String[list.size()]; 474 475 for (int i = 0; i < tokens.length; i++) 476 { 477 tokens[i] = String.valueOf(list.get(i)); 478 } 479 480 return tokens; 481 } 482 483 /** 484 * Return the configuration at the specified index. 485 * 486 * @param index The index of the configuration to retrieve 487 * @return the configuration at this index 488 */ 489 public Configuration getConfiguration(final int index) 490 { 491 beginRead(false); 492 try 493 { 494 return configList.get(index); 495 } 496 finally 497 { 498 endRead(); 499 } 500 } 501 502 /** 503 * Returns the "in memory configuration". In this configuration 504 * changes are stored. 505 * 506 * @return the in memory configuration 507 */ 508 public Configuration getInMemoryConfiguration() 509 { 510 beginRead(false); 511 try 512 { 513 return inMemoryConfiguration; 514 } 515 finally 516 { 517 endRead(); 518 } 519 } 520 521 /** 522 * Returns a copy of this object. This implementation will create a deep 523 * clone, i.e. all configurations contained in this composite will also be 524 * cloned. This only works if all contained configurations support cloning; 525 * otherwise a runtime exception will be thrown. Registered event handlers 526 * won't get cloned. 527 * 528 * @return the copy 529 * @since 1.3 530 */ 531 @Override 532 public Object clone() 533 { 534 try 535 { 536 final CompositeConfiguration copy = (CompositeConfiguration) super 537 .clone(); 538 copy.configList = new LinkedList<>(); 539 copy.inMemoryConfiguration = ConfigurationUtils 540 .cloneConfiguration(getInMemoryConfiguration()); 541 copy.configList.add(copy.inMemoryConfiguration); 542 543 for (final Configuration config : configList) 544 { 545 if (config != getInMemoryConfiguration()) 546 { 547 copy.addConfiguration(ConfigurationUtils 548 .cloneConfiguration(config)); 549 } 550 } 551 552 copy.cloneInterpolator(this); 553 return copy; 554 } 555 catch (final CloneNotSupportedException cnex) 556 { 557 // cannot happen 558 throw new ConfigurationRuntimeException(cnex); 559 } 560 } 561 562 /** 563 * {@inheritDoc} This implementation ensures that the in memory 564 * configuration is correctly initialized. 565 */ 566 @Override 567 public void setListDelimiterHandler( 568 final ListDelimiterHandler listDelimiterHandler) 569 { 570 if (inMemoryConfiguration instanceof AbstractConfiguration) 571 { 572 ((AbstractConfiguration) inMemoryConfiguration) 573 .setListDelimiterHandler(listDelimiterHandler); 574 } 575 super.setListDelimiterHandler(listDelimiterHandler); 576 } 577 578 /** 579 * Returns the configuration source, in which the specified key is defined. 580 * This method will iterate over all existing child configurations and check 581 * whether they contain the specified key. The following constellations are 582 * possible: 583 * <ul> 584 * <li>If exactly one child configuration contains the key, this 585 * configuration is returned as the source configuration. This may be the 586 * <em>in memory configuration</em> (this has to be explicitly checked by 587 * the calling application).</li> 588 * <li>If none of the child configurations contain the key, <b>null</b> is 589 * returned.</li> 590 * <li>If the key is contained in multiple child configurations or if the 591 * key is <b>null</b>, a {@code IllegalArgumentException} is thrown. 592 * In this case the source configuration cannot be determined.</li> 593 * </ul> 594 * 595 * @param key the key to be checked 596 * @return the source configuration of this key 597 * @throws IllegalArgumentException if the source configuration cannot be 598 * determined 599 * @since 1.5 600 */ 601 public Configuration getSource(final String key) 602 { 603 if (key == null) 604 { 605 throw new IllegalArgumentException("Key must not be null!"); 606 } 607 608 Configuration source = null; 609 for (final Configuration conf : configList) 610 { 611 if (conf.containsKey(key)) 612 { 613 if (source != null) 614 { 615 throw new IllegalArgumentException("The key " + key 616 + " is defined by multiple sources!"); 617 } 618 source = conf; 619 } 620 } 621 622 return source; 623 } 624 625 /** 626 * Replaces the current in-memory configuration by the given one. 627 * 628 * @param config the new in-memory configuration 629 */ 630 private void replaceInMemoryConfiguration(final Configuration config) 631 { 632 if (!inMemoryConfigIsChild) 633 { 634 // remove current in-memory configuration 635 configList.remove(inMemoryConfiguration); 636 } 637 inMemoryConfiguration = config; 638 } 639 640 /** 641 * Adds the value of a property to the given list. This method is used by 642 * {@code getList()} for gathering property values from the child 643 * configurations. 644 * 645 * @param dest the list for collecting the data 646 * @param config the configuration to query 647 * @param key the key of the property 648 */ 649 private void appendListProperty(final List<Object> dest, final Configuration config, 650 final String key) 651 { 652 final Object value = interpolate(config.getProperty(key)); 653 if (value != null) 654 { 655 if (value instanceof Collection) 656 { 657 final Collection<?> col = (Collection<?>) value; 658 dest.addAll(col); 659 } 660 else 661 { 662 dest.add(value); 663 } 664 } 665 } 666}