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 1842194 2018-09-27 22:24:23Z ggregory $ 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(final 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(final 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(final Configuration inMemoryConfiguration, 136 final Collection<? extends Configuration> configurations) 137 { 138 this(inMemoryConfiguration); 139 140 if (configurations != null) 141 { 142 for (final 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(final 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(final Configuration config, final 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(final 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(final Configuration config, final 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(final 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(final String key, final 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(final String key) 347 { 348 Configuration firstMatchingConfiguration = null; 349 for (final 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 return null; 363 } 364 365 @Override 366 protected Iterator<String> getKeysInternal() 367 { 368 final Set<String> keys = new LinkedHashSet<>(); 369 for (final Configuration config : configList) 370 { 371 for (final Iterator<String> it = config.getKeys(); it.hasNext();) 372 { 373 keys.add(it.next()); 374 } 375 } 376 377 return keys.iterator(); 378 } 379 380 @Override 381 protected Iterator<String> getKeysInternal(final String key) 382 { 383 final Set<String> keys = new LinkedHashSet<>(); 384 for (final Configuration config : configList) 385 { 386 for (final Iterator<String> it = config.getKeys(key); it.hasNext();) 387 { 388 keys.add(it.next()); 389 } 390 } 391 392 return keys.iterator(); 393 } 394 395 @Override 396 protected boolean isEmptyInternal() 397 { 398 for (final Configuration config : configList) 399 { 400 if (!config.isEmpty()) 401 { 402 return false; 403 } 404 } 405 406 return true; 407 } 408 409 @Override 410 protected void clearPropertyDirect(final String key) 411 { 412 for (final Configuration config : configList) 413 { 414 config.clearProperty(key); 415 } 416 } 417 418 @Override 419 protected boolean containsKeyInternal(final String key) 420 { 421 for (final Configuration config : configList) 422 { 423 if (config.containsKey(key)) 424 { 425 return true; 426 } 427 } 428 return false; 429 } 430 431 @Override 432 public List<Object> getList(final String key, final List<?> defaultValue) 433 { 434 final List<Object> list = new ArrayList<>(); 435 436 // add all elements from the first configuration containing the requested key 437 final Iterator<Configuration> it = configList.iterator(); 438 while (it.hasNext() && list.isEmpty()) 439 { 440 final Configuration config = it.next(); 441 if (config != inMemoryConfiguration && config.containsKey(key)) 442 { 443 appendListProperty(list, config, key); 444 } 445 } 446 447 // add all elements from the in memory configuration 448 appendListProperty(list, inMemoryConfiguration, key); 449 450 if (list.isEmpty()) 451 { 452 // This is okay because we just return this list to the caller 453 @SuppressWarnings("unchecked") 454 final 455 List<Object> resultList = (List<Object>) defaultValue; 456 return resultList; 457 } 458 459 final ListIterator<Object> lit = list.listIterator(); 460 while (lit.hasNext()) 461 { 462 lit.set(interpolate(lit.next())); 463 } 464 465 return list; 466 } 467 468 @Override 469 public String[] getStringArray(final String key) 470 { 471 final List<Object> list = getList(key); 472 473 // transform property values into strings 474 final String[] tokens = new String[list.size()]; 475 476 for (int i = 0; i < tokens.length; i++) 477 { 478 tokens[i] = String.valueOf(list.get(i)); 479 } 480 481 return tokens; 482 } 483 484 /** 485 * Return the configuration at the specified index. 486 * 487 * @param index The index of the configuration to retrieve 488 * @return the configuration at this index 489 */ 490 public Configuration getConfiguration(final int index) 491 { 492 beginRead(false); 493 try 494 { 495 return configList.get(index); 496 } 497 finally 498 { 499 endRead(); 500 } 501 } 502 503 /** 504 * Returns the "in memory configuration". In this configuration 505 * changes are stored. 506 * 507 * @return the in memory configuration 508 */ 509 public Configuration getInMemoryConfiguration() 510 { 511 beginRead(false); 512 try 513 { 514 return inMemoryConfiguration; 515 } 516 finally 517 { 518 endRead(); 519 } 520 } 521 522 /** 523 * Returns a copy of this object. This implementation will create a deep 524 * clone, i.e. all configurations contained in this composite will also be 525 * cloned. This only works if all contained configurations support cloning; 526 * otherwise a runtime exception will be thrown. Registered event handlers 527 * won't get cloned. 528 * 529 * @return the copy 530 * @since 1.3 531 */ 532 @Override 533 public Object clone() 534 { 535 try 536 { 537 final CompositeConfiguration copy = (CompositeConfiguration) super 538 .clone(); 539 copy.configList = new LinkedList<>(); 540 copy.inMemoryConfiguration = ConfigurationUtils 541 .cloneConfiguration(getInMemoryConfiguration()); 542 copy.configList.add(copy.inMemoryConfiguration); 543 544 for (final Configuration config : configList) 545 { 546 if (config != getInMemoryConfiguration()) 547 { 548 copy.addConfiguration(ConfigurationUtils 549 .cloneConfiguration(config)); 550 } 551 } 552 553 copy.cloneInterpolator(this); 554 return copy; 555 } 556 catch (final CloneNotSupportedException cnex) 557 { 558 // cannot happen 559 throw new ConfigurationRuntimeException(cnex); 560 } 561 } 562 563 /** 564 * {@inheritDoc} This implementation ensures that the in memory 565 * configuration is correctly initialized. 566 */ 567 @Override 568 public void setListDelimiterHandler( 569 final ListDelimiterHandler listDelimiterHandler) 570 { 571 if (inMemoryConfiguration instanceof AbstractConfiguration) 572 { 573 ((AbstractConfiguration) inMemoryConfiguration) 574 .setListDelimiterHandler(listDelimiterHandler); 575 } 576 super.setListDelimiterHandler(listDelimiterHandler); 577 } 578 579 /** 580 * Returns the configuration source, in which the specified key is defined. 581 * This method will iterate over all existing child configurations and check 582 * whether they contain the specified key. The following constellations are 583 * possible: 584 * <ul> 585 * <li>If exactly one child configuration contains the key, this 586 * configuration is returned as the source configuration. This may be the 587 * <em>in memory configuration</em> (this has to be explicitly checked by 588 * the calling application).</li> 589 * <li>If none of the child configurations contain the key, <b>null</b> is 590 * returned.</li> 591 * <li>If the key is contained in multiple child configurations or if the 592 * key is <b>null</b>, a {@code IllegalArgumentException} is thrown. 593 * In this case the source configuration cannot be determined.</li> 594 * </ul> 595 * 596 * @param key the key to be checked 597 * @return the source configuration of this key 598 * @throws IllegalArgumentException if the source configuration cannot be 599 * determined 600 * @since 1.5 601 */ 602 public Configuration getSource(final String key) 603 { 604 if (key == null) 605 { 606 throw new IllegalArgumentException("Key must not be null!"); 607 } 608 609 Configuration source = null; 610 for (final Configuration conf : configList) 611 { 612 if (conf.containsKey(key)) 613 { 614 if (source != null) 615 { 616 throw new IllegalArgumentException("The key " + key 617 + " is defined by multiple sources!"); 618 } 619 source = conf; 620 } 621 } 622 623 return source; 624 } 625 626 /** 627 * Replaces the current in-memory configuration by the given one. 628 * 629 * @param config the new in-memory configuration 630 */ 631 private void replaceInMemoryConfiguration(final Configuration config) 632 { 633 if (!inMemoryConfigIsChild) 634 { 635 // remove current in-memory configuration 636 configList.remove(inMemoryConfiguration); 637 } 638 inMemoryConfiguration = config; 639 } 640 641 /** 642 * Adds the value of a property to the given list. This method is used by 643 * {@code getList()} for gathering property values from the child 644 * configurations. 645 * 646 * @param dest the list for collecting the data 647 * @param config the configuration to query 648 * @param key the key of the property 649 */ 650 private void appendListProperty(final List<Object> dest, final Configuration config, 651 final String key) 652 { 653 final Object value = interpolate(config.getProperty(key)); 654 if (value != null) 655 { 656 if (value instanceof Collection) 657 { 658 final Collection<?> col = (Collection<?>) value; 659 dest.addAll(col); 660 } 661 else 662 { 663 dest.add(value); 664 } 665 } 666 } 667}