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