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