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