View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration.tree;
18  
19  import java.util.LinkedList;
20  import java.util.List;
21  
22  /**
23   * <p>
24   * A specialized implementation of the {@code NodeCombiner} interface
25   * that constructs a union from two passed in node hierarchies.
26   * </p>
27   * <p>
28   * The given source hierarchies are traversed and their nodes are added to the
29   * resulting structure. Under some circumstances two nodes can be combined
30   * rather than adding both. This is the case if both nodes are single children
31   * (no lists) of their parents and do not have values. The corresponding check
32   * is implemented in the {@code findCombineNode()} method.
33   * </p>
34   * <p>
35   * Sometimes it is not possible for this combiner to detect whether two nodes
36   * can be combined or not. Consider the following two node hierarchies:
37   * </p>
38   * <p>
39   *
40   * <pre>
41   * Hierarchy 1:
42   *
43   * Database
44   *   +--Tables
45   *        +--Table
46   *             +--name [users]
47   *             +--fields
48   *                   +--field
49   *                   |    +--name [uid]
50   *                   +--field
51   *                   |    +--name [usrname]
52   *                     ...
53   * </pre>
54   *
55   * </p>
56   * <p>
57   *
58   * <pre>
59   * Hierarchy 2:
60   *
61   * Database
62   *   +--Tables
63   *        +--Table
64   *             +--name [documents]
65   *             +--fields
66   *                   +--field
67   *                   |    +--name [docid]
68   *                   +--field
69   *                   |    +--name [docname]
70   *                     ...
71   * </pre>
72   *
73   * </p>
74   * <p>
75   * Both hierarchies contain data about database tables. Each describes a single
76   * table. If these hierarchies are to be combined, the result should probably
77   * look like the following:
78   * <p>
79   *
80   * <pre>
81   * Database
82   *   +--Tables
83   *        +--Table
84   *        |    +--name [users]
85   *        |    +--fields
86   *        |          +--field
87   *        |          |    +--name [uid]
88   *        |            ...
89   *        +--Table
90   *             +--name [documents]
91   *             +--fields
92   *                   +--field
93   *                   |    +--name [docid]
94   *                     ...
95   * </pre>
96   *
97   * </p>
98   * <p>
99   * 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 }