/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.compare.rangedifferencer;
import java.util.ArrayList;
import java.util.List;
/**
* A <code>RangeDifferencer</code> finds the differences between two or three <code>IRangeComparator</code>s.
* <p>
* To use the differencer, clients provide an <code>IRangeComparator</code>
* that breaks their input data into a sequence of comparable entities. The differencer
* returns the differences among these sequences as an array of <code>RangeDifference</code> objects
* (<code>findDifferences</code> methods).
* Every <code>RangeDifference</code> represents a single kind of difference
* and the corresponding ranges of the underlying comparable entities in the
* left, right, and optionally ancestor sides.
* <p>
* Alternatively, the <code>findRanges</code> methods not only return objects for
* the differing ranges but for non-differing ranges too.
* <p>
* The algorithm used is an objectified version of one described in:
* <it>A File Comparison Program,</it> by Webb Miller and Eugene W. Myers,
* Software Practice and Experience, Vol. 15, Nov. 1985.
*
* @see IRangeComparator
* @see RangeDifference
*/
public final class RangeDifferencer
{
private static final RangeDifference[] EMPTY_RESULT = new RangeDifference[0];
/* (non Javadoc)
* Cannot be instantiated!
*/
private RangeDifferencer()
{
// nothing to do
}
/**
* Finds the differences between two <code>IRangeComparator</code>s.
* The differences are returned as an array of <code>RangeDifference</code>s.
* If no differences are detected an empty array is returned.
*
* @param left the left range comparator
* @param right the right range comparator
* @return an array of range differences, or an empty array if no differences were found
* @since 2.0
*/
public static RangeDifference[] findDifferences(IRangeComparator left,
IRangeComparator right)
{
return RangeComparatorLCS.findDifferences(left, right);
}
/**
* Finds the differences among three <code>IRangeComparator</code>s.
* The differences are returned as a list of <code>RangeDifference</code>s.
* If no differences are detected an empty list is returned.
* If the ancestor range comparator is <code>null</code>, a two-way
* comparison is performed.
*
* @param ancestor the ancestor range comparator or <code>null</code>
* @param left the left range comparator
* @param right the right range comparator
* @return an array of range differences, or an empty array if no differences were found
* @since 2.0
*/
public static RangeDifference[] findDifferences(IRangeComparator ancestor,
IRangeComparator left, IRangeComparator right)
{
if (ancestor == null)
{
return findDifferences(left, right);
}
RangeDifference[] leftAncestorScript = null;
RangeDifference[] rightAncestorScript = findDifferences(ancestor, right);
if (rightAncestorScript != null)
{
leftAncestorScript = findDifferences(ancestor, left);
}
if (rightAncestorScript == null || leftAncestorScript == null)
{
return null;
}
DifferencesIterator myIter = new DifferencesIterator(rightAncestorScript);
DifferencesIterator yourIter = new DifferencesIterator(leftAncestorScript);
List diff3 = new ArrayList();
diff3.add(new RangeDifference(RangeDifference.ERROR)); // add a sentinel
int changeRangeStart = 0;
int changeRangeEnd = 0;
//
// Combine the two two-way edit scripts into one
//
while (myIter.fDifference != null || yourIter.fDifference != null)
{
DifferencesIterator startThread;
myIter.removeAll();
yourIter.removeAll();
//
// take the next diff that is closer to the start
//
if (myIter.fDifference == null)
{
startThread = yourIter;
}
else if (yourIter.fDifference == null)
{
startThread = myIter;
}
else
{ // not at end of both scripts take the lowest range
if (myIter.fDifference.fLeftStart <= yourIter.fDifference.fLeftStart) // 2 -> common (Ancestor) change range
{
startThread = myIter;
}
else
{
startThread = yourIter;
}
}
changeRangeStart = startThread.fDifference.fLeftStart;
changeRangeEnd = startThread.fDifference.leftEnd();
startThread.next();
//
// check for overlapping changes with other thread
// merge overlapping changes with this range
//
DifferencesIterator other = startThread.other(myIter, yourIter);
while (other.fDifference != null
&& other.fDifference.fLeftStart <= changeRangeEnd)
{
int newMax = other.fDifference.leftEnd();
other.next();
if (newMax >= changeRangeEnd)
{
changeRangeEnd = newMax;
other = other.other(myIter, yourIter);
}
}
diff3.add(createRangeDifference3(myIter, yourIter, diff3, right, left,
changeRangeStart, changeRangeEnd));
}
// remove sentinel
diff3.remove(0);
return (RangeDifference[]) diff3.toArray(EMPTY_RESULT);
}
/**
* Finds the differences among two <code>IRangeComparator</code>s.
* In contrast to <code>findDifferences</code>, the result
* contains <code>RangeDifference</code> elements for non-differing ranges too.
*
* @param left the left range comparator
* @param right the right range comparator
* @return an array of range differences
* @since 2.0
*/
public static RangeDifference[] findRanges(IRangeComparator left,
IRangeComparator right)
{
RangeDifference[] in = findDifferences(left, right);
List out = new ArrayList();
RangeDifference rd;
int mstart = 0;
int ystart = 0;
for (int i = 0; i < in.length; i++)
{
RangeDifference es = in[i];
rd = new RangeDifference(RangeDifference.NOCHANGE, mstart, es
.rightStart()
- mstart,
ystart, es.leftStart() - ystart);
if (rd.maxLength() != 0)
{
out.add(rd);
}
out.add(es);
mstart = es.rightEnd();
ystart = es.leftEnd();
}
rd = new RangeDifference(RangeDifference.NOCHANGE, mstart, right
.getRangeCount()
- mstart,
ystart, left.getRangeCount() - ystart);
if (rd.maxLength() > 0)
{
out.add(rd);
}
return (RangeDifference[]) out.toArray(EMPTY_RESULT);
}
/**
* Finds the differences among three <code>IRangeComparator</code>s.
* In contrast to <code>findDifferences</code>, the result
* contains <code>RangeDifference</code> elements for non-differing ranges too.
* If the ancestor range comparator is <code>null</code>, a two-way
* comparison is performed.
*
* @param ancestor the ancestor range comparator or <code>null</code>
* @param left the left range comparator
* @param right the right range comparator
* @return an array of range differences
* @since 2.0
*/
public static RangeDifference[] findRanges(IRangeComparator ancestor,
IRangeComparator left, IRangeComparator right)
{
if (ancestor == null)
{
return findRanges(left, right);
}
RangeDifference[] in = findDifferences(ancestor, left, right);
List out = new ArrayList();
RangeDifference rd;
int mstart = 0;
int ystart = 0;
int astart = 0;
for (int i = 0; i < in.length; i++)
{
RangeDifference es = in[i];
rd = new RangeDifference(RangeDifference.NOCHANGE, mstart, es
.rightStart()
- mstart,
ystart, es.leftStart() - ystart, astart, es.ancestorStart() - astart);
if (rd.maxLength() > 0)
{
out.add(rd);
}
out.add(es);
mstart = es.rightEnd();
ystart = es.leftEnd();
astart = es.ancestorEnd();
}
rd = new RangeDifference(RangeDifference.NOCHANGE, mstart, right
.getRangeCount()
- mstart,
ystart, left.getRangeCount() - ystart, astart, ancestor.getRangeCount()
- astart);
if (rd.maxLength() > 0)
{
out.add(rd);
}
return (RangeDifference[]) out.toArray(EMPTY_RESULT);
}
//---- private methods
/*
* Creates a <code>RangeDifference3</code> given the
* state of two DifferenceIterators.
*/
private static RangeDifference createRangeDifference3(
DifferencesIterator myIter, DifferencesIterator yourIter, List diff3,
IRangeComparator right, IRangeComparator left, int changeRangeStart,
int changeRangeEnd)
{
int rightStart;
int rightEnd;
int leftStart;
int leftEnd;
int kind = RangeDifference.ERROR;
RangeDifference last = (RangeDifference) diff3.get(diff3.size() - 1);
if (myIter.getCount() == 0)
{ // only left changed
rightStart = changeRangeStart - last.ancestorEnd() + last.rightEnd();
rightEnd = changeRangeEnd - last.ancestorEnd() + last.rightEnd();
kind = RangeDifference.LEFT;
}
else
{
RangeDifference f = (RangeDifference) myIter.fRange.get(0);
RangeDifference l = (RangeDifference) myIter.fRange.get(myIter.fRange
.size() - 1);
rightStart = changeRangeStart - f.fLeftStart + f.fRightStart;
rightEnd = changeRangeEnd - l.leftEnd() + l.rightEnd();
}
if (yourIter.getCount() == 0)
{ // only right changed
leftStart = changeRangeStart - last.ancestorEnd() + last.leftEnd();
leftEnd = changeRangeEnd - last.ancestorEnd() + last.leftEnd();
kind = RangeDifference.RIGHT;
}
else
{
RangeDifference f = (RangeDifference) yourIter.fRange.get(0);
RangeDifference l = (RangeDifference) yourIter.fRange.get(yourIter.fRange
.size() - 1);
leftStart = changeRangeStart - f.fLeftStart + f.fRightStart;
leftEnd = changeRangeEnd - l.leftEnd() + l.rightEnd();
}
if (kind == RangeDifference.ERROR)
{ // overlapping change (conflict) -> compare the changed ranges
if (rangeSpansEqual(right, rightStart, rightEnd - rightStart, left,
leftStart, leftEnd - leftStart))
{
kind = RangeDifference.ANCESTOR;
}
else
{
kind = RangeDifference.CONFLICT;
}
}
return new RangeDifference(kind, rightStart, rightEnd - rightStart,
leftStart, leftEnd - leftStart, changeRangeStart, changeRangeEnd
- changeRangeStart);
}
/*
* Tests whether <code>right</code> and <code>left</code> changed in the same way
*/
private static boolean rangeSpansEqual(IRangeComparator right,
int rightStart, int rightLen, IRangeComparator left, int leftStart,
int leftLen)
{
if (rightLen == leftLen)
{
int i = 0;
for (i = 0; i < rightLen; i++)
{
if (!rangesEqual(right, rightStart + i, left, leftStart + i))
{
break;
}
}
if (i == rightLen)
{
return true;
}
}
return false;
}
/*
* Tests if two ranges are equal
*/
private static boolean rangesEqual(IRangeComparator a, int ai,
IRangeComparator b, int bi)
{
return a.rangesEqual(ai, b, bi);
}
}