You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by eb...@apache.org on 2009/06/23 12:57:00 UTC

svn commit: r787630 - in /commons/proper/configuration/branches/configuration2_experimental/src: main/java/org/apache/commons/configuration2/combined/MergeCombiner.java test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java

Author: ebourg
Date: Tue Jun 23 10:57:00 2009
New Revision: 787630

URL: http://svn.apache.org/viewvc?rev=787630&view=rev
Log:
Adapted the MergeCombiner to the NodeHandler paradigm

Added:
    commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java   (with props)
    commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java   (with props)

Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java?rev=787630&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java Tue Jun 23 10:57:00 2009
@@ -0,0 +1,159 @@
+/*
+ * 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.combined;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.configuration2.expr.NodeHandler;
+
+/**
+ * <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 <T, U> CombinedNode combine(T node1, NodeHandler<T> handler1, U node2, NodeHandler<U> handler2)
+    {
+        CombinedNode result = createCombinedNode();
+        result.setName(handler1.nodeName(node1));
+        result.setValue(handler1.getValue(node1));
+        addAttributes(result, node1, handler1, node2, handler2);
+
+        // Check if nodes can be combined
+        List<U> children2 = new LinkedList<U>(handler2.getChildren(node2));
+        for (T child1 : handler1.getChildren(node1))
+        {
+            U child2 = canCombine(node1, handler1, node2, handler2, child1, children2);
+            if (child2 != null)
+            {
+                CombinedNode n = combine(child1, handler1, child2, handler2);
+                result.addChild(n.getName(), n);
+                children2.remove(child2);
+            }
+            else
+            {
+                result.addChild(handler1.nodeName(child1), child1);
+            }
+        }
+
+        // Add remaining children of node 2
+        for (U node : children2)
+        {
+            result.addChild(handler2.nodeName(node), node);
+        }
+        
+        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 handler1 the node handler for the first node
+     * @param node2 the second node
+     * @param handler2 the node handler for the second node
+     */
+    protected <T, U> void addAttributes(CombinedNode result, T node1,
+            NodeHandler<T> handler1, U node2, NodeHandler<U> handler2)
+    {
+        appendAttributes(result, node1, handler1);
+        for (String attr : handler2.getAttributes(node2))
+        {
+            if (handler1.getAttributeValue(node1, attr) == null)
+            {
+                result.addAttributeValue(attr, handler2.getAttributeValue(node2, 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 <T, U> U canCombine(T node1, NodeHandler<T> handler1, U node2, NodeHandler<U> handler2, T child, List<U> children2)
+    {
+        List<String> attrs1 = handler1.getAttributes(child);
+        List<U> nodes = new ArrayList<U>();
+
+        List<U> children = handler2.getChildren(node2, handler1.nodeName(child));
+        for (U node : children)
+        {
+            for (String attr1 : attrs1)
+            {
+                Object attr2val = handler2.getAttributeValue(node, attr1);
+                if (attr2val != null && !handler1.getAttributeValue(child, attr1).equals(attr2val))
+                {
+                    node = null;
+                    break;
+                }
+            }
+            if (node != null)
+            {
+                nodes.add(node);
+            }
+        }
+
+        if (nodes.size() == 1)
+        {
+            return nodes.get(0);
+        }
+        if (nodes.size() > 1 && !isListNode(child, handler1))
+        {
+            for (U node : nodes)
+            {
+                children2.remove(node);
+            }
+        }
+
+        return null;
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/combined/MergeCombiner.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java
URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java?rev=787630&view=auto
==============================================================================
--- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java (added)
+++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java Tue Jun 23 10:57:00 2009
@@ -0,0 +1,147 @@
+/*
+ * 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.combined;
+
+import org.apache.commons.configuration2.AbstractHierarchicalConfiguration;
+import org.apache.commons.configuration2.ConfigurationException;
+import org.apache.commons.configuration2.InMemoryConfiguration;
+import org.apache.commons.configuration2.expr.NodeList;
+import org.apache.commons.configuration2.expr.xpath.XPathExpressionEngine;
+import org.apache.commons.configuration2.tree.ConfigurationNode;
+
+/**
+ * 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
+    {
+        InMemoryConfiguration 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
+    {
+        InMemoryConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong value of min attribute", 1, config.getInt("gui.level[@min]"));
+        assertEquals("Wrong value of default attribute", 2, config.getInt("gui.level[@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
+    {
+        InMemoryConfiguration config = createCombinedConfiguration();
+        assertEquals("Wrong user", "Admin", config.getString("base.services.security.login.user"));
+        assertEquals("Wrong user type", "default", config.getString("base.services.security.login.user[@type]"));
+        assertNull("Wrong password", config.getString("base.services.security.login.passwd"));
+        assertEquals("Wrong password type", "secret", config.getString("base.services.security.login.passwd[@type]"));
+    }
+
+    /**
+     * Tests if a list from the first node structure overrides a list in the
+     * second structure.
+     */
+    public void testListFromFirstStructure() throws ConfigurationException
+    {
+        InMemoryConfiguration 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.url[@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
+    {
+        InMemoryConfiguration 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);
+        InMemoryConfiguration 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/Channel[@id='1']/Name"));
+        assertEquals("Bad Channel Type", "half", config.getString("Channels/Channel[@id='1']/@type"));
+        assertEquals("Bad Channel 2 Name", "Channel 2", config.getString("Channels/Channel[@id='2']/Name"));
+        assertEquals("Bad Channel Type", "full", config.getString("Channels/Channel[@id='2']/@type"));
+        assertEquals("Bad Channel Data", "test 1 data", config.getString("Channels/Channel[@id='1']/ChannelData"));
+        assertEquals("Bad Channel Data", "test 2 data", config.getString("Channels/Channel[@id='2']/ChannelData"));
+        assertEquals("Bad Channel Data", "more test 2 data", config.getString("Channels/Channel[@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(InMemoryConfiguration config)
+    {
+        assertEquals("Wrong number of tables", 1, config.getMaxIndex("database.tables.table"));
+        AbstractHierarchicalConfiguration 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"));
+
+        NodeList nds = config.getExpressionEngine().query(config.getRootNode(), "database.tables.table", config.getNodeHandler());
+        assertFalse("No node found", nds.size() == 0);
+        return (ConfigurationNode) nds.getNode(0);
+    }
+}

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/combined/TestMergeCombiner.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL