1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 package org.openoffice.xmerge.merger.merge; 25 26 import org.w3c.dom.Element; 27 import org.w3c.dom.Node; 28 29 import org.openoffice.xmerge.ConverterCapabilities; 30 import org.openoffice.xmerge.MergeException; 31 import org.openoffice.xmerge.merger.MergeAlgorithm; 32 import org.openoffice.xmerge.merger.NodeMergeAlgorithm; 33 import org.openoffice.xmerge.merger.Iterator; 34 import org.openoffice.xmerge.merger.Difference; 35 import org.openoffice.xmerge.util.XmlUtil; 36 37 /** 38 * This is an implementation of the <code>MergeAlgorithm</code> interface. 39 * This class will merge two <code>Document</code> classes. It utilizes the 40 * appropriate class which implements {@link 41 * org.openoffice.xmerge.merger.NodeMergeAlgorithm 42 * NodeMergeAlgorithm} to perform the merge. 43 * 44 * @author smak 45 */ 46 public class DocumentMerge implements MergeAlgorithm { 47 48 private NodeMergeAlgorithm subDocumentMerge = null; 49 50 /** The capabilities of this converter. */ 51 protected ConverterCapabilities cc_; 52 53 54 /** 55 * Constructor 56 * 57 * @param cc The <code>ConverterCapabilities</code>. 58 * @param merge The <code>NodeMergeAlgorithm</code>. 59 */ DocumentMerge(ConverterCapabilities cc, NodeMergeAlgorithm merge)60 public DocumentMerge(ConverterCapabilities cc, NodeMergeAlgorithm merge) { 61 cc_ = cc; 62 subDocumentMerge = merge; 63 } 64 65 applyDifference(Iterator orgSeq, Iterator modSeq, Difference[] differences)66 public void applyDifference(Iterator orgSeq, Iterator modSeq, 67 Difference[] differences) throws MergeException { 68 69 70 // a quick test whether the differences array is in ascending order 71 int currentPosition = -1; 72 boolean haveDeleteOperation = false; 73 74 for (int i = 0; i < differences.length; i++) { 75 if (differences[i].getOrgPosition() > currentPosition) { 76 currentPosition = differences[i].getOrgPosition(); 77 if (differences[i].getOperation() == Difference.DELETE) { 78 haveDeleteOperation = true; 79 } else { 80 haveDeleteOperation = false; 81 } 82 } else if (differences[i].getOrgPosition() == currentPosition) { 83 if (differences[i].getOperation() == Difference.DELETE) { 84 haveDeleteOperation = true; 85 } else if (differences[i].getOperation() == Difference.ADD && 86 haveDeleteOperation == true) { 87 throw new MergeException( 88 "Differences array is not sorted. Delete before Add"); 89 } 90 } else { 91 throw new MergeException("Differences array need to be sorted."); 92 } 93 } 94 95 // reset sequence counters 96 orgSeq.start(); 97 int orgSeqCounter = 0; 98 99 modSeq.start(); 100 int modSeqCounter = 0; 101 102 // check for each diff unit in the diff array to apply the diff 103 for (int i = 0; i < differences.length; i++) { 104 105 Difference currentDiff = differences[i]; 106 107 int operation = currentDiff.getOperation(); 108 109 Object currentElement; 110 111 switch (operation) { 112 113 case Difference.DELETE: 114 // loop through the original sequence up to the expected 115 // position. note that we use delta (see above comment) 116 // also. we will just continue the counter without reset it. 117 for (; 118 orgSeqCounter < currentDiff.getOrgPosition(); 119 orgSeqCounter++, orgSeq.next()) { 120 // empty 121 } 122 123 // remove the Node. note that it will NOT affect the 124 // iterator sequence as ParaNodeIterator is a static one. 125 removeNode((Node)(orgSeq.currentElement())); 126 127 break; 128 129 // if it's an add operation, then get content from original seq 130 case Difference.ADD: 131 // loop through the modified sequence up to the expected 132 // position to get the content. As we don't need to modify 133 // the sequence. we don't need to use delta to do adjustment. 134 for (; 135 modSeqCounter < currentDiff.getModPosition(); 136 modSeqCounter++, modSeq.next()) { 137 // empty 138 } 139 140 currentElement = orgSeq.currentElement(); 141 142 for (; 143 orgSeqCounter < currentDiff.getOrgPosition(); 144 orgSeqCounter++, currentElement = orgSeq.next()) { 145 // empty 146 } 147 148 if (orgSeqCounter > orgSeq.elementCount() - 1) { 149 // append the element to the end of the original sequence 150 appendNode((Node)(orgSeq.currentElement()), 151 (Node)(modSeq.currentElement())); 152 } else { 153 // insert the element BEFORE the current element 154 insertNode((Node)(orgSeq.currentElement()), 155 (Node)(modSeq.currentElement())); 156 } 157 158 break; 159 160 case Difference.CHANGE: 161 for (; 162 modSeqCounter < currentDiff.getModPosition(); 163 modSeqCounter++, modSeq.next()) { 164 // empty 165 } 166 167 currentElement = orgSeq.currentElement(); 168 169 for (; 170 orgSeqCounter < currentDiff.getOrgPosition(); 171 orgSeqCounter++, currentElement = orgSeq.next()) { 172 // empty 173 } 174 175 if (subDocumentMerge == null) { 176 // use a simple replace if no row merge algorithm supply 177 replaceElement((Element)orgSeq.currentElement(), 178 (Element)modSeq.currentElement()); 179 } else { 180 subDocumentMerge.merge((Element)orgSeq.currentElement(), 181 (Element)modSeq.currentElement()); 182 183 } 184 break; 185 186 default: 187 break; 188 } 189 } 190 } 191 192 193 /** 194 * Removes the specified <code>Node</code>. 195 * 196 * @param node <code>Node</code> to remove. 197 */ removeNode(Node node)198 protected void removeNode(Node node) { 199 200 Node parent = node.getParentNode(); 201 parent.removeChild(node); 202 } 203 204 /** 205 * Appends <code>Node</code> after the specified <code>Node</code>. 206 * 207 * @param oldNode <code>Node</code> to append after. 208 * @param newNode <code>Node</code> to append. 209 */ appendNode(Node oldNode, Node newNode)210 protected void appendNode(Node oldNode, Node newNode) { 211 Node clonedNode = XmlUtil.deepClone(oldNode, newNode); 212 Node parent = oldNode.getParentNode(); 213 parent.appendChild(clonedNode); 214 } 215 216 217 /** 218 * Insert <code>Node</code> before the specified <code>Node</code>. 219 * 220 * @param oldNode <code>Node</code> to insert before. 221 * @param newNode <code>Node</code> to insert. 222 */ insertNode(Node oldNode, Node newNode)223 protected void insertNode(Node oldNode, Node newNode) { 224 Node clonedNode = XmlUtil.deepClone(oldNode, newNode); 225 Node parent = oldNode.getParentNode(); 226 parent.insertBefore(clonedNode, oldNode); 227 } 228 229 230 /** 231 * Replace <code>Element</code>. 232 * 233 * @param currElem <code>Element</code> to be replaced. 234 * @param newElem <code>Element</code> to replace. 235 */ replaceElement(Element currElem, Element newElem)236 protected void replaceElement(Element currElem, Element newElem) { 237 238 Node clonedNode = XmlUtil.deepClone(currElem, newElem); 239 Node parent = currElem.getParentNode(); 240 parent.replaceChild(clonedNode, currElem); 241 } 242 } 243 244