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 */ 062public class CompositeConfiguration extends AbstractConfiguration implements Cloneable { 063 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 * Create a CompositeConfiguration with an empty in memory configuration and adds the collection of configurations 086 * specified. 087 * 088 * @param configurations the collection of configurations to add 089 */ 090 public CompositeConfiguration(final Collection<? extends Configuration> configurations) { 091 this(new BaseConfiguration(), configurations); 092 } 093 094 /** 095 * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will 096 * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special 097 * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as 098 * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <b>true</b> instead. 099 * 100 * @param inMemoryConfiguration the in memory configuration to use 101 */ 102 public CompositeConfiguration(final Configuration inMemoryConfiguration) { 103 this.configList.clear(); 104 this.inMemoryConfiguration = inMemoryConfiguration; 105 this.configList.add(inMemoryConfiguration); 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 if (configurations != null) { 119 configurations.forEach(this::addConfiguration); 120 } 121 } 122 123 /** 124 * Add a configuration. 125 * 126 * @param config the configuration to add 127 */ 128 public void addConfiguration(final Configuration config) { 129 addConfiguration(config, false); 130 } 131 132 /** 133 * Adds a child configuration and optionally makes it the <em>in-memory configuration</em>. This means that all future 134 * property write operations are executed on this configuration. Note that the current in-memory configuration is 135 * replaced by the new one. If it was created automatically or passed to the constructor, it is removed from the list of 136 * child configurations! Otherwise, it stays in the list of child configurations at its current position, but it passes 137 * its role as in-memory configuration to the new one. 138 * 139 * @param config the configuration to be added 140 * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b> 141 * otherwise 142 * @since 1.8 143 */ 144 public void addConfiguration(final Configuration config, final boolean asInMemory) { 145 beginWrite(false); 146 try { 147 if (!configList.contains(config)) { 148 if (asInMemory) { 149 replaceInMemoryConfiguration(config); 150 inMemoryConfigIsChild = true; 151 } 152 153 if (!inMemoryConfigIsChild) { 154 // As the inMemoryConfiguration contains all manually added 155 // keys, we must make sure that it is always last. "Normal", non 156 // composed configurations add their keys at the end of the 157 // configuration and we want to mimic this behavior. 158 configList.add(configList.indexOf(inMemoryConfiguration), config); 159 } else { 160 // However, if the in-memory configuration is a regular child, 161 // only the order in which child configurations are added is relevant 162 configList.add(config); 163 } 164 165 if (config instanceof AbstractConfiguration) { 166 ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 167 } 168 } 169 } finally { 170 endWrite(); 171 } 172 } 173 174 /** 175 * Add a configuration to the start of the list of child configurations. 176 * 177 * @param config the configuration to add 178 * @since 2.3 179 */ 180 public void addConfigurationFirst(final Configuration config) { 181 addConfigurationFirst(config, false); 182 } 183 184 /** 185 * Adds a child configuration to the start of the collection and optionally makes it the <em>in-memory 186 * configuration</em>. This means that all future property write operations are executed on this configuration. Note 187 * that the current in-memory configuration is replaced by the new one. If it was created automatically or passed to the 188 * constructor, it is removed from the list of child configurations! Otherwise, it stays in the list of child 189 * configurations at its current position, but it passes its role as in-memory configuration to the new one. 190 * 191 * @param config the configuration to be added 192 * @param asInMemory <b>true</b> if this configuration becomes the new <em>in-memory</em> configuration, <b>false</b> 193 * otherwise 194 * @since 2.3 195 */ 196 public void addConfigurationFirst(final Configuration config, final boolean asInMemory) { 197 beginWrite(false); 198 try { 199 if (!configList.contains(config)) { 200 if (asInMemory) { 201 replaceInMemoryConfiguration(config); 202 inMemoryConfigIsChild = true; 203 } 204 configList.add(0, config); 205 206 if (config instanceof AbstractConfiguration) { 207 ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 208 } 209 } 210 } finally { 211 endWrite(); 212 } 213 } 214 215 /** 216 * Add this property to the in-memory Configuration. 217 * 218 * @param key The Key to add the property to. 219 * @param token The Value to add. 220 */ 221 @Override 222 protected void addPropertyDirect(final String key, final Object token) { 223 inMemoryConfiguration.addProperty(key, token); 224 } 225 226 /** 227 * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property 228 * values from the child configurations. 229 * 230 * @param dest the list for collecting the data 231 * @param config the configuration to query 232 * @param key the key of the property 233 */ 234 private void appendListProperty(final List<Object> dest, final Configuration config, final String key) { 235 final Object value = interpolate(config.getProperty(key)); 236 if (value != null) { 237 if (value instanceof Collection) { 238 final Collection<?> col = (Collection<?>) value; 239 dest.addAll(col); 240 } else { 241 dest.add(value); 242 } 243 } 244 } 245 246 /** 247 * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong> 248 * A new in-memory configuration is created; the old one is lost. 249 */ 250 @Override 251 protected void clearInternal() { 252 configList.clear(); 253 // recreate the in memory configuration 254 inMemoryConfiguration = new BaseConfiguration(); 255 ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 256 ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler()); 257 configList.add(inMemoryConfiguration); 258 inMemoryConfigIsChild = false; 259 } 260 261 @Override 262 protected void clearPropertyDirect(final String key) { 263 configList.forEach(config -> config.clearProperty(key)); 264 } 265 266 /** 267 * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in 268 * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a 269 * runtime exception will be thrown. Registered event handlers won't get cloned. 270 * 271 * @return the copy 272 * @since 1.3 273 */ 274 @Override 275 public Object clone() { 276 try { 277 final CompositeConfiguration copy = (CompositeConfiguration) super.clone(); 278 copy.configList = new LinkedList<>(); 279 copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration()); 280 copy.configList.add(copy.inMemoryConfiguration); 281 282 configList.forEach(config -> { 283 if (config != getInMemoryConfiguration()) { 284 copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config)); 285 } 286 }); 287 288 copy.cloneInterpolator(this); 289 return copy; 290 } catch (final CloneNotSupportedException cnex) { 291 // cannot happen 292 throw new ConfigurationRuntimeException(cnex); 293 } 294 } 295 296 @Override 297 protected boolean containsKeyInternal(final String key) { 298 return configList.stream().anyMatch(config -> config.containsKey(key)); 299 } 300 301 /** 302 * Tests whether this configuration contains one or more matches to this value. This operation stops at first 303 * match but may be more expensive than the containsKey method. 304 * @since 2.11.0 305 */ 306 @Override 307 protected boolean containsValueInternal(final Object value) { 308 return configList.stream().anyMatch(config -> config.containsValue(value)); 309 } 310 311 /** 312 * Gets the configuration at the specified index. 313 * 314 * @param index The index of the configuration to retrieve 315 * @return the configuration at this index 316 */ 317 public Configuration getConfiguration(final int index) { 318 beginRead(false); 319 try { 320 return configList.get(index); 321 } finally { 322 endRead(); 323 } 324 } 325 326 /** 327 * Gets the "in memory configuration". In this configuration changes are stored. 328 * 329 * @return the in memory configuration 330 */ 331 public Configuration getInMemoryConfiguration() { 332 beginRead(false); 333 try { 334 return inMemoryConfiguration; 335 } finally { 336 endRead(); 337 } 338 } 339 340 @Override 341 protected Iterator<String> getKeysInternal() { 342 final Set<String> keys = new LinkedHashSet<>(); 343 configList.forEach(config -> config.getKeys().forEachRemaining(keys::add)); 344 return keys.iterator(); 345 } 346 347 @Override 348 protected Iterator<String> getKeysInternal(final String key) { 349 final Set<String> keys = new LinkedHashSet<>(); 350 configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add)); 351 return keys.iterator(); 352 } 353 354 @Override 355 public List<Object> getList(final String key, final List<?> defaultValue) { 356 final List<Object> list = new ArrayList<>(); 357 358 // add all elements from the first configuration containing the requested key 359 final Iterator<Configuration> it = configList.iterator(); 360 while (it.hasNext() && list.isEmpty()) { 361 final Configuration config = it.next(); 362 if (config != inMemoryConfiguration && config.containsKey(key)) { 363 appendListProperty(list, config, key); 364 } 365 } 366 367 // add all elements from the in memory configuration 368 appendListProperty(list, inMemoryConfiguration, key); 369 370 if (list.isEmpty()) { 371 // This is okay because we just return this list to the caller 372 @SuppressWarnings("unchecked") 373 final List<Object> resultList = (List<Object>) defaultValue; 374 return resultList; 375 } 376 377 final ListIterator<Object> lit = list.listIterator(); 378 while (lit.hasNext()) { 379 lit.set(interpolate(lit.next())); 380 } 381 382 return list; 383 } 384 385 /** 386 * Gets the number of configurations. 387 * 388 * @return the number of configuration 389 */ 390 public int getNumberOfConfigurations() { 391 beginRead(false); 392 try { 393 return configList.size(); 394 } finally { 395 endRead(); 396 } 397 } 398 399 /** 400 * Read property from underlying composite 401 * 402 * @param key key to use for mapping 403 * 404 * @return object associated with the given configuration key. 405 */ 406 @Override 407 protected Object getPropertyInternal(final String key) { 408 return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null); 409 } 410 411 /** 412 * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing 413 * child configurations and check whether they contain the specified key. The following constellations are possible: 414 * <ul> 415 * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration. 416 * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li> 417 * <li>If none of the child configurations contain the key, <b>null</b> is returned.</li> 418 * <li>If the key is contained in multiple child configurations or if the key is <b>null</b>, a 419 * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li> 420 * </ul> 421 * 422 * @param key the key to be checked 423 * @return the source configuration of this key 424 * @throws IllegalArgumentException if the source configuration cannot be determined 425 * @since 1.5 426 */ 427 public Configuration getSource(final String key) { 428 if (key == null) { 429 throw new IllegalArgumentException("Key must not be null!"); 430 } 431 432 Configuration source = null; 433 for (final Configuration conf : configList) { 434 if (conf.containsKey(key)) { 435 if (source != null) { 436 throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!"); 437 } 438 source = conf; 439 } 440 } 441 442 return source; 443 } 444 445 @Override 446 public String[] getStringArray(final String key) { 447 final List<Object> list = getList(key); 448 449 // transform property values into strings 450 final String[] tokens = new String[list.size()]; 451 452 for (int i = 0; i < tokens.length; i++) { 453 tokens[i] = String.valueOf(list.get(i)); 454 } 455 456 return tokens; 457 } 458 459 @Override 460 protected boolean isEmptyInternal() { 461 return configList.stream().allMatch(Configuration::isEmpty); 462 } 463 464 /** 465 * Remove a configuration. The in memory configuration cannot be removed. 466 * 467 * @param config The configuration to remove 468 */ 469 public void removeConfiguration(final Configuration config) { 470 beginWrite(false); 471 try { 472 // Make sure that you can't remove the inMemoryConfiguration from 473 // the CompositeConfiguration object 474 if (!config.equals(inMemoryConfiguration)) { 475 configList.remove(config); 476 } 477 } finally { 478 endWrite(); 479 } 480 } 481 482 /** 483 * Replaces the current in-memory configuration by the given one. 484 * 485 * @param config the new in-memory configuration 486 */ 487 private void replaceInMemoryConfiguration(final Configuration config) { 488 if (!inMemoryConfigIsChild) { 489 // remove current in-memory configuration 490 configList.remove(inMemoryConfiguration); 491 } 492 inMemoryConfiguration = config; 493 } 494 495 /** 496 * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized. 497 */ 498 @Override 499 public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { 500 if (inMemoryConfiguration instanceof AbstractConfiguration) { 501 ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler); 502 } 503 super.setListDelimiterHandler(listDelimiterHandler); 504 } 505}