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