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