Author: rgoers
Date: Mon Mar 30 23:00:32 2009
New Revision: 760225

URL: http://svn.apache.org/viewvc?rev=760225&view=rev
Log:
CONFIGURATION-378 Add MergeCombiner

Added:
    
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java
Modified:
    
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml
    
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml
    
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
    
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java?rev=760225&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/MergeCombiner.java
 Mon Mar 30 23:00:32 2009
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration2.tree;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * <p>
+ * A specialized implementation of the <code>NodeCombiner</code> interface
+ * that performs a merge from two passed in node hierarchies.
+ * </p>
+ * <p>
+ * This combiner performs the merge using a few rules:
+ * <ol>
+ * <li>Nodes can be merged when attributes that appear in both have the same 
value.</li>
+ * <li>Only a single node in the second file is considered a match to the node 
in the first file.</li>
+ * <li>Attributes in nodes that match are merged.
+ * <li>Nodes in both files that do not match are added to the result.</li>
+ * </ol>
+ * </p>
+ *
+ * @author <a
+ * href="http://commons.apache.org/configuration/team-list.html";>Commons
+ * Configuration team</a>
+ * @version $Id:  $
+ * @since 1.7
+ */
+public class MergeCombiner extends NodeCombiner
+{
+    /**
+     * Combines the given nodes to a new union node.
+     *
+     * @param node1 the first source node
+     * @param node2 the second source node
+     * @return the union node
+     */
+
+    public ConfigurationNode combine(ConfigurationNode node1, 
ConfigurationNode node2)
+    {
+        ConfigurationNode result = doCombine(node1, node2);
+        printTree(result);
+        return result;
+    }
+    
+    public ConfigurationNode doCombine(ConfigurationNode node1, 
ConfigurationNode node2)
+    {
+        ViewNode result = createViewNode();
+        result.setName(node1.getName());
+        result.setValue(node1.getValue());
+        addAttributes(result, node1, node2);
+
+        // Check if nodes can be combined
+        List children2 = new LinkedList(node2.getChildren());
+        for (Iterator it = node1.getChildren().iterator(); it.hasNext();)
+        {
+            ConfigurationNode child1 = (ConfigurationNode) it.next();
+            ConfigurationNode child2 = canCombine(node1, node2, child1, 
children2);
+            if (child2 != null)
+            {
+                result.addChild(doCombine(child1, child2));
+                children2.remove(child2);
+            }
+            else
+            {
+                result.addChild(child1);
+            }
+        }
+
+        // Add remaining children of node 2
+        for (Iterator it = children2.iterator(); it.hasNext();)
+        {
+            result.addChild((ConfigurationNode) it.next());
+        }
+        return result;
+    }
+
+    /**
+     * Handles the attributes during a combination process. First all 
attributes
+     * of the first node will be added to the result. Then all attributes of 
the
+     * second node, which are not contained in the first node, will also be
+     * added.
+     *
+     * @param result the resulting node
+     * @param node1 the first node
+     * @param node2 the second node
+     */
+    protected void addAttributes(ViewNode result, ConfigurationNode node1,
+            ConfigurationNode node2)
+    {
+        result.appendAttributes(node1);
+        for (Iterator it = node2.getAttributes().iterator(); it.hasNext();)
+        {
+            ConfigurationNode attr = (ConfigurationNode) it.next();
+            if (node1.getAttributeCount(attr.getName()) == 0)
+            {
+                result.addAttribute(attr);
+            }
+        }
+    }
+
+    /**
+     * Tests if the first node can be combined with the second node. A node can
+     * only be combined if its attributes are all present in the second node 
and
+     * they all have the same value.
+     *
+     * @param node1 the first node
+     * @param node2 the second node
+     * @param child the child node (of the first node)
+     * @return a child of the second node, with which a combination is possible
+     */
+    protected ConfigurationNode canCombine(ConfigurationNode node1,
+            ConfigurationNode node2, ConfigurationNode child, List children2)
+    {
+        List attrs1 = child.getAttributes();
+        List nodes = new ArrayList();
+
+        List children = node2.getChildren(child.getName());
+        Iterator it = children.iterator();
+        while (it.hasNext())
+        {
+            ConfigurationNode node = (ConfigurationNode) it.next();
+            Iterator iter = attrs1.iterator();
+            while (iter.hasNext())
+            {
+                ConfigurationNode attr1 = (ConfigurationNode) iter.next();
+                List list2 = node.getAttributes(attr1.getName());
+                if (list2.size() == 1
+                    && 
!attr1.getValue().equals(((ConfigurationNode)list2.get(0)).getValue()))
+                {
+                    node = null;
+                    break;
+                }
+            }
+            if (node != null)
+            {
+                nodes.add(node);
+            }
+        }
+
+        if (nodes.size() == 1)
+        {
+            return (ConfigurationNode) nodes.get(0);
+        }
+        if (nodes.size() > 1 && !isListNode(child))
+        {
+            Iterator iter = nodes.iterator();
+            while (iter.hasNext())
+            {
+                children2.remove(iter.next());
+            }
+        }        
+
+        return null;
+    }
+}
\ No newline at end of file

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/tree/NodeCombiner.java
 Mon Mar 30 23:00:32 2009
