
net.sf.javagimmicks.collections.diff.DifferenceUtils Maven / Gradle / Ivy
package net.sf.javagimmicks.collections.diff;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
/**
* This class serves as central entry point into the diff API and provides some additional
* helper methods around it. The main methods findDifferences()
take two
* (typed) {@link List}s as arguments and internally compare them with the
* LCS (longest common subsequences) algorithm.
* The resulting differences between the {@link List}s are returned in form of a
* {@link DifferenceList} object, which is actually a {@link List} of {@link Difference}
* objects (but containing some more specific logic).
* Each one of the {@link Difference} objects carries detailed information about one single
* difference between the two compared {@link List}s, like the start and end index of a
* deletion and/or addition or the changed elements.
* A {@link Comparator} may be passed as an additional parameter, in order to compare
* elements of the respective type. If the elements are not comparable and no
* {@link Comparator} is passed, they must implement {@link #equals(Object)} and {@link #hashCode()}
* in order to make the comparison work.
*
* This implementation class for the actual algorithm is strongly based upon the
* Diff
class from the java-diff project on
* http://www.incava.org.
* That class was refactored, adapted to Java 5 and is now used here.
*
*/
public class DifferenceUtils
{
/**
* Finds the differences between two provided {@link List}s using the "longest common subsequences" algorithm.
* @param the element type for the provided {@link List}s
* @param fromList the first {@link List} to be analyzed (called 'from' list)
* @param toList the second {@link List} to be analyzed (called 'to' list)
* @return the differences between the two {@link List}s encapsulated in a {@link DifferenceList} object
*/
public static DifferenceList findDifferences(List fromList, List toList)
{
return new DifferenceAlgorithm(fromList, toList, null).getDifferences();
}
/**
* Finds the differences between two provided arrays using the "longest common subsequences" algorithm.
* @param the element type for the provided arrays
* @param fromArray the first arrays to be analyzed (called 'from' list)
* @param toArray the second arrays to be analyzed (called 'to' list)
* @return the differences between the two arrays encapsulated in a {@link DifferenceList} object
*/
public static DifferenceList findDifferences(T[] fromArray, T[] toArray)
{
return findDifferences(Arrays.asList(fromArray), Arrays.asList(toArray));
}
/**
* Finds the differences between two provided {@link List}s using the "longest common subsequences" algorithm.
* @param the element type for the provided {@link List}s
* @param fromList the first {@link List} to be analyzed (called 'from' list)
* @param toList the second {@link List} to be analyzed (called 'to' list)
* @param comparator a {@link Comparator} able to compare elements of the respective type
* @return the differences between the two {@link List}s encapsulated in a {@link DifferenceList} object
*/
public static DifferenceList findDifferences(List fromList, List toList, Comparator comparator)
{
return new DifferenceAlgorithm(fromList, toList, comparator).getDifferences();
}
/**
* Finds the differences between two provided arrays using the "longest common subsequences" algorithm.
* @param the element type for the provided arrays
* @param fromArray the first arrays to be analyzed (called 'from' list)
* @param toArray the second arrays to be analyzed (called 'to' list)
* @param comparator a {@link Comparator} able to compare elements of the respective type
* @return the differences between the two arrays encapsulated in a {@link DifferenceList} object
*/
public static DifferenceList findDifferences(T[] fromArray, T[] toArray, Comparator comparator)
{
return findDifferences(Arrays.asList(fromArray), Arrays.asList(toArray), comparator);
}
/**
* Returns an inverted {@link Difference} object for a given one.
* Inverted means that delete and add information are exchanged.
* @param the element type of the {@link Difference} object
* @param difference the {@link Difference} object to invert
* @return an inverted version of the provided {@link Difference} object using the original
* one in background
*/
public static Difference getInvertedDifference(Difference difference)
{
return new InvertedDifference(difference);
}
/**
* Returns an inverted {@link DifferenceList} object for a given one.
* Inverted means that delete and add information are exchanged.
* @param the element type of the {@link DifferenceList} object
* @param differenceList the {@link DifferenceList} object to invert
* @return an inverted version of the provided {@link DifferenceList} object using the original
* one in background
*/
public static DifferenceList getInvertedDifferenceList(DifferenceList differenceList)
{
return new InvertedDifferenceList(differenceList);
}
/**
* Applies the difference information contained in a given
* {@link Difference} object to a target {@link List}.
* The means: remove there all elements denoted by the "delete" information of the
* {@link Difference} object at the right position
* and add all elements from the "add" {@link List} at the same position.
* @param the element type of the {@link Difference} object and target {@link List}
* @param d the {@link Difference} object to apply
* @param targetList the {@link List} where to apply the changes
*/
public static void applyDifference(Difference d, List targetList)
{
// We need this index for deleting AND adding (to know where to add)
int deleteStartIndex = d.getDeleteStartIndex();
if(d.isDelete())
{
// Get a ListIterator at the delete position
ListIterator iterator = targetList.listIterator(deleteStartIndex);
// Delete as many elements as are contained in the delete list
for(int i = 0; i < d.getDeleteList().size(); ++i)
{
iterator.next();
iterator.remove();
}
}
if(d.isAdd())
{
// Adding is easy as we have addAll(); just use the right index and add the complete AddList
targetList.addAll(deleteStartIndex, d.getAddList());
}
}
/**
* Applies the difference information contained in a given
* {@link DifferenceList} object to a target {@link List}.
* The means: apply step by step (in reverse order) all {@link Difference} object inside
* of the {@link DifferenceList}.
* If a {@link List} with the same elements like the original from {@link List} is provided here,
* it will have after execution the same elements like the original to {@link List}.
* @param the element type of the {@link DifferenceList} object and target {@link List}
* @param diffList the {@link DifferenceList} object to apply
* @param targetList the {@link List} where to apply the changes
*/
public static void applyDifferenceList(DifferenceList diffList, List targetList)
{
// ATTENTION: In any case, apply in reverse order; otherwise would cause data corruption
ListIterator> iterator = diffList.listIterator(diffList.size());
while(iterator.hasPrevious())
{
applyDifference(iterator.previous(), targetList);
}
}
public static String toString(Difference d)
{
return new StringBuilder()
.append("del(")
.append(d.getDeleteStartIndex())
.append(", ")
.append(d.getDeleteEndIndex())
.append(")")
.append("|")
.append("add(")
.append(d.getAddStartIndex())
.append(", ")
.append(d.getAddEndIndex())
.append(")")
.toString();
}
protected static class InvertedDifference implements Difference
{
protected final Difference _original;
protected InvertedDifference(Difference original)
{
_original = original;
}
public int getAddStartIndex()
{
return _original.getDeleteStartIndex();
}
public int getAddEndIndex()
{
return _original.getDeleteEndIndex();
}
public List getAddList()
{
return _original.getDeleteList();
}
public boolean isAdd()
{
return _original.isDelete();
}
public int getDeleteStartIndex()
{
return _original.getAddStartIndex();
}
public int getDeleteEndIndex()
{
return _original.getAddEndIndex();
}
public List getDeleteList()
{
return _original.getAddList();
}
public boolean isDelete()
{
return _original.isAdd();
}
public Difference invert()
{
return _original;
}
public String toString()
{
return DifferenceUtils.toString(this);
}
}
protected static class InvertedDifferenceList extends AbstractList> implements DifferenceList
{
protected final DifferenceList _original;
protected InvertedDifferenceList(DifferenceList original)
{
_original = original;
}
@Override
public void add(int index, Difference element)
{
_original.add(index, element.invert());
}
@Override
public Difference get(int index)
{
return _original.get(index).invert();
}
@Override
public Difference remove(int index)
{
return _original.remove(index).invert();
}
@Override
public Difference set(int index, Difference element)
{
return _original.set(index, element.invert()).invert();
}
public void applyTo(List list)
{
DifferenceUtils.applyDifferenceList(this, list);
}
public DifferenceList invert()
{
return _original;
}
@Override
public int size()
{
return _original.size();
}
}
}