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