1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.configuration2; 19 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.Iterator; 23 import java.util.LinkedHashSet; 24 import java.util.LinkedList; 25 import java.util.List; 26 import java.util.ListIterator; 27 import java.util.Set; 28 29 import org.apache.commons.configuration2.convert.ListDelimiterHandler; 30 import org.apache.commons.configuration2.ex.ConfigurationRuntimeException; 31 32 /** 33 * <p> 34 * {@code CompositeConfiguration} allows you to add multiple {@code Configuration} objects to an aggregated 35 * configuration. If you add Configuration1, and then Configuration2, any properties shared will mean that the value 36 * defined by Configuration1 will be returned. If Configuration1 doesn't have the property, then Configuration2 will be 37 * checked. You can add multiple different types or the same type of properties file. 38 * </p> 39 * <p> 40 * When querying properties the order in which child configurations have been added is relevant. To deal with property 41 * updates, a so-called <em>in-memory configuration</em> is used. Per default, such a configuration is created 42 * automatically. All property writes target this special configuration. There are constructors which allow you to 43 * provide a specific in-memory configuration. If used that way, the in-memory configuration is always the last one in 44 * the list of child configurations. This means that for query operations all other configurations take precedence. 45 * </p> 46 * <p> 47 * Alternatively it is possible to mark a child configuration as in-memory configuration when it is added. In this case 48 * the treatment of the in-memory configuration is slightly different: it remains in the list of child configurations at 49 * the position it was added, i.e. its priority for property queries can be defined by adding the child configurations 50 * in the correct order. 51 * </p> 52 * <p> 53 * This configuration class uses a {@code Synchronizer} to control concurrent access. While all methods for reading and 54 * writing configuration properties make use of this {@code Synchronizer} per default, the methods for managing the list 55 * of child configurations and the in-memory configuration 56 * ({@code addConfiguration(), getNumberOfConfigurations(), removeConfiguration(), 57 * getConfiguration(), getInMemoryConfiguration()}) are guarded, too. Because most methods for accessing configuration 58 * data delegate to the list of child configurations, the thread-safety of a {@code CompositeConfiguration} object also 59 * depends on the {@code Synchronizer} objects used by these children. 60 * </p> 61 */ 62 public class CompositeConfiguration extends AbstractConfiguration implements Cloneable { 63 64 /** List holding all the configuration */ 65 private List<Configuration> configList = new LinkedList<>(); 66 67 /** 68 * Configuration that holds in memory stuff. Inserted as first so any setProperty() override anything else added. 69 */ 70 private Configuration inMemoryConfiguration; 71 72 /** 73 * Stores a flag whether the current in-memory configuration is also a child configuration. 74 */ 75 private boolean inMemoryConfigIsChild; 76 77 /** 78 * Creates an empty CompositeConfiguration object which can then be added some other Configuration files 79 */ 80 public CompositeConfiguration() { 81 clear(); 82 } 83 84 /** 85 * Creates a CompositeConfiguration object with a specified <em>in-memory configuration</em>. This configuration will 86 * store any changes made to the {@code CompositeConfiguration}. Note: Use this constructor if you want to set a special 87 * type of in-memory configuration. If you have a configuration which should act as both a child configuration and as 88 * in-memory configuration, use {@link #addConfiguration(Configuration, boolean)} with a value of <b>true</b> instead. 89 * 90 * @param inMemoryConfiguration the in memory configuration to use 91 */ 92 public CompositeConfiguration(final Configuration inMemoryConfiguration) { 93 this.configList.clear(); 94 this.inMemoryConfiguration = inMemoryConfiguration; 95 this.configList.add(inMemoryConfiguration); 96 } 97 98 /** 99 * 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 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 * Remove a configuration. The in memory configuration cannot be removed. 217 * 218 * @param config The configuration to remove 219 */ 220 public void removeConfiguration(final Configuration config) { 221 beginWrite(false); 222 try { 223 // Make sure that you can't remove the inMemoryConfiguration from 224 // the CompositeConfiguration object 225 if (!config.equals(inMemoryConfiguration)) { 226 configList.remove(config); 227 } 228 } finally { 229 endWrite(); 230 } 231 } 232 233 /** 234 * Gets the number of configurations. 235 * 236 * @return the number of configuration 237 */ 238 public int getNumberOfConfigurations() { 239 beginRead(false); 240 try { 241 return configList.size(); 242 } finally { 243 endRead(); 244 } 245 } 246 247 /** 248 * Removes all child configurations and reinitializes the <em>in-memory configuration</em>. <strong>Attention:</strong> 249 * A new in-memory configuration is created; the old one is lost. 250 */ 251 @Override 252 protected void clearInternal() { 253 configList.clear(); 254 // recreate the in memory configuration 255 inMemoryConfiguration = new BaseConfiguration(); 256 ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 257 ((BaseConfiguration) inMemoryConfiguration).setListDelimiterHandler(getListDelimiterHandler()); 258 configList.add(inMemoryConfiguration); 259 inMemoryConfigIsChild = false; 260 } 261 262 /** 263 * Add this property to the in-memory Configuration. 264 * 265 * @param key The Key to add the property to. 266 * @param token The Value to add. 267 */ 268 @Override 269 protected void addPropertyDirect(final String key, final Object token) { 270 inMemoryConfiguration.addProperty(key, token); 271 } 272 273 /** 274 * Read property from underlying composite 275 * 276 * @param key key to use for mapping 277 * 278 * @return object associated with the given configuration key. 279 */ 280 @Override 281 protected Object getPropertyInternal(final String key) { 282 return configList.stream().filter(config -> config.containsKey(key)).findFirst().map(config -> config.getProperty(key)).orElse(null); 283 } 284 285 @Override 286 protected Iterator<String> getKeysInternal() { 287 final Set<String> keys = new LinkedHashSet<>(); 288 configList.forEach(config -> config.getKeys().forEachRemaining(keys::add)); 289 return keys.iterator(); 290 } 291 292 @Override 293 protected Iterator<String> getKeysInternal(final String key) { 294 final Set<String> keys = new LinkedHashSet<>(); 295 configList.forEach(config -> config.getKeys(key).forEachRemaining(keys::add)); 296 return keys.iterator(); 297 } 298 299 @Override 300 protected boolean isEmptyInternal() { 301 return configList.stream().allMatch(Configuration::isEmpty); 302 } 303 304 @Override 305 protected void clearPropertyDirect(final String key) { 306 configList.forEach(config -> config.clearProperty(key)); 307 } 308 309 @Override 310 protected boolean containsKeyInternal(final String key) { 311 return configList.stream().anyMatch(config -> config.containsKey(key)); 312 } 313 314 @Override 315 public List<Object> getList(final String key, final List<?> defaultValue) { 316 final List<Object> list = new ArrayList<>(); 317 318 // add all elements from the first configuration containing the requested key 319 final Iterator<Configuration> it = configList.iterator(); 320 while (it.hasNext() && list.isEmpty()) { 321 final Configuration config = it.next(); 322 if (config != inMemoryConfiguration && config.containsKey(key)) { 323 appendListProperty(list, config, key); 324 } 325 } 326 327 // add all elements from the in memory configuration 328 appendListProperty(list, inMemoryConfiguration, key); 329 330 if (list.isEmpty()) { 331 // This is okay because we just return this list to the caller 332 @SuppressWarnings("unchecked") 333 final List<Object> resultList = (List<Object>) defaultValue; 334 return resultList; 335 } 336 337 final ListIterator<Object> lit = list.listIterator(); 338 while (lit.hasNext()) { 339 lit.set(interpolate(lit.next())); 340 } 341 342 return list; 343 } 344 345 @Override 346 public String[] getStringArray(final String key) { 347 final List<Object> list = getList(key); 348 349 // transform property values into strings 350 final String[] tokens = new String[list.size()]; 351 352 for (int i = 0; i < tokens.length; i++) { 353 tokens[i] = String.valueOf(list.get(i)); 354 } 355 356 return tokens; 357 } 358 359 /** 360 * Gets the configuration at the specified index. 361 * 362 * @param index The index of the configuration to retrieve 363 * @return the configuration at this index 364 */ 365 public Configuration getConfiguration(final int index) { 366 beginRead(false); 367 try { 368 return configList.get(index); 369 } finally { 370 endRead(); 371 } 372 } 373 374 /** 375 * Gets the "in memory configuration". In this configuration changes are stored. 376 * 377 * @return the in memory configuration 378 */ 379 public Configuration getInMemoryConfiguration() { 380 beginRead(false); 381 try { 382 return inMemoryConfiguration; 383 } finally { 384 endRead(); 385 } 386 } 387 388 /** 389 * Returns a copy of this object. This implementation will create a deep clone, i.e. all configurations contained in 390 * this composite will also be cloned. This only works if all contained configurations support cloning; otherwise a 391 * runtime exception will be thrown. Registered event handlers won't get cloned. 392 * 393 * @return the copy 394 * @since 1.3 395 */ 396 @Override 397 public Object clone() { 398 try { 399 final CompositeConfiguration copy = (CompositeConfiguration) super.clone(); 400 copy.configList = new LinkedList<>(); 401 copy.inMemoryConfiguration = ConfigurationUtils.cloneConfiguration(getInMemoryConfiguration()); 402 copy.configList.add(copy.inMemoryConfiguration); 403 404 configList.forEach(config -> { 405 if (config != getInMemoryConfiguration()) { 406 copy.addConfiguration(ConfigurationUtils.cloneConfiguration(config)); 407 } 408 }); 409 410 copy.cloneInterpolator(this); 411 return copy; 412 } catch (final CloneNotSupportedException cnex) { 413 // cannot happen 414 throw new ConfigurationRuntimeException(cnex); 415 } 416 } 417 418 /** 419 * {@inheritDoc} This implementation ensures that the in memory configuration is correctly initialized. 420 */ 421 @Override 422 public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { 423 if (inMemoryConfiguration instanceof AbstractConfiguration) { 424 ((AbstractConfiguration) inMemoryConfiguration).setListDelimiterHandler(listDelimiterHandler); 425 } 426 super.setListDelimiterHandler(listDelimiterHandler); 427 } 428 429 /** 430 * Gets the configuration source, in which the specified key is defined. This method will iterate over all existing 431 * child configurations and check whether they contain the specified key. The following constellations are possible: 432 * <ul> 433 * <li>If exactly one child configuration contains the key, this configuration is returned as the source configuration. 434 * This may be the <em>in memory configuration</em> (this has to be explicitly checked by the calling application).</li> 435 * <li>If none of the child configurations contain the key, <b>null</b> is returned.</li> 436 * <li>If the key is contained in multiple child configurations or if the key is <b>null</b>, a 437 * {@code IllegalArgumentException} is thrown. In this case the source configuration cannot be determined.</li> 438 * </ul> 439 * 440 * @param key the key to be checked 441 * @return the source configuration of this key 442 * @throws IllegalArgumentException if the source configuration cannot be determined 443 * @since 1.5 444 */ 445 public Configuration getSource(final String key) { 446 if (key == null) { 447 throw new IllegalArgumentException("Key must not be null!"); 448 } 449 450 Configuration source = null; 451 for (final Configuration conf : configList) { 452 if (conf.containsKey(key)) { 453 if (source != null) { 454 throw new IllegalArgumentException("The key " + key + " is defined by multiple sources!"); 455 } 456 source = conf; 457 } 458 } 459 460 return source; 461 } 462 463 /** 464 * Replaces the current in-memory configuration by the given one. 465 * 466 * @param config the new in-memory configuration 467 */ 468 private void replaceInMemoryConfiguration(final Configuration config) { 469 if (!inMemoryConfigIsChild) { 470 // remove current in-memory configuration 471 configList.remove(inMemoryConfiguration); 472 } 473 inMemoryConfiguration = config; 474 } 475 476 /** 477 * Adds the value of a property to the given list. This method is used by {@code getList()} for gathering property 478 * values from the child configurations. 479 * 480 * @param dest the list for collecting the data 481 * @param config the configuration to query 482 * @param key the key of the property 483 */ 484 private void appendListProperty(final List<Object> dest, final Configuration config, final String key) { 485 final Object value = interpolate(config.getProperty(key)); 486 if (value != null) { 487 if (value instanceof Collection) { 488 final Collection<?> col = (Collection<?>) value; 489 dest.addAll(col); 490 } else { 491 dest.add(value); 492 } 493 } 494 } 495 }