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 * @version $Id: UnionCombiner.java 1842194 2018-09-27 22:24:23Z ggregory $ 114 * @since 1.3 115 */ 116public class UnionCombiner extends NodeCombiner 117{ 118 /** 119 * Combines the given nodes to a new union node. 120 * 121 * @param node1 the first source node 122 * @param node2 the second source node 123 * @return the union node 124 */ 125 @Override 126 public ImmutableNode combine(final ImmutableNode node1, 127 final ImmutableNode node2) 128 { 129 final ImmutableNode.Builder result = new ImmutableNode.Builder(); 130 result.name(node1.getNodeName()); 131 132 // attributes of the first node take precedence 133 result.addAttributes(node2.getAttributes()); 134 result.addAttributes(node1.getAttributes()); 135 136 // Check if nodes can be combined 137 final List<ImmutableNode> children2 = new LinkedList<>(node2.getChildren()); 138 for (final ImmutableNode child1 : node1.getChildren()) 139 { 140 final ImmutableNode child2 = findCombineNode(node1, node2, child1 141 ); 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 (final ImmutableNode c : children2) 155 { 156 result.addChild(c); 157 } 158 159 return result.create(); 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 * <ul> 175 * <li>In both the first and the second source node there is only one child 176 * node with the given name (no list structures).</li> 177 * <li>The given name is not in the list of known list nodes, i.e. it was 178 * not passed to the {@code addListNode()} method.</li> 179 * <li>None of these matching child nodes has a value.</li> 180 * </ul> 181 * <p> 182 * If all of these tests are successful, the matching child node of the 183 * second source node is returned. Otherwise the result is <b>null</b>. 184 * </p> 185 * 186 * @param node1 the first source node 187 * @param node2 the second source node 188 * @param child the child node of the first source node to be checked 189 * @return the matching child node of the second source node or <b>null</b> 190 * if there is none 191 */ 192 protected ImmutableNode findCombineNode(final ImmutableNode node1, 193 final ImmutableNode node2, final ImmutableNode child) 194 { 195 if (child.getValue() == null && !isListNode(child) 196 && HANDLER.getChildrenCount(node1, child.getNodeName()) == 1 197 && HANDLER.getChildrenCount(node2, child.getNodeName()) == 1) 198 { 199 final ImmutableNode child2 = 200 HANDLER.getChildren(node2, child.getNodeName()).get(0); 201 if (child2.getValue() == null) 202 { 203 return child2; 204 } 205 } 206 return null; 207 } 208}