org.incava.diff.Differ Maven / Gradle / Ivy
package org.incava.diff;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Compares two collections, returning a list of the additions, changes, and
* deletions between them. A Comparator
may be passed as an
* argument to the constructor, and will thus be used. If not provided, the
* initial value in the from
collection will be looked at to see if
* it supports the Comparable
interface. If so, its
* equals
and compareTo
methods will be invoked on the
* instances in the "from" and "to" collections; otherwise, for speed, hash
* codes from the objects will be used instead for comparison.
*
* The file FileDiff.java shows an example usage of this class, in an
* application similar to the Unix "diff" program.
*/
public abstract class Differ {
/**
* The source array, AKA the "from" values.
*/
private final List from;
/**
* The target array, AKA the "to" values.
*/
private final List to;
/**
* The list of differences, as DiffType
instances.
*/
private final List diffs;
/**
* The point at which the pending deletion starts.
*/
private Integer delStart = null;
/**
* The point at which the pending deletion ends.
*/
private Integer delEnd = null;
/**
* The point at which the pending addition starts.
*/
private Integer addStart = null;
/**
* The point at which the pending addition ends.
*/
private Integer addEnd = null;
/**
* The comparator used, if any.
*/
private final Comparator comparator;
/**
* Constructs the Differ object for the two arrays, using the given comparator.
*/
public Differ(ObjectType[] from, ObjectType[] to, Comparator comp) {
this(Arrays.asList(from), Arrays.asList(to), comp);
}
/**
* Constructs the Differ object for the two arrays, using the default
* comparison mechanism between the objects, such as equals
and
* compareTo
.
*/
public Differ(ObjectType[] from, ObjectType[] to) {
this(from, to, null);
}
/**
* Constructs the Differ object for the two collections, using the default
* comparison mechanism between the objects, such as equals
and
* compareTo
.
*/
public Differ(List from, List to) {
this(from, to, null);
}
/**
* Constructs the Differ object for the two collections, using the given
* comparator.
*/
public Differ(List from, List to, Comparator comp) {
this.from = from;
this.to = to;
this.comparator = comp;
this.diffs = new ArrayList();
}
/**
* Runs diff and returns the results.
*/
public List execute() {
traverseSequences();
addPending();
return diffs;
}
/**
* Runs diff and returns the results.
*
* @deprecated execute
is a more accurate and descriptive name.
*/
@Deprecated
public List diff() {
return execute();
}
/**
* Subclasses implement this to return their own subclass of
* Difference
.
*/
public abstract DiffType createDifference(Integer delStart, Integer delEnd, Integer addStart, Integer addEnd);
/**
* Adds the last difference, if pending.
*/
protected void addPending() {
if (delStart != null) {
diffs.add(createDifference(delStart, delEnd, addStart, addEnd));
delStart = null;
delEnd = null;
addStart = null;
addEnd = null;
}
}
/**
* Traverses the sequences, seeking the longest common subsequences,
* invoking the methods finishedFrom
, finishedTo
,
* onFromNotTo
, and onToNotFrom
.
*/
protected void traverseSequences() {
LCS lcs = new LCS(from, to, comparator);
List matches = lcs.getMatches();
int toIdx = 0;
int fromIdx = 0;
int lastMatch = matches.size() - 1;
while (fromIdx <= lastMatch) {
Integer toElement = matches.get(fromIdx);
if (toElement == null) {
onFromNotTo(fromIdx, toIdx);
}
else {
while (toIdx < toElement.intValue()) {
onToNotFrom(fromIdx, toIdx++);
}
onMatch(fromIdx, toIdx++);
}
fromIdx++;
}
traverseEndOfSequences(fromIdx, toIdx);
}
protected void traverseEndOfSequences(int fromIdx, int toIdx) {
int lastFrom = from.size() - 1;
int lastTo = to.size() - 1;
while (fromIdx <= lastFrom || toIdx <= lastTo) {
// last from?
if (fromIdx == lastFrom + 1 && toIdx <= lastTo) {
while (toIdx <= lastTo) {
onToNotFrom(fromIdx, toIdx++);
}
}
// last to?
if (toIdx == lastTo + 1 && fromIdx <= lastFrom) {
while (fromIdx <= lastFrom) {
onFromNotTo(fromIdx++, toIdx);
}
}
if (fromIdx <= lastFrom) {
onFromNotTo(fromIdx++, toIdx);
}
if (toIdx <= lastTo) {
onToNotFrom(fromIdx, toIdx++);
}
}
}
/**
* Invoked for elements in from
and not in to
.
*/
protected void onFromNotTo(int fromIdx, int toIdx) {
if (delStart == null) {
setIndices(fromIdx, fromIdx, toIdx, Difference.NONE);
}
else {
delStart = Math.min(fromIdx, delStart);
delEnd = Math.max(fromIdx, delEnd);
}
}
/**
* Invoked for elements in to
and not in from
.
*/
protected void onToNotFrom(int fromIdx, int toIdx) {
if (delStart == null) {
setIndices(fromIdx, Difference.NONE, toIdx, toIdx);
}
else {
addStart = Math.min(toIdx, addStart);
addEnd = Math.max(toIdx, addEnd);
}
}
private void setIndices(int delSt, int delEn, int addSt, int addEn) {
delStart = delSt;
delEnd = delEn;
addStart = addSt;
addEnd = addEn;
}
/**
* Invoked for elements matching in from
and to
.
*/
protected void onMatch(int fromIdx, int toIdx) {
addPending();
}
}