net.sf.javagimmicks.collections.diff.DifferenceAlgorithm Maven / Gradle / Ivy
/**
*
*/
package net.sf.javagimmicks.collections.diff;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
class DifferenceAlgorithm
{
protected final List _fromList;
protected final List _toList;
protected final Comparator _comparator;
protected final DifferenceList _differences = new DefaultDifferenceList();
protected DefaultDifference _pending;
protected DifferenceAlgorithm(List fromList, List toList, Comparator comparator)
{
_fromList = fromList;
_toList = toList;
_comparator = comparator;
traverseSequences();
// add the last difference, if pending:
if (_pending != null)
{
_differences.add(_pending);
}
}
public DifferenceList getDifferences()
{
return _differences;
}
/**
* Traverses the sequences, seeking the longest common subsequences,
* invoking the methods finishedA
, finishedB
,
* onANotB
, and onBNotA
.
*/
protected void traverseSequences()
{
List matches = getLongestCommonSubsequences();
final int lastIndexA = _fromList.size() - 1;
final int lastIndexB = _toList.size() - 1;
int indexB = 0;
int indexA;
int lastMatchIndex = matches.size() - 1;
for (indexA = 0; indexA <= lastMatchIndex; ++indexA)
{
Integer bLine = matches.get(indexA);
if (bLine == null)
{
onANotB(indexA, indexB);
}
else
{
while (indexB < bLine)
{
onBNotA(indexA, indexB++);
}
onMatch(indexA, indexB++);
}
}
boolean calledFinishA = false;
boolean calledFinishB = false;
while (indexA <= lastIndexA || indexB <= lastIndexB)
{
// last A?
if (indexA == lastIndexA + 1 && indexB <= lastIndexB)
{
if (!calledFinishA && callFinishedA())
{
finishedA(lastIndexA);
calledFinishA = true;
}
else
{
while (indexB <= lastIndexB)
{
onBNotA(indexA, indexB++);
}
}
}
// last B?
if (indexB == lastIndexB + 1 && indexA <= lastIndexA)
{
if (!calledFinishB && callFinishedB())
{
finishedB(lastIndexB);
calledFinishB = true;
}
else
{
while (indexA <= lastIndexA)
{
onANotB(indexA++, indexB);
}
}
}
if (indexA <= lastIndexA)
{
onANotB(indexA++, indexB);
}
if (indexB <= lastIndexB)
{
onBNotA(indexA, indexB++);
}
}
}
/**
* Override and return true in order to have finishedA
* invoked at the last element in the a
list.
*/
protected boolean callFinishedA()
{
return false;
}
/**
* Override and return true in order to have finishedB
* invoked at the last element in the b
list.
*/
protected boolean callFinishedB()
{
return false;
}
/**
* Invoked at the last element in a
, if
* callFinishedA
returns true.
*/
protected void finishedA(int lastA)
{
}
/**
* Invoked at the last element in b
, if
* callFinishedB
returns true.
*/
protected void finishedB(int lastB)
{
}
/**
* Invoked for elements in a
and not in b
.
*/
protected void onANotB(int indexA, int indexB)
{
if (_pending == null)
{
_pending = new DefaultDifference();
_pending.setDeleteStartIndex(indexA);
_pending.setDeleteEndIndex(indexA);
_pending.setAddStartIndex(indexB);
_pending.setFromList(_fromList);
_pending.setToList(_toList);
}
else
{
setDeleted(_pending, indexA);
}
}
/**
* Invoked for elements in b
and not in a
.
*/
protected void onBNotA(int indexA, int indexB)
{
if (_pending == null)
{
_pending = new DefaultDifference();
_pending.setDeleteStartIndex(indexA);
_pending.setAddStartIndex(indexB);
_pending.setAddEndIndex(indexB);
_pending.setFromList(_fromList);
_pending.setToList(_toList);
}
else
{
setAdded(_pending, indexB);
}
}
/**
* Invoked for elements matching in a
and b
.
*/
protected void onMatch(int indexA, int indexB)
{
if (_pending != null)
{
_differences.add(_pending);
_pending = null;
}
}
/**
* Compares the two objects, using the comparator provided with the
* constructor, if any.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected boolean equals(T x, T y)
{
if(_comparator == null)
{
if(x instanceof Comparable)
{
return ((Comparable)x).compareTo(y) == 0;
}
else
{
return x.equals(y);
}
}
else
{
return _comparator.compare(x, y) == 0;
}
}
/**
* Returns an array of the longest common subsequences.
*/
protected List getLongestCommonSubsequences()
{
int startIndexA = 0;
int endIndexA = _fromList.size() - 1;
int startIndexB = 0;
int endIndexB = _toList.size() - 1;
final TreeMap matches = new TreeMap();
while (startIndexA <= endIndexA && startIndexB <= endIndexB
&& equals(_fromList.get(startIndexA), _toList.get(startIndexB)))
{
matches.put(startIndexA++, startIndexB++);
}
while (startIndexA <= endIndexA && startIndexB <= endIndexB
&& equals(_fromList.get(endIndexA), _toList.get(endIndexB)))
{
matches.put(endIndexA--, endIndexB--);
}
final Map> bMatches;
if(_comparator == null)
{
if(_fromList.get(0) instanceof Comparable>)
{
bMatches = new TreeMap>();
}
else
{
bMatches = new HashMap>();
}
}
else
{
bMatches = new TreeMap>(_comparator);
}
for (int indexB = startIndexB; indexB <= endIndexB; ++indexB)
{
T key = _toList.get(indexB);
List positions = bMatches.get(key);
if (positions == null)
{
positions = new ArrayList();
bMatches.put(key, positions);
}
positions.add(indexB);
}
final TreeMap thresh = new TreeMap();
final TreeMap links = new TreeMap();
for (int indexA = startIndexA; indexA <= endIndexA; ++indexA)
{
T key = _fromList.get(indexA); // keygen here.
List positions = bMatches.get(key);
if (positions == null)
{
continue;
}
Integer k = 0;
ListIterator positionIter = positions.listIterator(positions.size());
while (positionIter.hasPrevious())
{
Integer j = positionIter.previous();
k = insert(thresh, j, k);
if (k != null)
{
Link value = k > 0 ? links.get(k - 1) : null;
links.put(k, new Link(value, indexA, j));
}
}
}
if (!thresh.isEmpty())
{
for (Link link = links.get(thresh.lastKey()); link != null; link = link._link)
{
matches.put(link._x, link._y);
}
}
return toList(matches);
}
protected static List toList(TreeMap map)
{
Integer[] result = new Integer[map.size() == 0 ? 0 : 1 + map.lastKey()];
for(Entry entry : map.entrySet())
{
result[entry.getKey()] = entry.getValue();
}
return Arrays.asList(result);
}
protected static boolean isNonzero(Integer i)
{
return i != null && i != 0;
}
protected boolean isGreaterThan(TreeMap thresh, Integer index, Integer val)
{
Integer lhs = thresh.get(index);
return lhs != null && (val != null && lhs > val);
}
protected boolean isLessThan(TreeMap thresh, Integer index, Integer val)
{
Integer lhs = thresh.get(index);
return lhs != null && (val == null || lhs < val);
}
/**
* Adds the given value to the "end" of the threshold map, that is, with the
* greatest index/key.
*/
protected void append(TreeMap thresh, Integer value)
{
int addIdx = thresh.isEmpty() ? 0 : thresh.lastKey() + 1;
thresh.put(addIdx, value);
}
/**
* Inserts the given values into the threshold map.
*/
protected Integer insert(TreeMap thresh, Integer j, Integer k)
{
if (isNonzero(k) && isGreaterThan(thresh, k, j)
&& isLessThan(thresh, k - 1, j))
{
thresh.put(k, j);
}
else
{
int highIndex = -1;
if (isNonzero(k))
{
highIndex = k;
}
else if (!thresh.isEmpty())
{
highIndex = thresh.lastKey();
}
// off the end?
if (highIndex == -1 || j.compareTo(thresh.get(thresh.lastKey())) > 0)
{
append(thresh, j);
k = highIndex + 1;
}
else
{
// binary search for insertion point:
int lowIndex = 0;
while (lowIndex <= highIndex)
{
int index = (highIndex + lowIndex) / 2;
Integer val = thresh.get(index);
int compareResult = j.compareTo(val);
if (compareResult == 0)
{
return null;
}
else if (compareResult > 0)
{
lowIndex = index + 1;
}
else
{
highIndex = index - 1;
}
}
thresh.put(lowIndex, j);
k = lowIndex;
}
}
return k;
}
protected static void setDeleted(DefaultDifference> d, int index)
{
d.setDeleteStartIndex(Math.min(index, d.getDeleteStartIndex()));
d.setDeleteEndIndex(Math.max(index, d.getDeleteEndIndex()));
}
protected static void setAdded(DefaultDifference> d, int index)
{
d.setAddStartIndex(Math.min(index, d.getAddStartIndex()));
d.setAddEndIndex(Math.max(index, d.getAddEndIndex()));
}
protected static class Link
{
protected Link(Link link, Integer x, Integer y)
{
_link = link;
_x = x;
_y = y;
}
public final Link _link;
public final Integer _x;
public final Integer _y;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy