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 }