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 018 package org.apache.commons.configuration; 019 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.Iterator; 023 import java.util.LinkedList; 024 import java.util.List; 025 import java.util.ListIterator; 026 027 /** 028 * This Configuration class allows you to add multiple different types of Configuration 029 * to this CompositeConfiguration. If you add Configuration1, and then Configuration2, 030 * any properties shared will mean that Configuration1 will be returned. 031 * You can add multiple different types or the same type of properties file. 032 * If Configuration1 doesn't have the property, then Configuration2 will be checked. 033 * 034 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 035 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 036 * @version $Id: CompositeConfiguration.java 705028 2008-10-15 20:33:35Z oheger $ 037 */ 038 public class CompositeConfiguration extends AbstractConfiguration 039 implements Cloneable 040 { 041 /** List holding all the configuration */ 042 private List configList = new LinkedList(); 043 044 /** 045 * Configuration that holds in memory stuff. Inserted as first so any 046 * setProperty() override anything else added. 047 */ 048 private Configuration inMemoryConfiguration; 049 050 /** 051 * Creates an empty CompositeConfiguration object which can then 052 * be added some other Configuration files 053 */ 054 public CompositeConfiguration() 055 { 056 clear(); 057 } 058 059 /** 060 * Creates a CompositeConfiguration object with a specified in memory 061 * configuration. This configuration will store any changes made to 062 * the CompositeConfiguration. 063 * 064 * @param inMemoryConfiguration the in memory configuration to use 065 */ 066 public CompositeConfiguration(Configuration inMemoryConfiguration) 067 { 068 configList.clear(); 069 this.inMemoryConfiguration = inMemoryConfiguration; 070 configList.add(inMemoryConfiguration); 071 } 072 073 /** 074 * Create a CompositeConfiguration with an empty in memory configuration 075 * and adds the collection of configurations specified. 076 * 077 * @param configurations the collection of configurations to add 078 */ 079 public CompositeConfiguration(Collection configurations) 080 { 081 this(new BaseConfiguration(), configurations); 082 } 083 084 /** 085 * Creates a CompositeConfiguration with a specified in memory 086 * configuration, and then adds the given collection of configurations. 087 * 088 * @param inMemoryConfiguration the in memory configuration to use 089 * @param configurations the collection of configurations to add 090 */ 091 public CompositeConfiguration(Configuration inMemoryConfiguration, Collection configurations) 092 { 093 this(inMemoryConfiguration); 094 095 if (configurations != null) 096 { 097 Iterator it = configurations.iterator(); 098 while (it.hasNext()) 099 { 100 addConfiguration((Configuration) it.next()); 101 } 102 } 103 } 104 105 /** 106 * Add a configuration. 107 * 108 * @param config the configuration to add 109 */ 110 public void addConfiguration(Configuration config) 111 { 112 if (!configList.contains(config)) 113 { 114 // As the inMemoryConfiguration contains all manually added keys, 115 // we must make sure that it is always last. "Normal", non composed 116 // configuration add their keys at the end of the configuration and 117 // we want to mimic this behaviour. 118 configList.add(configList.indexOf(inMemoryConfiguration), config); 119 120 if (config instanceof AbstractConfiguration) 121 { 122 ((AbstractConfiguration) config).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 123 } 124 } 125 } 126 127 /** 128 * Remove a configuration. The in memory configuration cannot be removed. 129 * 130 * @param config The configuration to remove 131 */ 132 public void removeConfiguration(Configuration config) 133 { 134 // Make sure that you can't remove the inMemoryConfiguration from 135 // the CompositeConfiguration object 136 if (!config.equals(inMemoryConfiguration)) 137 { 138 configList.remove(config); 139 } 140 } 141 142 /** 143 * Return the number of configurations. 144 * 145 * @return the number of configuration 146 */ 147 public int getNumberOfConfigurations() 148 { 149 return configList.size(); 150 } 151 152 /** 153 * Remove all configuration reinitialize the in memory configuration. 154 */ 155 public void clear() 156 { 157 configList.clear(); 158 // recreate the in memory configuration 159 inMemoryConfiguration = new BaseConfiguration(); 160 ((BaseConfiguration) inMemoryConfiguration).setThrowExceptionOnMissing(isThrowExceptionOnMissing()); 161 ((BaseConfiguration) inMemoryConfiguration).setListDelimiter(getListDelimiter()); 162 ((BaseConfiguration) inMemoryConfiguration).setDelimiterParsingDisabled(isDelimiterParsingDisabled()); 163 configList.add(inMemoryConfiguration); 164 } 165 166 /** 167 * Add this property to the inmemory Configuration. 168 * 169 * @param key The Key to add the property to. 170 * @param token The Value to add. 171 */ 172 protected void addPropertyDirect(String key, Object token) 173 { 174 inMemoryConfiguration.addProperty(key, token); 175 } 176 177 /** 178 * Read property from underlying composite 179 * 180 * @param key key to use for mapping 181 * 182 * @return object associated with the given configuration key. 183 */ 184 public Object getProperty(String key) 185 { 186 Configuration firstMatchingConfiguration = null; 187 for (Iterator i = configList.iterator(); i.hasNext();) 188 { 189 Configuration config = (Configuration) i.next(); 190 if (config.containsKey(key)) 191 { 192 firstMatchingConfiguration = config; 193 break; 194 } 195 } 196 197 if (firstMatchingConfiguration != null) 198 { 199 return firstMatchingConfiguration.getProperty(key); 200 } 201 else 202 { 203 return null; 204 } 205 } 206 207 public Iterator getKeys() 208 { 209 List keys = new ArrayList(); 210 for (Iterator i = configList.iterator(); i.hasNext();) 211 { 212 Configuration config = (Configuration) i.next(); 213 214 Iterator j = config.getKeys(); 215 while (j.hasNext()) 216 { 217 String key = (String) j.next(); 218 if (!keys.contains(key)) 219 { 220 keys.add(key); 221 } 222 } 223 } 224 225 return keys.iterator(); 226 } 227 228 public Iterator getKeys(String key) 229 { 230 List keys = new ArrayList(); 231 for (Iterator i = configList.iterator(); i.hasNext();) 232 { 233 Configuration config = (Configuration) i.next(); 234 235 Iterator j = config.getKeys(key); 236 while (j.hasNext()) 237 { 238 String newKey = (String) j.next(); 239 if (!keys.contains(newKey)) 240 { 241 keys.add(newKey); 242 } 243 } 244 } 245 246 return keys.iterator(); 247 } 248 249 public boolean isEmpty() 250 { 251 boolean isEmpty = true; 252 for (Iterator i = configList.iterator(); i.hasNext();) 253 { 254 Configuration config = (Configuration) i.next(); 255 if (!config.isEmpty()) 256 { 257 return false; 258 } 259 } 260 261 return isEmpty; 262 } 263 264 protected void clearPropertyDirect(String key) 265 { 266 for (Iterator i = configList.iterator(); i.hasNext();) 267 { 268 Configuration config = (Configuration) i.next(); 269 config.clearProperty(key); 270 } 271 } 272 273 public boolean containsKey(String key) 274 { 275 for (Iterator i = configList.iterator(); i.hasNext();) 276 { 277 Configuration config = (Configuration) i.next(); 278 if (config.containsKey(key)) 279 { 280 return true; 281 } 282 } 283 return false; 284 } 285 286 public List getList(String key, List defaultValue) 287 { 288 List list = new ArrayList(); 289 290 // add all elements from the first configuration containing the requested key 291 Iterator it = configList.iterator(); 292 while (it.hasNext() && list.isEmpty()) 293 { 294 Configuration config = (Configuration) it.next(); 295 if (config != inMemoryConfiguration && config.containsKey(key)) 296 { 297 appendListProperty(list, config, key); 298 } 299 } 300 301 // add all elements from the in memory configuration 302 appendListProperty(list, inMemoryConfiguration, key); 303 304 if (list.isEmpty()) 305 { 306 return defaultValue; 307 } 308 309 ListIterator lit = list.listIterator(); 310 while (lit.hasNext()) 311 { 312 lit.set(interpolate(lit.next())); 313 } 314 315 return list; 316 } 317 318 public String[] getStringArray(String key) 319 { 320 List list = getList(key); 321 322 // transform property values into strings 323 String[] tokens = new String[list.size()]; 324 325 for (int i = 0; i < tokens.length; i++) 326 { 327 tokens[i] = String.valueOf(list.get(i)); 328 } 329 330 return tokens; 331 } 332 333 /** 334 * Return the configuration at the specified index. 335 * 336 * @param index The index of the configuration to retrieve 337 * @return the configuration at this index 338 */ 339 public Configuration getConfiguration(int index) 340 { 341 return (Configuration) configList.get(index); 342 } 343 344 /** 345 * Returns the "in memory configuration". In this configuration 346 * changes are stored. 347 * 348 * @return the in memory configuration 349 */ 350 public Configuration getInMemoryConfiguration() 351 { 352 return inMemoryConfiguration; 353 } 354 355 /** 356 * Returns a copy of this object. This implementation will create a deep 357 * clone, i.e. all configurations contained in this composite will also be 358 * cloned. This only works if all contained configurations support cloning; 359 * otherwise a runtime exception will be thrown. Registered event handlers 360 * won't get cloned. 361 * 362 * @return the copy 363 * @since 1.3 364 */ 365 public Object clone() 366 { 367 try 368 { 369 CompositeConfiguration copy = (CompositeConfiguration) super 370 .clone(); 371 copy.clearConfigurationListeners(); 372 copy.configList = new LinkedList(); 373 copy.inMemoryConfiguration = ConfigurationUtils 374 .cloneConfiguration(getInMemoryConfiguration()); 375 copy.configList.add(copy.inMemoryConfiguration); 376 377 for (int i = 0; i < getNumberOfConfigurations(); i++) 378 { 379 Configuration config = getConfiguration(i); 380 if (config != getInMemoryConfiguration()) 381 { 382 copy.addConfiguration(ConfigurationUtils 383 .cloneConfiguration(config)); 384 } 385 } 386 387 return copy; 388 } 389 catch (CloneNotSupportedException cnex) 390 { 391 // cannot happen 392 throw new ConfigurationRuntimeException(cnex); 393 } 394 } 395 396 /** 397 * Sets a flag whether added values for string properties should be checked 398 * for the list delimiter. This implementation ensures that the in memory 399 * configuration is correctly initialized. 400 * 401 * @param delimiterParsingDisabled the new value of the flag 402 * @since 1.4 403 */ 404 public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled) 405 { 406 ((BaseConfiguration) getInMemoryConfiguration()) 407 .setDelimiterParsingDisabled(delimiterParsingDisabled); 408 super.setDelimiterParsingDisabled(delimiterParsingDisabled); 409 } 410 411 /** 412 * Sets the character that is used as list delimiter. This implementation 413 * ensures that the in memory configuration is correctly initialized. 414 * 415 * @param listDelimiter the new list delimiter character 416 * @since 1.4 417 */ 418 public void setListDelimiter(char listDelimiter) 419 { 420 ((BaseConfiguration) getInMemoryConfiguration()) 421 .setListDelimiter(listDelimiter); 422 super.setListDelimiter(listDelimiter); 423 } 424 425 /** 426 * Returns the configuration source, in which the specified key is defined. 427 * This method will iterate over all existing child configurations and check 428 * whether they contain the specified key. The following constellations are 429 * possible: 430 * <ul> 431 * <li>If exactly one child configuration contains the key, this 432 * configuration is returned as the source configuration. This may be the 433 * <em>in memory configuration</em> (this has to be explicitly checked by 434 * the calling application).</li> 435 * <li>If none of the child configurations contain the key, <b>null</b> is 436 * returned.</li> 437 * <li>If the key is contained in multiple child configurations or if the 438 * key is <b>null</b>, a <code>IllegalArgumentException</code> is thrown. 439 * In this case the source configuration cannot be determined.</li> 440 * </ul> 441 * 442 * @param key the key to be checked 443 * @return the source configuration of this key 444 * @throws IllegalArgumentException if the source configuration cannot be 445 * determined 446 * @since 1.5 447 */ 448 public Configuration getSource(String key) 449 { 450 if (key == null) 451 { 452 throw new IllegalArgumentException("Key must not be null!"); 453 } 454 455 Configuration source = null; 456 for (Iterator it = configList.iterator(); it.hasNext();) 457 { 458 Configuration conf = (Configuration) it.next(); 459 if (conf.containsKey(key)) 460 { 461 if (source != null) 462 { 463 throw new IllegalArgumentException("The key " + key 464 + " is defined by multiple sources!"); 465 } 466 source = conf; 467 } 468 } 469 470 return source; 471 } 472 473 /** 474 * Adds the value of a property to the given list. This method is used by 475 * <code>getList()</code> for gathering property values from the child 476 * configurations. 477 * 478 * @param dest the list for collecting the data 479 * @param config the configuration to query 480 * @param key the key of the property 481 */ 482 private static void appendListProperty(List dest, Configuration config, 483 String key) 484 { 485 Object value = config.getProperty(key); 486 if (value != null) 487 { 488 if (value instanceof Collection) 489 { 490 dest.addAll((Collection) value); 491 } 492 else 493 { 494 dest.add(value); 495 } 496 } 497 } 498 }