@@ -20,6 +20,8 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.List;
+import java.io.PrintStream;
 
 /**
  * <p>
@@ -51,6 +53,9 @@
  */
 public abstract class NodeCombiner
 {
+    /** Stream to write debug output to */
+    private PrintStream debugStream = null;
+
     /** Stores a list with node names that are known to be list nodes. */
     protected Set<String> listNodes;
 
@@ -119,4 +124,51 @@
     {
         return new ViewNode();
     }
+
+    /**
+     * Set the output stream to write the tree to.
+     * @param stream The OutputStream.
+     */
+    public void setDebugStream(PrintStream stream)
+    {
+        this.debugStream = stream;
+    }
+
+    protected void printTree(ConfigurationNode result)
+    {
+        if (debugStream != null)
+        {
+            printTree("", result);
+        }
+    }
+
+    private void printTree(String indent, ConfigurationNode result)
+    {
+        StringBuffer buffer = new 
StringBuffer(indent).append("<").append(result.getName());
+        for (ConfigurationNode node : result.getAttributes())
+        {
+            buffer.append(" 
").append(node.getName()).append("='").append(node.getValue()).append("'");
+        }
+        buffer.append(">");
+        debugStream.print(buffer.toString());
+        if (result.getValue() != null)
+        {
+            debugStream.print(result.getValue());
+        }
+        boolean newline = false;
+        if (result.getChildrenCount() > 0)
+        {
+            debugStream.print("\n");
+            for (ConfigurationNode node : result.getChildren())
+            {
+                printTree(indent + "  ", node);
+            }
+            newline = true;
+        }
+        if (newline)
+        {
+            debugStream.print(indent);
+        }
+        debugStream.println("</" + result.getName() + ">");
+    }
 }

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDefaultConfigurationBuilder.java
 Mon Mar 30 23:00:32 2009
@@ -880,10 +880,6 @@
         verify("1005", config, 50);
     }
 
