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;
import net.sf.javagimmicks.collections.diff.Difference.Range;
/**
* This class serves as central entry point into the diff API and provides some
* additional helper methods around it.
*
* The main methods findDifferences()
take as arguments two (typed)
* {@link List}s 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 (which seems to be
* offline in between). That class was refactored, adapted to Java 5 and is now
* used here.
*
*/
public class DifferenceUtils
{
private 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(final List fromList, final 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(final T[] fromArray, final 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(final List fromList, final List toList,
final 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(final T[] fromArray, final T[] toArray,
final 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(final 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(final 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(final Difference d, final List targetList)
{
final Range deleteRange = d.deleteRange();
final Range addRange = d.addRange();
if (deleteRange.exists())
{
// Get a ListIterator at the delete position
final ListIterator iterator = targetList.listIterator(deleteRange.getStartIndex());
// Delete as many elements as are contained in the delete list
for (int i = 0; i < deleteRange.size(); ++i)
{
iterator.next();
iterator.remove();
}
}
if (addRange.exists())
{
// Adding is easy as we have addAll(); just use the right index and add
// the complete AddList
targetList.addAll(deleteRange.getStartIndex(), addRange);
}
}
/**
* 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(final DifferenceList diffList, final List targetList)
{
// ATTENTION: In any case, apply in reverse order; otherwise would cause
// data corruption
final ListIterator> iterator = diffList.listIterator(diffList.size());
while (iterator.hasPrevious())
{
applyDifference(iterator.previous(), targetList);
}
}
public static String toString(final Difference> d)
{
final Range> deleteRange = d.deleteRange();
final Range> addRange = d.addRange();
return new StringBuilder()
.append("del(")
.append(deleteRange.getStartIndex())
.append(", ")
.append(deleteRange.getEndIndex())
.append(")")
.append("|")
.append("add(")
.append(addRange.getStartIndex())
.append(", ")
.append(addRange.getEndIndex())
.append(")")
.toString();
}
protected static class InvertedDifference implements Difference
{
protected final Difference _original;
protected InvertedDifference(final Difference original)
{
_original = original;
}
@Override
public Range deleteRange()
{
return _original.addRange();
}
@Override
public Range addRange()
{
return _original.deleteRange();
}
@Override
public Difference invert()
{
return _original;
}
@Override
public String toString()
{
return DifferenceUtils.toString(this);
}
}
protected static class InvertedDifferenceList extends AbstractList> implements DifferenceList
{
protected final DifferenceList _original;
protected InvertedDifferenceList(final DifferenceList original)
{
_original = original;
}
@Override
public void add(final int index, final Difference element)
{
_original.add(index, element.invert());
}
@Override
public Difference get(final int index)
{
return _original.get(index).invert();
}
@Override
public Difference remove(final int index)
{
return _original.remove(index).invert();
}
@Override
public Difference set(final int index, final Difference element)
{
return _original.set(index, element.invert()).invert();
}
@Override
public void applyTo(final List list)
{
DifferenceUtils.applyDifferenceList(this, list);
}
@Override
public DifferenceList invert()
{
return _original;
}
@Override
public int size()
{
return _original.size();
}
}
}