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 package org.apache.commons.configuration; 018 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.HashMap; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Set; 026 027 import org.apache.commons.configuration.event.ConfigurationEvent; 028 import org.apache.commons.configuration.event.ConfigurationListener; 029 import org.apache.commons.configuration.tree.ConfigurationNode; 030 import org.apache.commons.configuration.tree.DefaultConfigurationKey; 031 import org.apache.commons.configuration.tree.DefaultConfigurationNode; 032 import org.apache.commons.configuration.tree.DefaultExpressionEngine; 033 import org.apache.commons.configuration.tree.ExpressionEngine; 034 import org.apache.commons.configuration.tree.NodeCombiner; 035 import org.apache.commons.configuration.tree.UnionCombiner; 036 import org.apache.commons.configuration.tree.ViewNode; 037 038 /** 039 * <p> 040 * A hierarchical composite configuration class. 041 * </p> 042 * <p> 043 * This class maintains a list of configuration objects, which can be added 044 * using the divers <code>addConfiguration()</code> methods. After that the 045 * configurations can be accessed either by name (if one was provided when the 046 * configuration was added) or by index. For the whole set of managed 047 * configurations a logical node structure is constructed. For this purpose a 048 * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code> 049 * object can be set. This makes it possible to specify different algorithms for 050 * the combination process. 051 * </p> 052 * <p> 053 * The big advantage of this class is that it creates a truly hierarchical 054 * structure of all the properties stored in the contained configurations - even 055 * if some of them are no hierarchical configurations per se. So all enhanced 056 * features provided by a hierarchical configuration (e.g. choosing an 057 * expression engine) are applicable. 058 * </p> 059 * <p> 060 * The class works by registering itself as an event listener at all added 061 * configurations. So it gets notified whenever one of these configurations is 062 * changed and can invalidate its internal node structure. The next time a 063 * property is accessed the node structure will be re-constructed using the 064 * current state of the managed configurations. Note that, depending on the used 065 * <code>NodeCombiner</code>, this may be a complex operation. 066 * </p> 067 * <p> 068 * Because of the way a <code>CombinedConfiguration</code> is working it has 069 * more or less view character: it provides a logic view on the configurations 070 * it contains. In this constellation not all methods defined for hierarchical 071 * configurations - especially methods that update the stored properties - can 072 * be implemented in a consistent manner. Using such methods (like 073 * <code>addProperty()</code>, or <code>clearProperty()</code> on a 074 * <code>CombinedConfiguration</code> is not strictly forbidden, however, 075 * depending on the current <code>{@link NodeCombiner}</code> and the involved 076 * properties, the results may be different than expected. Some examples may 077 * illustrate this: 078 * </p> 079 * <p> 080 * <ul> 081 * <li>Imagine a <code>CombinedConfiguration</code> <em>cc</em> containing 082 * two child configurations with the following content: 083 * <dl> 084 * <dt>user.properties</dt> 085 * <dd> 086 * 087 * <pre> 088 * gui.background = blue 089 * gui.position = (10, 10, 400, 200) 090 * </pre> 091 * 092 * </dd> 093 * <dt>default.properties</dt> 094 * <dd> 095 * 096 * <pre> 097 * gui.background = black 098 * gui.foreground = white 099 * home.dir = /data 100 * </pre> 101 * 102 * </dd> 103 * </dl> 104 * As a <code>NodeCombiner</code> a 105 * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code> 106 * is used. This combiner will ensure that defined user settings take precedence 107 * over the default values. If the resulting <code>CombinedConfiguration</code> 108 * is queried for the background color, <code>blue</code> will be returned 109 * because this value is defined in <code>user.properties</code>. Now 110 * consider what happens if the key <code>gui.background</code> is removed 111 * from the <code>CombinedConfiguration</code>: 112 * 113 * <pre>cc.clearProperty("gui.background");</pre> 114 * 115 * Will a <code>cc.containsKey("gui.background")</code> now return <b>false</b>? 116 * No, it won't! The <code>clearProperty()</code> operation is executed on the 117 * node set of the combined configuration, which was constructed from the nodes 118 * of the two child configurations. It causes the value of the 119 * <em>background</em> node to be cleared, which is also part of the first 120 * child configuration. This modification of one of its child configurations 121 * causes the <code>CombinedConfiguration</code> to be re-constructed. This 122 * time the <code>OverrideCombiner</code> cannot find a 123 * <code>gui.background</code> property in the first child configuration, but 124 * it finds one in the second, and adds it to the resulting combined 125 * configuration. So the property is still present (with a different value now).</li> 126 * <li><code>addProperty()</code> can also be problematic: Most node 127 * combiners use special view nodes for linking parts of the original 128 * configurations' data together. If new properties are added to such a special 129 * node, they do not belong to any of the managed configurations and thus hang 130 * in the air. Using the same configurations as in the last example, the 131 * statement 132 * 133 * <pre> 134 * addProperty("database.user", "scott"); 135 * </pre> 136 * 137 * would cause such a hanging property. If now one of the child configurations 138 * is changed and the <code>CombinedConfiguration</code> is re-constructed, 139 * this property will disappear! (Add operations are not problematic if they 140 * result in a child configuration being updated. For instance an 141 * <code>addProperty("home.url", "localhost");</code> will alter the second 142 * child configuration - because the prefix <em>home</em> is here already 143 * present; when the <code>CombinedConfiguration</code> is re-constructed, 144 * this change is taken into account.)</li> 145 * </ul> 146 * Because of such problems it is recommended to perform updates only on the 147 * managed child configurations. 148 * </p> 149 * <p> 150 * Whenever the node structure of a <code>CombinedConfiguration</code> becomes 151 * invalid (either because one of the contained configurations was modified or 152 * because the <code>invalidate()</code> method was directly called) an event 153 * is generated. So this can be detected by interested event listeners. This 154 * also makes it possible to add a combined configuration into another one. 155 * </p> 156 * <p> 157 * Implementation note: Adding and removing configurations to and from a 158 * combined configuration is not thread-safe. If a combined configuration is 159 * manipulated by multiple threads, the developer has to take care about 160 * properly synchronization. 161 * </p> 162 * 163 * @author <a 164 * href="http://commons.apache.org/configuration/team-list.html">Commons 165 * Configuration team</a> 166 * @since 1.3 167 * @version $Id: CombinedConfiguration.java 712401 2008-11-08 15:29:56Z oheger $ 168 */ 169 public class CombinedConfiguration extends HierarchicalConfiguration implements 170 ConfigurationListener, Cloneable 171 { 172 /** 173 * Constant for the invalidate event that is fired when the internal node 174 * structure becomes invalid. 175 */ 176 public static final int EVENT_COMBINED_INVALIDATE = 40; 177 178 /** 179 * The serial version ID. 180 */ 181 private static final long serialVersionUID = 8338574525528692307L; 182 183 /** Constant for the expression engine for parsing the at path. */ 184 private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine(); 185 186 /** Constant for the default node combiner. */ 187 private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner(); 188 189 /** Constant for the name of the property used for the reload check.*/ 190 private static final String PROP_RELOAD_CHECK = "CombinedConfigurationReloadCheck"; 191 192 /** Stores the combiner. */ 193 private NodeCombiner nodeCombiner; 194 195 /** Stores the combined root node. */ 196 private volatile ConfigurationNode combinedRoot; 197 198 /** Stores a list with the contained configurations. */ 199 private List configurations; 200 201 /** Stores a map with the named configurations. */ 202 private Map namedConfigurations; 203 204 /** 205 * An expression engine used for converting child configurations to 206 * hierarchical ones. 207 */ 208 private ExpressionEngine conversionExpressionEngine; 209 210 /** A flag whether an enhanced reload check is to be performed.*/ 211 private boolean forceReloadCheck; 212 213 /** 214 * Creates a new instance of <code>CombinedConfiguration</code> and 215 * initializes the combiner to be used. 216 * 217 * @param comb the node combiner (can be <b>null</b>, then a union combiner 218 * is used as default) 219 */ 220 public CombinedConfiguration(NodeCombiner comb) 221 { 222 setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER); 223 clear(); 224 } 225 226 /** 227 * Creates a new instance of <code>CombinedConfiguration</code> that uses 228 * a union combiner. 229 * 230 * @see org.apache.commons.configuration.tree.UnionCombiner 231 */ 232 public CombinedConfiguration() 233 { 234 this(null); 235 } 236 237 /** 238 * Returns the node combiner that is used for creating the combined node 239 * structure. 240 * 241 * @return the node combiner 242 */ 243 public NodeCombiner getNodeCombiner() 244 { 245 return nodeCombiner; 246 } 247 248 /** 249 * Sets the node combiner. This object will be used when the combined node 250 * structure is to be constructed. It must not be <b>null</b>, otherwise an 251 * <code>IllegalArgumentException</code> exception is thrown. Changing the 252 * node combiner causes an invalidation of this combined configuration, so 253 * that the new combiner immediately takes effect. 254 * 255 * @param nodeCombiner the node combiner 256 */ 257 public void setNodeCombiner(NodeCombiner nodeCombiner) 258 { 259 if (nodeCombiner == null) 260 { 261 throw new IllegalArgumentException( 262 "Node combiner must not be null!"); 263 } 264 this.nodeCombiner = nodeCombiner; 265 invalidate(); 266 } 267 268 /** 269 * Returns a flag whether an enhanced reload check must be performed. 270 * 271 * @return the force reload check flag 272 * @since 1.4 273 */ 274 public boolean isForceReloadCheck() 275 { 276 return forceReloadCheck; 277 } 278 279 /** 280 * Sets the force reload check flag. If this flag is set, each property 281 * access on this configuration will cause a reload check on the contained 282 * configurations. This is a workaround for a problem with some reload 283 * implementations that only check if a reload is required when they are 284 * triggered. Per default this mode is disabled. If the force reload check 285 * flag is set to <b>true</b>, accessing properties will be less 286 * performant, but reloads on contained configurations will be detected. 287 * 288 * @param forceReloadCheck the value of the flag 289 * @since 1.4 290 */ 291 public void setForceReloadCheck(boolean forceReloadCheck) 292 { 293 this.forceReloadCheck = forceReloadCheck; 294 } 295 296 /** 297 * Returns the <code>ExpressionEngine</code> for converting flat child 298 * configurations to hierarchical ones. 299 * 300 * @return the conversion expression engine 301 * @since 1.6 302 */ 303 public ExpressionEngine getConversionExpressionEngine() 304 { 305 return conversionExpressionEngine; 306 } 307 308 /** 309 * Sets the <code>ExpressionEngine</code> for converting flat child 310 * configurations to hierarchical ones. When constructing the root node for 311 * this combined configuration the properties of all child configurations 312 * must be combined to a single hierarchical node structure. In this 313 * process, non hierarchical configurations are converted to hierarchical 314 * ones first. This can be problematic if a child configuration contains 315 * keys that are no compatible with the default expression engine used by 316 * hierarchical configurations. Therefore it is possible to specify a 317 * specific expression engine to be used for this purpose. 318 * 319 * @param conversionExpressionEngine the conversion expression engine 320 * @see ConfigurationUtils#convertToHierarchical(Configuration, ExpressionEngine) 321 * @since 1.6 322 */ 323 public void setConversionExpressionEngine( 324 ExpressionEngine conversionExpressionEngine) 325 { 326 this.conversionExpressionEngine = conversionExpressionEngine; 327 } 328 329 /** 330 * Adds a new configuration to this combined configuration. It is possible 331 * (but not mandatory) to give the new configuration a name. This name must 332 * be unique, otherwise a <code>ConfigurationRuntimeException</code> will 333 * be thrown. With the optional <code>at</code> argument you can specify 334 * where in the resulting node structure the content of the added 335 * configuration should appear. This is a string that uses dots as property 336 * delimiters (independent on the current expression engine). For instance 337 * if you pass in the string <code>"database.tables"</code>, 338 * all properties of the added configuration will occur in this branch. 339 * 340 * @param config the configuration to add (must not be <b>null</b>) 341 * @param name the name of this configuration (can be <b>null</b>) 342 * @param at the position of this configuration in the combined tree (can be 343 * <b>null</b>) 344 */ 345 public void addConfiguration(AbstractConfiguration config, String name, 346 String at) 347 { 348 if (config == null) 349 { 350 throw new IllegalArgumentException( 351 "Added configuration must not be null!"); 352 } 353 if (name != null && namedConfigurations.containsKey(name)) 354 { 355 throw new ConfigurationRuntimeException( 356 "A configuration with the name '" 357 + name 358 + "' already exists in this combined configuration!"); 359 } 360 361 ConfigData cd = new ConfigData(config, name, at); 362 configurations.add(cd); 363 if (name != null) 364 { 365 namedConfigurations.put(name, config); 366 } 367 368 config.addConfigurationListener(this); 369 invalidate(); 370 } 371 372 /** 373 * Adds a new configuration to this combined configuration with an optional 374 * name. The new configuration's properties will be added under the root of 375 * the combined node structure. 376 * 377 * @param config the configuration to add (must not be <b>null</b>) 378 * @param name the name of this configuration (can be <b>null</b>) 379 */ 380 public void addConfiguration(AbstractConfiguration config, String name) 381 { 382 addConfiguration(config, name, null); 383 } 384 385 /** 386 * Adds a new configuration to this combined configuration. The new 387 * configuration is not given a name. Its properties will be added under the 388 * root of the combined node structure. 389 * 390 * @param config the configuration to add (must not be <b>null</b>) 391 */ 392 public void addConfiguration(AbstractConfiguration config) 393 { 394 addConfiguration(config, null, null); 395 } 396 397 /** 398 * Returns the number of configurations that are contained in this combined 399 * configuration. 400 * 401 * @return the number of contained configurations 402 */ 403 public int getNumberOfConfigurations() 404 { 405 return configurations.size(); 406 } 407 408 /** 409 * Returns the configuration at the specified index. The contained 410 * configurations are numbered in the order they were added to this combined 411 * configuration. The index of the first configuration is 0. 412 * 413 * @param index the index 414 * @return the configuration at this index 415 */ 416 public Configuration getConfiguration(int index) 417 { 418 ConfigData cd = (ConfigData) configurations.get(index); 419 return cd.getConfiguration(); 420 } 421 422 /** 423 * Returns the configuration with the given name. This can be <b>null</b> 424 * if no such configuration exists. 425 * 426 * @param name the name of the configuration 427 * @return the configuration with this name 428 */ 429 public Configuration getConfiguration(String name) 430 { 431 return (Configuration) namedConfigurations.get(name); 432 } 433 434 /** 435 * Removes the specified configuration from this combined configuration. 436 * 437 * @param config the configuration to be removed 438 * @return a flag whether this configuration was found and could be removed 439 */ 440 public boolean removeConfiguration(Configuration config) 441 { 442 for (int index = 0; index < getNumberOfConfigurations(); index++) 443 { 444 if (((ConfigData) configurations.get(index)).getConfiguration() == config) 445 { 446 removeConfigurationAt(index); 447 return true; 448 } 449 } 450 451 return false; 452 } 453 454 /** 455 * Removes the configuration at the specified index. 456 * 457 * @param index the index 458 * @return the removed configuration 459 */ 460 public Configuration removeConfigurationAt(int index) 461 { 462 ConfigData cd = (ConfigData) configurations.remove(index); 463 if (cd.getName() != null) 464 { 465 namedConfigurations.remove(cd.getName()); 466 } 467 cd.getConfiguration().removeConfigurationListener(this); 468 invalidate(); 469 return cd.getConfiguration(); 470 } 471 472 /** 473 * Removes the configuration with the specified name. 474 * 475 * @param name the name of the configuration to be removed 476 * @return the removed configuration (<b>null</b> if this configuration 477 * was not found) 478 */ 479 public Configuration removeConfiguration(String name) 480 { 481 Configuration conf = getConfiguration(name); 482 if (conf != null) 483 { 484 removeConfiguration(conf); 485 } 486 return conf; 487 } 488 489 /** 490 * Returns a set with the names of all configurations contained in this 491 * combined configuration. Of course here are only these configurations 492 * listed, for which a name was specified when they were added. 493 * 494 * @return a set with the names of the contained configurations (never 495 * <b>null</b>) 496 */ 497 public Set getConfigurationNames() 498 { 499 return namedConfigurations.keySet(); 500 } 501 502 /** 503 * Invalidates this combined configuration. This means that the next time a 504 * property is accessed the combined node structure must be re-constructed. 505 * Invalidation of a combined configuration also means that an event of type 506 * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other 507 * events most times appear twice (once before and once after an update), 508 * this event is only fired once (after update). 509 */ 510 public void invalidate() 511 { 512 combinedRoot = null; 513 fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false); 514 } 515 516 /** 517 * Event listener call back for configuration update events. This method is 518 * called whenever one of the contained configurations was modified. It 519 * invalidates this combined configuration. 520 * 521 * @param event the update event 522 */ 523 public void configurationChanged(ConfigurationEvent event) 524 { 525 if (!event.isBeforeUpdate()) 526 { 527 invalidate(); 528 } 529 } 530 531 /** 532 * Returns the configuration root node of this combined configuration. This 533 * method will construct a combined node structure using the current node 534 * combiner if necessary. 535 * 536 * @return the combined root node 537 */ 538 public ConfigurationNode getRootNode() 539 { 540 if (combinedRoot == null) 541 { 542 combinedRoot = constructCombinedNode(); 543 } 544 return combinedRoot; 545 } 546 547 /** 548 * Clears this configuration. All contained configurations will be removed. 549 */ 550 public void clear() 551 { 552 fireEvent(EVENT_CLEAR, null, null, true); 553 configurations = new ArrayList(); 554 namedConfigurations = new HashMap(); 555 fireEvent(EVENT_CLEAR, null, null, false); 556 invalidate(); 557 } 558 559 /** 560 * Returns a copy of this object. This implementation performs a deep clone, 561 * i.e. all contained configurations will be cloned, too. For this to work, 562 * all contained configurations must be cloneable. Registered event 563 * listeners won't be cloned. The clone will use the same node combiner than 564 * the original. 565 * 566 * @return the copied object 567 */ 568 public Object clone() 569 { 570 CombinedConfiguration copy = (CombinedConfiguration) super.clone(); 571 copy.clear(); 572 for (Iterator it = configurations.iterator(); it.hasNext();) 573 { 574 ConfigData cd = (ConfigData) it.next(); 575 copy.addConfiguration((AbstractConfiguration) ConfigurationUtils 576 .cloneConfiguration(cd.getConfiguration()), cd.getName(), 577 cd.getAt()); 578 } 579 580 copy.setRootNode(new DefaultConfigurationNode()); 581 return copy; 582 } 583 584 /** 585 * Returns the configuration source, in which the specified key is defined. 586 * This method will determine the configuration node that is identified by 587 * the given key. The following constellations are possible: 588 * <ul> 589 * <li>If no node object is found for this key, <b>null</b> is returned.</li> 590 * <li>If the key maps to multiple nodes belonging to different 591 * configuration sources, a <code>IllegalArgumentException</code> is 592 * thrown (in this case no unique source can be determined).</li> 593 * <li>If exactly one node is found for the key, the (child) configuration 594 * object, to which the node belongs is determined and returned.</li> 595 * <li>For keys that have been added directly to this combined 596 * configuration and that do not belong to the namespaces defined by 597 * existing child configurations this configuration will be returned.</li> 598 * </ul> 599 * 600 * @param key the key of a configuration property 601 * @return the configuration, to which this property belongs or <b>null</b> 602 * if the key cannot be resolved 603 * @throws IllegalArgumentException if the key maps to multiple properties 604 * and the source cannot be determined, or if the key is <b>null</b> 605 * @since 1.5 606 */ 607 public Configuration getSource(String key) 608 { 609 if (key == null) 610 { 611 throw new IllegalArgumentException("Key must not be null!"); 612 } 613 614 List nodes = fetchNodeList(key); 615 if (nodes.isEmpty()) 616 { 617 return null; 618 } 619 620 Iterator it = nodes.iterator(); 621 Configuration source = findSourceConfiguration((ConfigurationNode) it 622 .next()); 623 while (it.hasNext()) 624 { 625 Configuration src = findSourceConfiguration((ConfigurationNode) it 626 .next()); 627 if (src != source) 628 { 629 throw new IllegalArgumentException("The key " + key 630 + " is defined by multiple sources!"); 631 } 632 } 633 634 return source; 635 } 636 637 /** 638 * Evaluates the passed in property key and returns a list with the matching 639 * configuration nodes. This implementation also evaluates the 640 * <em>force reload check</em> flag. If it is set, 641 * <code>performReloadCheck()</code> is invoked. 642 * 643 * @param key the property key 644 * @return a list with the matching configuration nodes 645 */ 646 protected List fetchNodeList(String key) 647 { 648 if (isForceReloadCheck()) 649 { 650 performReloadCheck(); 651 } 652 653 return super.fetchNodeList(key); 654 } 655 656 /** 657 * Triggers the contained configurations to perform a reload check if 658 * necessary. This method is called when a property of this combined 659 * configuration is accessed and the <code>forceReloadCheck</code> property 660 * is set to <b>true</b>. 661 * 662 * @see #setForceReloadCheck(boolean) 663 * @since 1.6 664 */ 665 protected void performReloadCheck() 666 { 667 for (Iterator it = configurations.iterator(); it.hasNext();) 668 { 669 try 670 { 671 // simply retrieve a property; this is enough for 672 // triggering a reload 673 ((ConfigData) it.next()).getConfiguration().getProperty( 674 PROP_RELOAD_CHECK); 675 } 676 catch (Exception ex) 677 { 678 // ignore all exceptions, e.g. missing property exceptions 679 ; 680 } 681 } 682 } 683 684 /** 685 * Creates the root node of this combined configuration. 686 * 687 * @return the combined root node 688 */ 689 private ConfigurationNode constructCombinedNode() 690 { 691 if (getNumberOfConfigurations() < 1) 692 { 693 return new ViewNode(); 694 } 695 696 else 697 { 698 Iterator it = configurations.iterator(); 699 ConfigurationNode node = ((ConfigData) it.next()) 700 .getTransformedRoot(); 701 while (it.hasNext()) 702 { 703 node = getNodeCombiner().combine(node, 704 ((ConfigData) it.next()).getTransformedRoot()); 705 } 706 return node; 707 } 708 } 709 710 /** 711 * Determines the configuration that owns the specified node. 712 * 713 * @param node the node 714 * @return the owning configuration 715 */ 716 private Configuration findSourceConfiguration(ConfigurationNode node) 717 { 718 ConfigurationNode root = null; 719 ConfigurationNode current = node; 720 721 // find the root node in this hierarchy 722 while (current != null) 723 { 724 root = current; 725 current = current.getParentNode(); 726 } 727 728 // Check with the root nodes of the child configurations 729 for (Iterator it = configurations.iterator(); it.hasNext();) 730 { 731 ConfigData cd = (ConfigData) it.next(); 732 if (root == cd.getRootNode()) 733 { 734 return cd.getConfiguration(); 735 } 736 } 737 738 return this; 739 } 740 741 /** 742 * An internal helper class for storing information about contained 743 * configurations. 744 */ 745 class ConfigData 746 { 747 /** Stores a reference to the configuration. */ 748 private AbstractConfiguration configuration; 749 750 /** Stores the name under which the configuration is stored. */ 751 private String name; 752 753 /** Stores the at information as path of nodes. */ 754 private Collection atPath; 755 756 /** Stores the at string.*/ 757 private String at; 758 759 /** Stores the root node for this child configuration.*/ 760 private ConfigurationNode rootNode; 761 762 /** 763 * Creates a new instance of <code>ConfigData</code> and initializes 764 * it. 765 * 766 * @param config the configuration 767 * @param n the name 768 * @param at the at position 769 */ 770 public ConfigData(AbstractConfiguration config, String n, String at) 771 { 772 configuration = config; 773 name = n; 774 atPath = parseAt(at); 775 this.at = at; 776 } 777 778 /** 779 * Returns the stored configuration. 780 * 781 * @return the configuration 782 */ 783 public AbstractConfiguration getConfiguration() 784 { 785 return configuration; 786 } 787 788 /** 789 * Returns the configuration's name. 790 * 791 * @return the name 792 */ 793 public String getName() 794 { 795 return name; 796 } 797 798 /** 799 * Returns the at position of this configuration. 800 * 801 * @return the at position 802 */ 803 public String getAt() 804 { 805 return at; 806 } 807 808 /** 809 * Returns the root node for this child configuration. 810 * 811 * @return the root node of this child configuration 812 * @since 1.5 813 */ 814 public ConfigurationNode getRootNode() 815 { 816 return rootNode; 817 } 818 819 /** 820 * Returns the transformed root node of the stored configuration. The 821 * term "transformed" means that an eventually defined at path 822 * has been applied. 823 * 824 * @return the transformed root node 825 */ 826 public ConfigurationNode getTransformedRoot() 827 { 828 ViewNode result = new ViewNode(); 829 ViewNode atParent = result; 830 831 if (atPath != null) 832 { 833 // Build the complete path 834 for (Iterator it = atPath.iterator(); it.hasNext();) 835 { 836 ViewNode node = new ViewNode(); 837 node.setName((String) it.next()); 838 atParent.addChild(node); 839 atParent = node; 840 } 841 } 842 843 // Copy data of the root node to the new path 844 HierarchicalConfiguration hc = ConfigurationUtils 845 .convertToHierarchical(getConfiguration(), 846 getConversionExpressionEngine()); 847 atParent.appendChildren(hc.getRootNode()); 848 atParent.appendAttributes(hc.getRootNode()); 849 rootNode = hc.getRootNode(); 850 851 return result; 852 } 853 854 /** 855 * Splits the at path into its components. 856 * 857 * @param at the at string 858 * @return a collection with the names of the single components 859 */ 860 private Collection parseAt(String at) 861 { 862 if (at == null) 863 { 864 return null; 865 } 866 867 Collection result = new ArrayList(); 868 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( 869 AT_ENGINE, at).iterator(); 870 while (it.hasNext()) 871 { 872 result.add(it.nextKey()); 873 } 874 return result; 875 } 876 } 877 }