-    /** This test doesn't pass and rightfully so. A new "MergeCombiner" needs 
to be
-     * created so it will.
-     * @throws Exception
-     */  /*
     public void testMultiTenantConfigurationAt() throws Exception
     {
         factory.setFile(MULTI_TENENT_FILE);
@@ -895,7 +891,7 @@
         HierarchicalConfiguration sub2 = 
config.configurationAt("Channels/chann...@id='2']");
         assertEquals("Channel 2", sub2.getString("Name"));
         assertEquals("more test 2 data", sub2.getString("MoreChannelData"));
-    } */
+    } 
 
     public void testMerge() throws Exception
     {

Added: 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java?rev=760225&view=auto
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java
 (added)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/tree/TestMergeCombiner.java
 Mon Mar 30 23:00:32 2009
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration2.tree;
+
+import java.util.List;
+
+import org.apache.commons.configuration2.ConfigurationException;
+import org.apache.commons.configuration2.HierarchicalConfiguration;
+import org.apache.commons.configuration2.XMLConfiguration;
+import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
+
+/**
+ * Test class for MergeCombiner.
+ *
+ * @version $Id:  $
+ */
+public class TestMergeCombiner extends AbstractCombinerTest
+{
+    /**
+     * Creates the combiner.
+     *
+     * @return the combiner
+     */
+    protected NodeCombiner createCombiner()
+    {
+        return new MergeCombiner();
+    }
+
+    /**
+     * Tests combination of simple elements.
+     */
+    public void testSimpleValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of bgcolors", 0, config
+                .getMaxIndex("gui.bgcolor"));
+        assertEquals("Wrong bgcolor", "green", 
config.getString("gui.bgcolor"));
+        assertEquals("Wrong selcolor", "yellow", config
+                .getString("gui.selcolor"));
+        assertEquals("Wrong fgcolor", "blue", config.getString("gui.fgcolor"));
+        assertEquals("Wrong level", 1, config.getInt("gui.level"));
+    }
+
+    /**
+     * Tests combination of attributes.
+     */
+    public void testAttributes() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong value of min attribute", 1, config
+                .getInt("gui.lev...@min]"));
+        assertEquals("Wrong value of default attribute", 2, config
+                .getInt("gui.lev...@default]"));
+        assertEquals("Wrong number of id attributes", 0, config
+                .getMaxIndex("database.tables.table(0)[...@id]"));
+        assertEquals("Wrong value of table id", 1, config
+                .getInt("database.tables.table(0)[...@id]"));
+    }
+
+    /**
+     * Tests whether property values are correctly overridden.
+     */
+    public void testOverrideValues() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong user", "Admin", config
+                .getString("base.services.security.login.user"));
+        assertEquals("Wrong user type", "default", config
+                .getString("base.services.security.login.us...@type]"));
+        assertNull("Wrong password", 
config.getString("base.services.security.login.passwd"));
+        assertEquals("Wrong password type", "secret", config
+                .getString("base.services.security.login.pass...@type]"));
+    }
+
+    /**
+     * Tests if a list from the first node structure overrides a list in the
+     * second structure.
+     */
+    public void testListFromFirstStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of services", 0, config
+                .getMaxIndex("net.service.url"));
+        assertEquals("Wrong service", "http://service1.org";, config
+                .getString("net.service.url"));
+        assertFalse("Type attribute available", config
+                .containsKey("net.service.u...@type]"));
+    }
+
+    /**
+     * Tests if a list from the second structure is added if it is not defined
+     * in the first structure.
+     */
+    public void testListFromSecondStructure() throws ConfigurationException
+    {
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong number of servers", 3, config
+                .getMaxIndex("net.server.url"));
+        assertEquals("Wrong server", "http://testsvr.com";, config
+                .getString("net.server.url(2)"));
+    }
+
+    /**
+     * Tests the combination of the table structure. With the merge combiner
+     * both table 1 and table 2 should be present.
+     */
+    public void testCombinedTable() throws ConfigurationException
+    {
+        checkTable(createCombinedConfiguration());
+    }
+
+    public void testMerge() throws ConfigurationException
+    {
+        //combiner.setDebugStream(System.out);
+        HierarchicalConfiguration config = createCombinedConfiguration();
+        config.setExpressionEngine(new XPathExpressionEngine());
+        assertEquals("Wrong number of Channels", 3, 
config.getMaxIndex("Channels/Channel"));
+        assertEquals("Bad Channel 1 Name", "My Channel",
+                config.getString("Channels/chann...@id='1']/Name"));
+        assertEquals("Bad Channel Type", "half",
+                config.getString("Channels/chann...@id='1']/@type"));
+        assertEquals("Bad Channel 2 Name", "Channel 2",
+                config.getString("Channels/chann...@id='2']/Name"));
+        assertEquals("Bad Channel Type", "full",
+                config.getString("Channels/chann...@id='2']/@type"));
+        assertEquals("Bad Channel Data", "test 1 data",
+                config.getString("Channels/chann...@id='1']/ChannelData"));
+        assertEquals("Bad Channel Data", "test 2 data",
+                config.getString("Channels/chann...@id='2']/ChannelData"));
+        assertEquals("Bad Channel Data", "more test 2 data",
+                config.getString("Channels/chann...@id='2']/MoreChannelData"));
+
+    }
+
+    /**
+     * Helper method for checking the combined table structure.
+     *
+     * @param config the config
+     * @return the node for the table element
+     */
+    private ConfigurationNode checkTable(HierarchicalConfiguration config)
+    {
+        assertEquals("Wrong number of tables", 1, config
+                .getMaxIndex("database.tables.table"));
+        HierarchicalConfiguration c = config
+                .configurationAt("database.tables.table(0)");
+        assertEquals("Wrong table name", "documents", c.getString("name"));
+        assertEquals("Wrong number of fields", 2, c
+                .getMaxIndex("fields.field.name"));
+        assertEquals("Wrong field", "docname", c
+                .getString("fields.field(1).name"));
+
+        List nds = config.getExpressionEngine().query(config.getRootNode(),
+                "database.tables.table");
+        assertFalse("No node found", nds.isEmpty());
+        return (ConfigurationNode) nds.get(0);
+    }
+}
\ No newline at end of file

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testMultiTenentConfigurationBuilder.xml
 Mon Mar 30 23:00:32 2009
