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.tree;
018    
019    import java.util.Iterator;
020    import java.util.LinkedList;
021    import java.util.List;
022    import java.util.ArrayList;
023    
024    /**
025     * <p>
026     * A specialized implementation of the <code>NodeCombiner</code> interface
027     * that performs a merge from two passed in node hierarchies.
028     * </p>
029     * <p>
030     * This combiner performs the merge using a few rules:
031     * <ol>
032     * <li>Nodes can be merged when attributes that appear in both have the same value.</li>
033     * <li>Only a single node in the second file is considered a match to the node in the first file.</li>
034     * <li>Attributes in nodes that match are merged.
035     * <li>Nodes in both files that do not match are added to the result.</li>
036     * </ol>
037     * </p>
038     *
039     * @author <a
040     * href="http://commons.apache.org/configuration/team-list.html">Commons
041     * Configuration team</a>
042     * @version $Id: MergeCombiner.java 764168 2009-04-11 06:23:13Z rgoers $
043     * @since 1.7
044     */
045    public class MergeCombiner extends NodeCombiner
046    {
047        /**
048         * Combines the given nodes to a new union node.
049         *
050         * @param node1 the first source node
051         * @param node2 the second source node
052         * @return the union node
053         */
054    
055        public ConfigurationNode combine(ConfigurationNode node1, ConfigurationNode node2)
056        {
057            ViewNode result = createViewNode();
058            result.setName(node1.getName());
059            result.setValue(node1.getValue());
060            addAttributes(result, node1, node2);
061    
062            // Check if nodes can be combined
063            List children2 = new LinkedList(node2.getChildren());
064            for (Iterator it = node1.getChildren().iterator(); it.hasNext();)
065            {
066                ConfigurationNode child1 = (ConfigurationNode) it.next();
067                ConfigurationNode child2 = canCombine(node1, node2, child1, children2);
068                if (child2 != null)
069                {
070                    result.addChild(combine(child1, child2));
071                    children2.remove(child2);
072                }
073                else
074                {
075                    result.addChild(child1);
076                }
077            }
078    
079            // Add remaining children of node 2
080            for (Iterator it = children2.iterator(); it.hasNext();)
081            {
082                result.addChild((ConfigurationNode) it.next());
083            }
084            return result;
085        }
086    
087        /**
088         * Handles the attributes during a combination process. First all attributes
089         * of the first node will be added to the result. Then all attributes of the
090         * second node, which are not contained in the first node, will also be
091         * added.
092         *
093         * @param result the resulting node
094         * @param node1 the first node
095         * @param node2 the second node
096         */
097        protected void addAttributes(ViewNode result, ConfigurationNode node1,
098                ConfigurationNode node2)
099        {
100            result.appendAttributes(node1);
101            for (Iterator it = node2.getAttributes().iterator(); it.hasNext();)
102            {
103                ConfigurationNode attr = (ConfigurationNode) it.next();
104                if (node1.getAttributeCount(attr.getName()) == 0)
105                {
106                    result.addAttribute(attr);
107                }
108            }
109        }
110    
111        /**
112         * Tests if the first node can be combined with the second node. A node can
113         * only be combined if its attributes are all present in the second node and
114         * they all have the same value.
115         *
116         * @param node1 the first node
117         * @param node2 the second node
118         * @param child the child node (of the first node)
119         * @return a child of the second node, with which a combination is possible
120         */
121        protected ConfigurationNode canCombine(ConfigurationNode node1,
122                ConfigurationNode node2, ConfigurationNode child, List children2)
123        {
124            List attrs1 = child.getAttributes();
125            List nodes = new ArrayList();
126    
127            List children = node2.getChildren(child.getName());
128            Iterator it = children.iterator();
129            while (it.hasNext())
130            {
131                ConfigurationNode node = (ConfigurationNode) it.next();
132                Iterator iter = attrs1.iterator();
133                while (iter.hasNext())
134                {
135                    ConfigurationNode attr1 = (ConfigurationNode) iter.next();
136                    List list2 = node.getAttributes(attr1.getName());
137                    if (list2.size() == 1
138                        && !attr1.getValue().equals(((ConfigurationNode) list2.get(0)).getValue()))
139                    {
140                        node = null;
141                        break;
142                    }
143                }
144                if (node != null)
145                {
146                    nodes.add(node);
147                }
148            }
149    
150            if (nodes.size() == 1)
151            {
152                return (ConfigurationNode) nodes.get(0);
153            }
154            if (nodes.size() > 1 && !isListNode(child))
155            {
156                Iterator iter = nodes.iterator();
157                while (iter.hasNext())
158                {
159                    children2.remove(iter.next());
160                }
161            }
162    
163            return null;
164        }
165    }