/*
* Copyright 2007, Plutext Pty Ltd.
*
* This file is part of Docx4all.
Docx4all is free software: you can redistribute it and/or modify
it under the terms of version 3 of the GNU General Public License
as published by the Free Software Foundation.
Docx4all is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Docx4all. If not, see <http://www.gnu.org/licenses/>.
*/
package org.plutext.client.diffengine;
import java.util.ArrayList;
import java.util.Collections;
import org.plutext.client.Skeleton;
import org.plutext.client.TextLine;
/**
* @author Jojada Tirtowidjojo - 02/07/2008
*/
public class DiffEngine {
private IDiffList<?> _source;
public IDiffList<?> getSource() {
return _source;
}
private IDiffList<?> _dest;
public IDiffList<?> getDestination() {
return _dest;
}
private ArrayList<DiffResultSpan> _matchList;
private DiffEngineLevel _level;
private DiffStateList _stateList;
private ArrayList<DiffResultSpan> _diffLines = null;
public ArrayList<DiffResultSpan> getDiffLines() {
return _diffLines;
}
public DiffEngine() {
_source = null;
_dest = null;
_matchList = null;
_stateList = null;
_level = DiffEngineLevel.FAST_IMPERFECT;
}
@SuppressWarnings("unchecked")
private int getSourceMatchLength(int destIndex, int sourceIndex,
int maxLength) {
int matchCount;
for (matchCount = 0; matchCount < maxLength; matchCount++) {
Comparable dest = _dest.getByIndex(destIndex + matchCount);
Comparable src = _source.getByIndex(sourceIndex + matchCount);
int result = ((Comparable) dest).compareTo(src);
if (result != 0) {
break;
}
}
return matchCount;
}
private void getLongestSourceMatch(DiffState curItem, int destIndex,
int destEnd, int sourceStart, int sourceEnd) {
int maxDestLength = (destEnd - destIndex) + 1;
int curLength = 0;
int curBestLength = 0;
int curBestIndex = -1;
int maxLength = 0;
for (int sourceIndex = sourceStart; sourceIndex <= sourceEnd; sourceIndex++) {
maxLength = Math.min(maxDestLength, (sourceEnd - sourceIndex) + 1);
if (maxLength <= curBestLength) {
// No chance to find a longer one any more
break;
}
curLength = getSourceMatchLength(destIndex, sourceIndex, maxLength);
if (curLength > curBestLength) {
// This is the best match so far
curBestIndex = sourceIndex;
curBestLength = curLength;
}
// jump over the match
sourceIndex += curBestLength;
}
// DiffState cur = _stateList.GetByIndex(destIndex);
if (curBestIndex == -1) {
curItem.setNoMatch();
} else {
curItem.setMatch(curBestIndex, curBestLength);
}
}
private void processRange(int destStart, int destEnd, int sourceStart,
int sourceEnd) {
int curBestIndex = -1;
int curBestLength = -1;
int maxPossibleDestLength = 0;
DiffState curItem = null;
DiffState bestItem = null;
for (int destIndex = destStart; destIndex <= destEnd; destIndex++) {
maxPossibleDestLength = (destEnd - destIndex) + 1;
if (maxPossibleDestLength <= curBestLength) {
// we won't find a longer one even if we looked
break;
}
curItem = _stateList.getByIndex(destIndex);
if (!curItem.hasValidLength(sourceStart, sourceEnd,
maxPossibleDestLength)) {
// recalc new best length since it isn't valid or has never been
// done.
getLongestSourceMatch(curItem, destIndex, destEnd, sourceStart,
sourceEnd);
}
if (curItem.getDiffStatus() == DiffState.DiffStatus.MATCHED) {
switch (_level) {
case FAST_IMPERFECT:
if (curItem.getLength() > curBestLength) {
// this is longest match so far
curBestIndex = destIndex;
curBestLength = curItem.getLength();
bestItem = curItem;
}
// Jump over the match
destIndex += curItem.getLength() - 1;
break;
case MEDIUM:
if (curItem.getLength() > curBestLength) {
// this is longest match so far
curBestIndex = destIndex;
curBestLength = curItem.getLength();
bestItem = curItem;
// Jump over the match
destIndex += curItem.getLength() - 1;
}
break;
default:
if (curItem.getLength() > curBestLength) {
// this is longest match so far
curBestIndex = destIndex;
curBestLength = curItem.getLength();
bestItem = curItem;
}
break;
}
}
}
if (curBestIndex < 0) {
// we are done - there are no matches in this span
} else {
int sourceIndex = bestItem.getStartIndex();
_matchList.add(DiffResultSpan.createNoChange(curBestIndex,
sourceIndex, curBestLength));
if (destStart < curBestIndex) {
// Still have more lower destination data
if (sourceStart < sourceIndex) {
// Still have more lower source data
// Recursive call to process lower indexes
processRange(destStart, curBestIndex - 1, sourceStart,
sourceIndex - 1);
}
}
int upperDestStart = curBestIndex + curBestLength;
int upperSourceStart = sourceIndex + curBestLength;
if (destEnd > upperDestStart) {
// we still have more upper dest data
if (sourceEnd > upperSourceStart) {
// set still have more upper source data
// Recursive call to process upper indexes
processRange(upperDestStart, destEnd, upperSourceStart,
sourceEnd);
}
}
}
}
public <T> long processDiff(
IDiffList<? extends T> source,
IDiffList<? extends T> destination,
DiffEngineLevel level) {
_level = level;
return processDiff(source, destination);
}
public long processDiff(
IDiffList<?> source,
IDiffList<?> destination) {
long timeStart = System.currentTimeMillis();
_source = source;
_dest = destination;
_matchList = new ArrayList<DiffResultSpan>();
int dcount = _dest.count();
int scount = _source.count();
if ((dcount > 0) && (scount > 0)) {
_stateList = new DiffStateList(dcount);
processRange(0, dcount - 1, 0, scount - 1);
}
diffReport();
return System.currentTimeMillis() - timeStart;
}
private boolean addChanges(ArrayList<DiffResultSpan> report, int curDest,
int nextDest, int curSource, int nextSource) {
boolean retval = false;
int diffDest = nextDest - curDest;
int diffSource = nextSource - curSource;
int minDiff = 0;
// if (diffDest > 0)
// {
// if (diffSource > 0)
// {
// minDiff = Math.Min(diffDest,diffSource);
// report.Add(DiffResultSpan.CreateReplace(curDest,curSource,minDiff));
// if (diffDest > diffSource)
// {
// curDest+=minDiff;
// report.Add(DiffResultSpan.CreateAddDestination(curDest,diffDest -
// diffSource));
// }
// else
// {
// if (diffSource > diffDest)
// {
// curSource+= minDiff;
// report.Add(DiffResultSpan.CreateDeleteSource(curSource,diffSource -
// diffDest));
// }
// }
// }
// else
// {
// report.Add(DiffResultSpan.CreateAddDestination(curDest,diffDest));
// }
// retval = true;
// }
// else
// {
// if (diffSource > 0)
// {
// report.Add(DiffResultSpan.CreateDeleteSource(curSource,diffSource));
// retval = true;
// }
// }
if (diffDest > 0) {
report.add(DiffResultSpan.createAddDestination(curDest, diffDest));
retval = true;
}
if (diffSource > 0) {
report
.add(DiffResultSpan.createDeleteSource(curSource,
diffSource));
retval = true;
}
return retval;
}
public void diffReport() {
_diffLines = new ArrayList<DiffResultSpan>();
int dcount = _dest.count();
int scount = _source.count();
// Deal with the special case of empty files
if (dcount == 0) {
if (scount > 0) {
_diffLines.add(DiffResultSpan.createDeleteSource(0, scount));
}
return;
} else {
if (scount == 0) {
_diffLines.add(DiffResultSpan.createAddDestination(0, dcount));
return;
}
}
Collections.sort(_matchList);
int curDest = 0;
int curSource = 0;
DiffResultSpan last = null;
// Process each match record
for (DiffResultSpan drs : _matchList) {
if ((!addChanges(_diffLines, curDest, drs.getDestIndex(),
curSource, drs.getSourceIndex()))
&& (last != null)) {
last.addLength(drs.getLength());
} else {
_diffLines.add(drs);
}
curDest = drs.getDestIndex() + drs.getLength();
curSource = drs.getSourceIndex() + drs.getLength();
last = drs;
}
// Process any tail end data
addChanges(_diffLines, curDest, dcount, curSource, scount);
}
public String Results(Skeleton source, Skeleton destination) {
String result = "";
int cnt = 1;
int i;
for (DiffResultSpan drs : getDiffLines()) {
switch (drs.getDiffResultSpanStatus()) {
case DELETE_SOURCE:
for (i = 0; i < drs.getLength(); i++) {
result += "\n"
+ ((TextLine) source.getByIndex(drs
.getSourceIndex()
+ i)).getLine()
+ " not at this location in dest";
cnt++;
}
break;
case NOCHANGE:
for (i = 0; i < drs.getLength(); i++) {
result += "\n"
+ ((TextLine) source.getByIndex(drs
.getSourceIndex()
+ i)).getLine()
+ "\t"
+ ((TextLine) destination.getByIndex(drs
.getDestIndex()
+ i)).getLine() + " (no change)";
cnt++;
}
break;
case ADD_DESTINATION:
for (i = 0; i < drs.getLength(); i++) {
result += "\n"
+ "---"
+ "\t"
+ ((TextLine) destination.getByIndex(drs
.getDestIndex()
+ i)).getLine()
+ " not at this location in source";
cnt++;
}
break;
case REPLACE:
for (i = 0; i < drs.getLength(); i++) {
result += "\n"
+ ((TextLine) source.getByIndex(drs
.getSourceIndex()
+ i)).getLine()
+ "\t"
+ ((TextLine) destination.getByIndex(drs
.getDestIndex()
+ i)).getLine() + " replaced ";
cnt++;
}
break;
}
}
return result;
}
}// DiffEngine class