@@ -5,6 +5,7 @@
     <result delimiterParsingDisabled="true" forceReloadCheck="true"
             
config-class="org.apache.commons.configuration2.DynamicCombinedConfiguration"
             keyPattern="$${sys:Id}">
+      <nodeCombiner 
config-class="org.apache.commons.configuration2.tree.MergeCombiner"/>
       <expressionEngine
           
config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/>
     </result>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine1.xml
 Mon Mar 30 23:00:32 2009
@@ -49,4 +49,15 @@
       </table>
     </tables>
   </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel>
+  </Channels>
 </config>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/src/test/resources/testcombine2.xml
 Mon Mar 30 23:00:32 2009
@@ -45,4 +45,18 @@
       </table>
     </tables>
   </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>  
 </config>

Modified: 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
 Mon Mar 30 23:00:32 2009
@@ -85,6 +85,10 @@
     </release>
 
     <release version="1.7" date="in SVN" description="">
+      <action dev="rgoers" type="add" issue="CONFIGURATION-378">
+        Added MergeCombiner to allow elements in two configurations to be 
merged when the
+        element and attributes in the first file match those in the second 
file.
+      </action>
       <action dev="rgoers" type="add" issue="CONFIGURATION-340">
         File system access has been abstracted to a FileSystem interface. Two 
implementations
         are provided, DefaultFileSystem that behaves in a backward compatible 
manner and

Modified: 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml?rev=760225&r1=760224&r2=760225&view=diff
==============================================================================
--- 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml
 (original)
+++ 
commons/proper/configuration/branches/configuration2_experimental/xdocs/userguide/howto_combinedconfiguration.xml
 Mon Mar 30 23:00:32 2009
@@ -85,10 +85,11 @@
       takes the root nodes of two hierarchical configurations and returns the
       root node of the combined node structure. It is up to a concrete
       implementation how this combined structure will look like. Commons
