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