blob: 1234273655ed156444486c898e0d4bfd89c8bb73 [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
package org.openoffice.xmerge.merger.merge;
import java.util.List;
import org.w3c.dom.Node;
import org.openoffice.xmerge.merger.Difference;
import org.openoffice.xmerge.merger.NodeMergeAlgorithm;
import org.openoffice.xmerge.merger.diff.CharacterParser;
import org.openoffice.xmerge.merger.diff.CharArrayLCSAlgorithm;
import org.openoffice.xmerge.merger.diff.TextNodeEntry;
import org.openoffice.xmerge.util.Debug;
/**
* This is an implementation of the <code>NodeMergeAlgorithm</code>
* interface. It is used to merge two paragraph <code>Node</code>
* objects based on character comparisons.
*
* @author smak
*/
public final class CharacterBaseParagraphMerge
implements NodeMergeAlgorithm {
private class cacheCharArray {
public cacheCharArray(int cacheSize) {
}
}
/**
* Merge two paragraph <code>Node</code> by using Longest Common
* Subsequence (LCS) character algorithm defined in {@link
* org.openoffice.xmerge.merger.diff.CharArrayLCSAlgorithm
* CharArrayLCSAlgorithm}
*
* @param orgPara The original paragraph <code>Node</code>.
* @param modPara The modified paragraph <code>Node</code>.
*/
public void merge(Node orgPara, Node modPara) {
CharacterParser orgParser = new CharacterParser(orgPara);
CharacterParser modParser = new CharacterParser(modPara);
char[] orgCharArray = orgParser.getCharArray();
char[] modCharArray = modParser.getCharArray();
CharArrayLCSAlgorithm diffAlgo = new CharArrayLCSAlgorithm();
Difference[] diffResult = diffAlgo.computeDiffs(orgCharArray,
modCharArray);
// debug use
System.out.println("Diff Result: ");
for (int i = 0; i < diffResult.length; i++) {
Debug.log(Debug.INFO, diffResult[i].debug());
}
applyDifference(orgParser, modParser, diffResult);
}
private void applyDifference(CharacterParser orgParser,
CharacterParser modParser,
Difference[] diffs) {
List orgNodeList = orgParser.getNodeList();
List modNodeList = modParser.getNodeList();
int diffCount = 0;
int modNodeListCnt = 0;
int numNode = orgNodeList.size();
for (int i = 0; i < numNode; i++) {
int extraChar = 0;
int orgDiffCount = diffCount;
TextNodeEntry orgTextNode = (TextNodeEntry)(orgNodeList.get(i));
Debug.log(Debug.INFO, "checking node " + (i + 1) + " of " + numNode);
// check any difference in this node and estimate the new char num
for (; diffCount < diffs.length; diffCount++) {
Debug.log(Debug.INFO, " checking diff " + (diffCount + 1) +
" of " + diffs.length);
Debug.log(Debug.INFO, " OrgPosision <" +
diffs[diffCount].getOrgPosition() + "> diffCount <" +
diffCount + "> orgDiffCount <" + orgDiffCount + ">");
// don't need to check and diffs beyond the current node text
// range except the last node
if (diffs[diffCount].getOrgPosition() > orgTextNode.endChar() &&
i < numNode - 1) {
Debug.log(Debug.INFO, " breaking!");
break;
}
if (diffs[diffCount].getOrgPosition()
>= orgTextNode.startChar()) {
if (diffs[diffCount].getOperation() == Difference.DELETE) {
extraChar--;
} else if (diffs[diffCount].getOperation()
== Difference.ADD) {
extraChar++;
}
}
}
Debug.log(Debug.INFO, " final diffCount <" + diffCount +
"> final orgDiffCount <" + orgDiffCount + ">");
// will only try to merge if there is a difference in this node
if (diffCount > orgDiffCount) {
Debug.log(Debug.INFO, " There is a difference, doing merge");
Debug.log(Debug.INFO, " TextNode name <" +
orgTextNode.node().getNodeName() + ">");
Debug.log(Debug.INFO, " TextNode value <" +
orgTextNode.node().getNodeValue() + ">");
Debug.log(Debug.INFO, " TextNode start char <" +
orgTextNode.startChar() + "> TextNode end char <" +
orgTextNode.endChar() + ">");
Debug.log(Debug.INFO, " extraChar value <" + extraChar + ">");
coreMerge(orgDiffCount, diffCount, diffs, orgParser,
modParser, orgTextNode, extraChar);
}
}
}
private void coreMerge(int startDiffNum, int endDiffNum, Difference[] diffs,
CharacterParser orgParser, CharacterParser modParser,
TextNodeEntry orgTextNode, int extraChar) {
Node orgNode = orgTextNode.node();
char[] modTextArray = modParser.getCharArray();
String tmpString;
// Handle situation where getNodeValue returns null
//
if (orgNode.getNodeValue() != null)
tmpString = orgNode.getNodeValue();
else
tmpString = "";
char[] orgNodeText = tmpString.toCharArray();
char[] newNodeText;
if (orgNodeText.length + extraChar > 0)
newNodeText = new char[orgNodeText.length + extraChar];
else
newNodeText = new char[0];
int orgTextPosition = orgTextNode.startChar(); // used for block copy
int newTextPosition = 0; // used for block copy
int unChangedTextLength = 0;
char[] cacheCharArray = new char[endDiffNum - startDiffNum];
int cacheLength = 0;
int lastDiffOperation = Difference.UNCHANGE;
int lastDiffPosition = -1;
// starting to diff
//
for (int j = startDiffNum; j < endDiffNum; j++) {
// copy any contents before the diff
//
if (diffs[j].getOrgPosition() > orgTextPosition) {
// need to flush first
if (cacheLength > 0) {
System.arraycopy(cacheCharArray, 0,
newNodeText, newTextPosition, cacheLength);
newTextPosition += cacheLength;
// reset the markers
lastDiffPosition = -1;
lastDiffOperation = Difference.UNCHANGE;
cacheLength = 0;
}
// find out the length how many characters are
// untouched by the diff
unChangedTextLength = diffs[j].getOrgPosition() -
orgTextPosition;
System.arraycopy(orgNodeText,
orgTextPosition - orgTextNode.startChar(),
newNodeText, newTextPosition,
unChangedTextLength);
orgTextPosition += unChangedTextLength;
newTextPosition += unChangedTextLength;
}
// for any deleted characters, just skip without copy
// but still need to take care the cached characters
//
if (diffs[j].getOperation() == Difference.DELETE) {
orgTextPosition++;
// flush out the cache and copy the content to new Text
if (cacheLength > 0) {
System.arraycopy(cacheCharArray, 0,
newNodeText, newTextPosition, cacheLength);
newTextPosition += cacheLength;
// reset the markers
lastDiffPosition = -1;
lastDiffOperation = Difference.UNCHANGE;
cacheLength = 0;
}
continue;
// check whether we should flush the cache.
// For changed diffs, only continuous changes can be cached
// For Add diffs, only same insertion point can be cached
// and for both changed/add diffs, need to have same operation
// as last cached diffs.
} else {
if (lastDiffOperation != diffs[j].getOperation() ||
(diffs[j].getOperation() == Difference.CHANGE &&
diffs[j].getOrgPosition() != lastDiffPosition + 1) ||
(diffs[j].getOperation() == Difference.ADD &&
diffs[j].getOrgPosition() != lastDiffPosition)) {
// flush the cache
if (cacheLength > 0) {
System.arraycopy(cacheCharArray, 0, newNodeText,
newTextPosition, cacheLength);
newTextPosition += cacheLength;
// reset the markers
lastDiffPosition = -1;
lastDiffOperation = Difference.UNCHANGE;
cacheLength = 0;
}
}
// add the diffs to the cache, now the diffs will be either
// a new 'changed' char or is an adjacent following change of
// last difference
cacheCharArray[cacheLength] =
modTextArray[diffs[j].getModPosition()];
cacheLength++;
lastDiffOperation = diffs[j].getOperation();
lastDiffPosition = diffs[j].getOrgPosition();
// need to increment the original text position
// after we cached it
if (lastDiffOperation == Difference.CHANGE) {
orgTextPosition++;
}
}
}
// flush any contents remaining in the cache
if (cacheLength > 0) {
System.arraycopy(cacheCharArray, 0, newNodeText,
newTextPosition, cacheLength);
newTextPosition += cacheLength;
// no need to reset any cache-related info as this is a last flush
}
// copy any contents after all the diffs
int orgStartPosition = orgTextNode.startChar();
if (orgNodeText.length + orgStartPosition > orgTextPosition) {
unChangedTextLength = orgNodeText.length + orgStartPosition
- orgTextPosition;
System.arraycopy(orgNodeText, orgTextPosition - orgStartPosition,
newNodeText, newTextPosition,
unChangedTextLength);
}
// set the text to the original node if there are any diffs processed.
// can't use newNodeText.length to check as even it is empty, we may
// process a whole bunch of deletion already (i.e. the whole
// orgNodeText deleted).
if (endDiffNum > startDiffNum) {
String newString = new String(newNodeText);
orgNode.setNodeValue(newString);
}
}
}