-      Configuration ships with the two concrete implementations
-      <code><a 
href="../apidocs/org/apache/commons/configuration2/tree/OverrideCombiner.html">OverrideCombiner</a></code>
+      Configuration ships with three concrete implementations
+      <code><a 
href="../apidocs/org/apache/commons/configuration2/tree/OverrideCombiner.html">OverrideCombiner</a></code>,
+      <code><a 
href="../apidocs/org/apache/commons/configuration/tree/MergeCombiner.html">MergeCombiner</a></code>
       and <code><a 
href="../apidocs/org/apache/commons/configuration2/tree/UnionCombiner.html">UnionCombiner</a></code>,
-      which implement an override and a union semantics respective.
+      which implement an override, merge and union semantics respectively.
     </p>
     <p>
       Constructing a combination of multiple node hierarchies is not a trivial
@@ -216,6 +217,471 @@
       node combiner would have concluded itself that <code>table</code> is a 
list
       node and would have acted correspondigly.
     </p>
+    <p>
+      The examples the follow are provided to further illustrate the 
differences
+      between the combiners that are delivered with Commons Configuration. The 
first
+      two files are the files that will be combined.
+    </p>
+    <table border='0'>
+    <tr>
+      <th width="50%">testfile1.xml</th>
+      <th width="50%">testfile2.xml</th>
+    </tr>
+    <tr><td width="50%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default="2">1</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type="secret"/>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id="1">
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1" type="half">
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id="2">
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id="3" type="half">
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id="4">
+      <Name>Channel 4</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="50%">
+<source><![CDATA[<config>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type="default">scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <gui>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min="1">4</level>
+  </gui>
+  <net>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+    <service>
+      <url type="2">http://service2.org</url>
+      <url type="2">http://service3.org</url>
+    </service>
+  </net>
+  <database>
+    <tables>
+      <table id="2">
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id="1">
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id="2" type="full">
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id="3" type="full">
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+    <Channel id="4" type="half">
+      <Name>Test Channel 1</Name>
+    </Channel>
+    <Channel id="4" type="full">
+      <Name>Test Channel 2</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td></tr></table>
+      <p>
+        The first listing shows the result of using the 
<code>OverrideCombiner</code>.
+      </p>
+      <table>
+        <tr><th width="40%">OverrideCombiner Results</th><th 
width="60%">Notes</th></tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section each of the child elements only appears once. 
The level element
+          merges the attributes from the two files and uses the element value 
of the first file.</li>
+          <li>In the security section the user type attribute was obtained 
from the second file
+          while the user value came from the first file. Alternately, the 
password type was
+          obtained from the first file while the value came from the 
second.</li>
+          <li>Only the data from table 1 was included.</li>
+          <li>Channel 1 in the first file completely overrode Channel 1 in the 
second file.</li>
+          <li>Channel 2 in the first file completely overrode Channel 2 in the 
second file. While
+          the attributes were merged in the case of the login elements the 
type attribute
+          was not merged in this case.</li>
+          <li>Again, only Channel 3 from the first file was included.</li>
+        </ul>
+      </p>
+      <p>
+        How the Channel elements ended up may not at first be obvious. The 
<code>OverrideCombiner</code>
+        simply noticed that the Channels element had three child elements 
named Channel and
+        used that to determine that only the contents of the Channels element 
in the first file
+        would be used.
+      </p></td></tr></table>
+      <p>
+        The next file is the the result of using the <code>UnionCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">UnionCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+ <source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2'>1</level>
+    <bgcolor>black</bgcolor>
+    <fgcolor>blue</fgcolor>
+    <level min='1'>4</level>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+      <url type='2'>http://service2.org</url>
+      <url type='2'>http://service3.org</url>
+    </service>
+    <server></server>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user>Admin</user>
+          <passwd type='secret'></passwd>
+          <user type='default'>scotty</user>
+          <passwd>BeamMeUp</passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1' id='2'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+        <name>tasks</name>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+    </Channel>
+    <Channel id='2'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='1'>
+      <Name>Channel 1</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The feature that is significant in this file is rather obvious. It is 
just a simple
+        union of the contents of the two files.
+      </p>
+      </td></tr></table>
+      <p>
+        Finally, the last file is the result of using the 
<code>MergeCombiner</code>
+      </p>
+      <table>
+        <tr>
+          <th width="40%">MergeCombiner Results</th>
+          <th width="60%">Notes</th>
+        </tr>
+        <tr><td width="40%">
+<source><![CDATA[<config>
+  <gui>
+    <bgcolor>green</bgcolor>
+    <selcolor>yellow</selcolor>
+    <level default='2' min='1'>1</level>
+    <fgcolor>blue</fgcolor>
+  </gui>
+  <net>
+    <proxy>
+      <url>http://www.url1.org</url>
+      <url>http://www.url2.org</url>
+      <url>http://www.url3.org</url>
+    </proxy>
+    <service>
+      <url>http://service1.org</url>
+    </service>
+    <server>
+      <url>http://appsvr1.com</url>
+      <url>http://appsvr2.com</url>
+      <url>http://testsvr.com</url>
+      <url>http://backupsvr.com</url>
+    </server>
+  </net>
+  <base>
+    <services>
+      <security>
+        <login>
+          <user type='default'>Admin</user>
+          <passwd type='secret'></passwd>
+        </login>
+      </security>
+    </services>
+  </base>
+  <database>
+    <tables>
+      <table id='1'>
+        <name>documents</name>
+        <fields>
+          <field>
+            <name>docid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>docname</name>
+            <type>varchar</type>
+          </field>
+          <field>
+            <name>authorID</name>
+            <type>int</type>
+          </field>
+        </fields>
+      </table>
+      <table id='2'>
+        <name>tasks</name>
+        <fields>
+          <field>
+            <name>taskid</name>
+            <type>long</type>
+          </field>
+          <field>
+            <name>taskname</name>
+            <type>varchar</type>
+          </field>
+        </fields>
+      </table>
+    </tables>
+  </database>
+  <Channels>
+    <Channel id='1' type='half'>
+      <Name>My Channel</Name>
+      <ChannelData>test 1 data</ChannelData>
+    </Channel>
+    <Channel id='2' type='full'>
+      <MoreChannelData>more test 2 data</MoreChannelData>
+      <Name>Channel 2</Name>
+      <ChannelData>test 2 data</ChannelData>
+    </Channel>
+    <Channel id='3' type='half'>
+      <Name>Test Channel</Name>
+    </Channel>
+    <Channel id='3' type='full'>
+      <Name>Channel 3</Name>
+      <ChannelData>test 3 data</ChannelData>
+    </Channel>
+  </Channels>
+</config>
+]]></source></td><td width="60%">
+      <p>
+        The features that are significant in this file are:
+        <ul>
+          <li>In the gui section the elements were merged.</li>
+          <li>In the net section the elements were merged, with the exception 
of the urls.</li>
+          <li>In the security section the user and password were merged. 
Notice that the
+          empty value for the password from the first file overrode the 
password in the
+          second file.</li>
+          <li>Both table elements appear</li>
+          <li>Channel 1 and Channel 2 were merged</li>
+          <li>Both Channel 3 elements appear as they were determined to not be 
the same.</li>
+        </ul>
+      </p>
+      <p>
+        When merging elements attributes play a critical role. If an element 
has an attribute that
+        appears in both sources, the value of that attribute must be the same 
for the elements to be
+        merged. Merging is only allowed between a single node in each of the 
files, so if an element
+        in the first file matches more than one element in the second file no 
merging will take
+        place and the element from the first file (and its contents) are 
included and the elements
+        in the second file are not. If the element is marked as a list node 
then the elements from
+        the second file will also be included.
+      </p></td></tr></table>
     </subsection>
 
        <subsection name="Constructing a CombinedConfiguration">


Reply via email to