    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.LinkedList;
    020    import java.util.List;
    021
    022    /**
    023     * <p>
    024     * A specialized implementation of the {@code NodeCombiner} interface
    025     * that constructs a union from two passed in node hierarchies.
    026     * </p>
    027     * <p>
    028     * The given source hierarchies are traversed and their nodes are added to the
    029     * resulting structure. Under some circumstances two nodes can be combined
    030     * rather than adding both. This is the case if both nodes are single children
    031     * (no lists) of their parents and do not have values. The corresponding check
    032     * is implemented in the {@code findCombineNode()} method.
    033     * </p>
    034     * <p>
    035     * Sometimes it is not possible for this combiner to detect whether two nodes
    036     * can be combined or not. Consider the following two node hierarchies:
    037     * </p>
    038     * <p>
    039     *
    040     * <pre>
    041     * Hierarchy 1:
    042     *
    043     * Database
    044     *   +--Tables
    045     *        +--Table
    046     *             +--name [users]
    047     *             +--fields
    048     *                   +--field
    049     *                   |    +--name [uid]
    050     *                   +--field
    051     *                   |    +--name [usrname]
    052     *                     ...
    053     * </pre>
    054     *
    055     * </p>
    056     * <p>
    057     *
    058     * <pre>
    059     * Hierarchy 2:
    060     *
    061     * Database
    062     *   +--Tables
    063     *        +--Table
    064     *             +--name [documents]
    065     *             +--fields
    066     *                   +--field
    067     *                   |    +--name [docid]
    068     *                   +--field
    069     *                   |    +--name [docname]
    070     *                     ...
    071     * </pre>
    072     *
    073     * </p>
    074     * <p>
    075     * Both hierarchies contain data about database tables. Each describes a single
    076     * table. If these hierarchies are to be combined, the result should probably
    077     * look like the following:
    078     * <p>
    079     *
    080     * <pre>
    081     * Database
    082     *   +--Tables
    083     *        +--Table
    084     *        |    +--name [users]
    085     *        |    +--fields
    086     *        |          +--field
    087     *        |          |    +--name [uid]
    088     *        |            ...
    089     *        +--Table
    090     *             +--name [documents]
    091     *             +--fields
    092     *                   +--field
    093     *                   |    +--name [docid]
    094     *                     ...
    095     * </pre>
    096     *
    097     * </p>
    098     * <p>
    099     * i.e. the {@code Tables} nodes should be combined, while the
    100     * {@code Table} nodes should both be added to the resulting tree. From
    101     * the combiner's point of view there is no difference between the
    102     * {@code Tables} and the {@code Table} nodes in the source trees,
    103     * so the developer has to help out and give a hint that the {@code Table}
    104     * nodes belong to a list structure. This can be done using the
    105     * {@code addListNode()} method; this method expects the name of a node,
    106     * which should be treated as a list node. So if
    107     * {@code addListNode("Table");} was called, the combiner knows that it
    108     * must not combine the {@code Table} nodes, but add it both to the
    109     * resulting tree.
    110     * </p>
    111     *
    112     * @author <a
    113     * href="http://commons.apache.org/configuration/team-list.html">Commons
    114     * Configuration team</a>
    115     * @version $Id: UnionCombiner.java 1206486 2011-11-26 16:41:12Z oheger $
    116     * @since 1.3
    117     */
    118    public class UnionCombiner extends NodeCombiner
    119    {
    120        /**
    121         * Combines the given nodes to a new union node.
    122         *
    123         * @param node1 the first source node
    124         * @param node2 the second source node
    125         * @return the union node
    126         */
    127        @Override
    128        public ConfigurationNode combine(ConfigurationNode node1,
    129                ConfigurationNode node2)
    130        {
    131            ViewNode result = createViewNode();
    132            result.setName(node1.getName());
    133            result.appendAttributes(node1);
    134            result.appendAttributes(node2);
    135
    136            // Check if nodes can be combined
    137            List<ConfigurationNode> children2 = new LinkedList<ConfigurationNode>(node2.getChildren());
    138            for (ConfigurationNode child1 : node1.getChildren())
    139            {
    140                ConfigurationNode child2 = findCombineNode(node1, node2, child1,
    141                        children2);
    142                if (child2 != null)
    143                {
    144                    result.addChild(combine(child1, child2));
    145                    children2.remove(child2);
    146                }
    147                else
    148                {
    149                    result.addChild(child1);
    150                }
    151            }
    152
    153            // Add remaining children of node 2
    154            for (ConfigurationNode c : children2)
    155            {
    156                result.addChild(c);
    157            }
    158
    159            return result;
    160        }
    161
    162        /**
    163         * <p>
    164         * Tries to find a child node of the second source node, with which a child
    165         * of the first source node can be combined. During combining of the source
    166         * nodes an iteration over the first source node's children is performed.
    167         * For each child node it is checked whether a corresponding child node in
    168         * the second source node exists. If this is the case, these corresponding
    169         * child nodes are recursively combined and the result is added to the
    170         * combined node. This method implements the checks whether such a recursive
    171         * combination is possible. The actual implementation tests the following
    172         * conditions:
    173         * </p>
    174         * <p>
    175         * <ul>
    176         * <li>In both the first and the second source node there is only one child
    177         * node with the given name (no list structures).</li>
    178         * <li>The given name is not in the list of known list nodes, i.e. it was
    179         * not passed to the {@code addListNode()} method.</li>
    180         * <li>None of these matching child nodes has a value.</li>
    181         * </ul>
    182         * </p>
    183         * <p>
    184         * If all of these tests are successful, the matching child node of the
    185         * second source node is returned. Otherwise the result is <b>null</b>.
    186         * </p>
    187         *
    188         * @param node1 the first source node
    189         * @param node2 the second source node
    190         * @param child the child node of the first source node to be checked
    191         * @param children a list with all children of the second source node
    192         * @return the matching child node of the second source node or <b>null</b>
    193         * if there is none
    194         */
    195        protected ConfigurationNode findCombineNode(ConfigurationNode node1,
    196                ConfigurationNode node2, ConfigurationNode child, List<ConfigurationNode> children)
    197        {
    198            if (child.getValue() == null && !isListNode(child)
    199                    && node1.getChildrenCount(child.getName()) == 1
    200                    && node2.getChildrenCount(child.getName()) == 1)
    201            {
    202                ConfigurationNode child2 = node2.getChildren(
    203                        child.getName()).iterator().next();
    204                if (child2.getValue() == null)
    205                {
    206                    return child2;
    207                }
    208            }
    209            return null;
    210        }
    211    }