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.ArrayList;
020    import java.util.Iterator;
021    import java.util.LinkedList;
022    import java.util.List;
023    
024    /**
025     * <p>
026     * A specialized implementation of the {@code NodeCombiner} 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 1206480 2011-11-26 16:28:58Z oheger $
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        @Override
056        public ConfigurationNode combine(ConfigurationNode node1, ConfigurationNode node2)
057        {
058            ViewNode result = createViewNode();
059            result.setName(node1.getName());
060            result.setValue(node1.getValue());
061            addAttributes(result, node1, node2);
062    
063            // Check if nodes can be combined
064            List<ConfigurationNode> children2 = new LinkedList<ConfigurationNode>(node2.getChildren());
065            for (ConfigurationNode child1 : node1.getChildren())
066            {
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 (ConfigurationNode c : children2)
081            {
082                result.addChild(c);
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 (ConfigurationNode attr : node2.getAttributes())
102            {
103                if (node1.getAttributeCount(attr.getName()) == 0)
104                {
105                    result.addAttribute(attr);
106                }
107            }
108        }
109    
110        /**
111         * Tests if the first node can be combined with the second node. A node can
112         * only be combined if its attributes are all present in the second node and
113         * they all have the same value.
114         *
115         * @param node1 the first node
116         * @param node2 the second node
117         * @param child the child node (of the first node)
118         * @return a child of the second node, with which a combination is possible
119         */
120        protected ConfigurationNode canCombine(ConfigurationNode node1,
121                ConfigurationNode node2, ConfigurationNode child, List<ConfigurationNode> children2)
122        {
123            List<ConfigurationNode> attrs1 = child.getAttributes();
124            List<ConfigurationNode> nodes = new ArrayList<ConfigurationNode>();
125    
126            List<ConfigurationNode> children = node2.getChildren(child.getName());
127            Iterator<ConfigurationNode> it = children.iterator();
128            while (it.hasNext())
129            {
130                ConfigurationNode node = it.next();
131                Iterator<ConfigurationNode> iter = attrs1.iterator();
132                while (iter.hasNext())
133                {
134                    ConfigurationNode attr1 = iter.next();
135                    List<ConfigurationNode> list2 = node.getAttributes(attr1.getName());
136                    if (list2.size() == 1
137                        && !attr1.getValue().equals(list2.get(0).getValue()))
138                    {
139                        node = null;
140                        break;
141                    }
142                }
143                if (node != null)
144                {
145                    nodes.add(node);
146                }
147            }
148    
149            if (nodes.size() == 1)
150            {
151                return (ConfigurationNode) nodes.get(0);
152            }
153            if (nodes.size() > 1 && !isListNode(child))
154            {
155                Iterator<ConfigurationNode> iter = nodes.iterator();
156                while (iter.hasNext())
157                {
158                    children2.remove(iter.next());
159                }
160            }
161    
162            return null;
163        }
164    }