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 }