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