diff --git a/licenses/incava-diff.txt b/licenses/incava-diff.txt
new file mode 100644
index 00000000..a9c35ef3
--- /dev/null
+++ b/licenses/incava-diff.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2009, incava.org
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+
+ * Neither the name of incava.org nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/helma/util/Diff.java b/src/helma/util/Diff.java
index aa4d5f0d..4d815fd4 100644
--- a/src/helma/util/Diff.java
+++ b/src/helma/util/Diff.java
@@ -1,863 +1,659 @@
-/* $Log$
-/* Revision 1.1 2002/10/31 08:39:34 hannes
-/* Added GNU Diff class from http://www.bmsi.com/java/#diff
/*
- * Revision 1.3 2000/03/03 21:58:03 stuart
- * move discard_confusing_lines and shift_boundaries to class file_data
- *
- * Revision 1.2 2000/03/02 16:37:38 stuart
- * Add GPL and copyright
- *
+Copyright (c) 2009, incava.org
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+
+ * Neither the name of incava.org nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+
package helma.util;
-import java.util.Hashtable;
+import java.util.*;
-/** A class to compare vectors of objects. The result of comparison
- is a list of change
objects which form an
- edit script. The objects compared are traditionally lines
- of text from two files. Comparison options such as "ignore
- whitespace" are implemented by modifying the equals
- and hashcode
methods for the objects compared.
-
- The basic algorithm is described in: - "An O(ND) Difference Algorithm and its Variations", Eugene Myers, - Algorithmica Vol. 1 No. 2, 1986, p 251. -
- This class outputs different results from GNU diff 1.15 on some - inputs. Our results are actually better (smaller change list, smaller - total size of changes), but it would be nice to know why. Perhaps - there is a memory overwrite bug in GNU diff 1.15. - - @author Stuart D. Gathman, translated from GNU diff 1.15 - Copyright (C) 2000 Business Management Systems, Inc. -
- This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 1, or (at your option) - any later version. -
- This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. -
- You should have received a copy of the
- GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+/**
+ * Compares two lists, 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
+ * a
("from") list 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" lists; 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 class Diff +{ + /** + * The source list, AKA the "from" values. + */ + protected List a; -public class Diff { + /** + * The target list, AKA the "to" values. + */ + protected List b; - /** Prepare to find differences between two arrays. Each element of - the arrays is translated to an "equivalence number" based on - the result ofequals
. The original Object arrays
- are no longer needed for computing the differences. They will
- be needed again later to print the results of the comparison as
- an edit script, if desired.
- */
- public Diff(Object[] a,Object[] b) {
- Hashtable h = new Hashtable(a.length + b.length);
- filevec[0] = new file_data(a,h);
- filevec[1] = new file_data(b,h);
- }
+ /**
+ * The list of differences, as Difference
instances.
+ */
+ protected List diffs = new ArrayList();
- /** 1 more than the maximum equivalence value used for this or its
- sibling file. */
- private int equiv_max = 1;
+ /**
+ * The pending, uncommitted difference.
+ */
+ private Difference pending;
- /** When set to true, the comparison uses a heuristic to speed it up.
- With this heuristic, for files with a constant small density
- of changes, the algorithm is linear in the file size. */
- public boolean heuristic = false;
+ /**
+ * The comparator used, if any.
+ */
+ private Comparator comparator;
- /** When set to true, the algorithm returns a guarranteed minimal
- set of changes. This makes things slower, sometimes much slower. */
- public boolean no_discards = false;
+ /**
+ * The thresholds.
+ */
+ private TreeMap thresh;
- private int[] xvec, yvec; /* Vectors being compared. */
- private int[] fdiag; /* Vector, indexed by diagonal, containing
- the X coordinate of the point furthest
- along the given diagonal in the forward
- search of the edit matrix. */
- private int[] bdiag; /* Vector, indexed by diagonal, containing
- the X coordinate of the point furthest
- along the given diagonal in the backward
- search of the edit matrix. */
- private int fdiagoff, bdiagoff;
- private final file_data[] filevec = new file_data[2];
- private int cost;
-
- /** Find the midpoint of the shortest edit script for a specified
- portion of the two files.
-
- We scan from the beginnings of the files, and simultaneously from the ends,
- doing a breadth-first search through the space of edit-sequence.
- When the two searches meet, we have found the midpoint of the shortest
- edit sequence.
-
- The value returned is the number of the diagonal on which the midpoint lies.
- The diagonal number equals the number of inserted lines minus the number
- of deleted lines (counting only lines before the midpoint).
- The edit cost is stored into COST; this is the total number of
- lines inserted or deleted (counting only lines before the midpoint).
-
- This function assumes that the first lines of the specified portions
- of the two files do not match, and likewise that the last lines do not
- match. The caller must trim matching lines from the beginning and end
- of the portions it is going to specify.
-
- Note that if we return the "wrong" diagonal value, or if
- the value of bdiag at that diagonal is "wrong",
- the worst this can do is cause suboptimal diff output.
- It cannot cause incorrect diff output. */
-
- private int diag (int xoff, int xlim, int yoff, int ylim) {
- final int[] fd = fdiag; // Give the compiler a chance.
- final int[] bd = bdiag; // Additional help for the compiler.
- final int[] xv = xvec; // Still more help for the compiler.
- final int[] yv = yvec; // And more and more . . .
- final int dmin = xoff - ylim; // Minimum valid diagonal.
- final int dmax = xlim - yoff; // Maximum valid diagonal.
- final int fmid = xoff - yoff; // Center diagonal of top-down search.
- final int bmid = xlim - ylim; // Center diagonal of bottom-up search.
- int fmin = fmid, fmax = fmid; // Limits of top-down search.
- int bmin = bmid, bmax = bmid; // Limits of bottom-up search.
- /* True if southeast corner is on an odd
- diagonal with respect to the northwest. */
- final boolean odd = (fmid - bmid & 1) != 0;
-
- fd[fdiagoff + fmid] = xoff;
- bd[bdiagoff + bmid] = xlim;
-
- for (int c = 1;; ++c)
- {
- int d; /* Active diagonal. */
- boolean big_snake = false;
-
- /* Extend the top-down search by an edit step in each diagonal. */
- if (fmin > dmin)
- fd[fdiagoff + --fmin - 1] = -1;
- else
- ++fmin;
- if (fmax < dmax)
- fd[fdiagoff + ++fmax + 1] = -1;
- else
- --fmax;
- for (d = fmax; d >= fmin; d -= 2)
- {
- int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];
-
- if (tlo >= thi)
- x = tlo + 1;
- else
- x = thi;
- oldx = x;
- y = x - d;
- while (x < xlim && y < ylim && xv[x] == yv[y]) {
- ++x; ++y;
- }
- if (x - oldx > 20)
- big_snake = true;
- fd[fdiagoff + d] = x;
- if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
- {
- cost = 2 * c - 1;
- return d;
- }
- }
-
- /* Similar extend the bottom-up search. */
- if (bmin > dmin)
- bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
- else
- ++bmin;
- if (bmax < dmax)
- bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
- else
- --bmax;
- for (d = bmax; d >= bmin; d -= 2)
- {
- int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];
-
- if (tlo < thi)
- x = tlo;
- else
- x = thi - 1;
- oldx = x;
- y = x - d;
- while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
- --x; --y;
- }
- if (oldx - x > 20)
- big_snake = true;
- bd[bdiagoff + d] = x;
- if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
- {
- cost = 2 * c;
- return d;
- }
- }
-
- /* Heuristic: check occasionally for a diagonal that has made
- lots of progress compared with the edit distance.
- If we have any such, find the one that has made the most
- progress and return it as if it had succeeded.
-
- With this heuristic, for files with a constant small density
- of changes, the algorithm is linear in the file size. */
-
- if (c > 200 && big_snake && heuristic)
- {
- int best = 0;
- int bestpos = -1;
-
- for (d = fmax; d >= fmin; d -= 2)
- {
- int dd = d - fmid;
- if ((fd[fdiagoff + d] - xoff)*2 - dd > 12 * (c + (dd > 0 ? dd : -dd)))
- {
- if (fd[fdiagoff + d] * 2 - dd > best
- && fd[fdiagoff + d] - xoff > 20
- && fd[fdiagoff + d] - d - yoff > 20)
- {
- int k;
- int x = fd[fdiagoff + d];
-
- /* We have a good enough best diagonal;
- now insist that it end with a significant snake. */
- for (k = 1; k <= 20; k++)
- if (xvec[x - k] != yvec[x - d - k])
- break;
-
- if (k == 21)
- {
- best = fd[fdiagoff + d] * 2 - dd;
- bestpos = d;
- }
- }
- }
- }
- if (best > 0)
- {
- cost = 2 * c - 1;
- return bestpos;
- }
-
- best = 0;
- for (d = bmax; d >= bmin; d -= 2)
- {
- int dd = d - bmid;
- if ((xlim - bd[bdiagoff + d])*2 + dd > 12 * (c + (dd > 0 ? dd : -dd)))
- {
- if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
- && xlim - bd[bdiagoff + d] > 20
- && ylim - (bd[bdiagoff + d] - d) > 20)
- {
- /* We have a good enough best diagonal;
- now insist that it end with a significant snake. */
- int k;
- int x = bd[bdiagoff + d];
-
- for (k = 0; k < 20; k++)
- if (xvec[x + k] != yvec[x - d + k])
- break;
- if (k == 20)
- {
- best = (xlim - bd[bdiagoff + d]) * 2 + dd;
- bestpos = d;
- }
- }
- }
- }
- if (best > 0)
- {
- cost = 2 * c - 1;
- return bestpos;
- }
- }
- }
- }
-
- /** Compare in detail contiguous subsequences of the two files
- which are known, as a whole, to match each other.
-
- The results are recorded in the vectors filevec[N].changed_flag, by
- storing a 1 in the element for each line that is an insertion or deletion.
-
- The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
-
- Note that XLIM, YLIM are exclusive bounds.
- All line numbers are origin-0 and discarded lines are not counted. */
-
- private void compareseq (int xoff, int xlim, int yoff, int ylim) {
- /* Slide down the bottom initial diagonal. */
- while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
- ++xoff; ++yoff;
+ /**
+ * Constructs the Diff object for the two arrays, using the given comparator.
+ */
+ public Diff(Object[] a, Object[] b, Comparator comp)
+ {
+ this(Arrays.asList(a), Arrays.asList(b), comp);
}
- /* Slide up the top initial diagonal. */
- while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
- --xlim; --ylim;
+
+ /**
+ * Constructs the Diff object for the two arrays, using the default
+ * comparison mechanism between the objects, such as equals
and
+ * compareTo
.
+ */
+ public Diff(Object[] a, Object[] b)
+ {
+ this(a, b, null);
+ }
+
+ /**
+ * Constructs the Diff object for the two lists, using the given comparator.
+ */
+ public Diff(List a, List b, Comparator comp)
+ {
+ this.a = a;
+ this.b = b;
+ this.comparator = comp;
+ this.thresh = null;
+ }
+
+ /**
+ * Constructs the Diff object for the two lists, using the default
+ * comparison mechanism between the objects, such as equals
and
+ * compareTo
.
+ */
+ public Diff(List a, List b)
+ {
+ this(a, b, null);
+ }
+
+ /**
+ * Runs diff and returns the results.
+ */
+ public Change diff()
+ {
+ traverseSequences();
+
+ // add the last difference, if pending:
+ if (pending != null) {
+ diffs.add(pending);
+ }
+
+ return Change.fromList(diffs);
+ }
+
+ /**
+ * Traverses the sequences, seeking the longest common subsequences,
+ * invoking the methods finishedA
, finishedB
,
+ * onANotB
, and onBNotA
.
+ */
+ protected void traverseSequences()
+ {
+ Integer[] matches = getLongestCommonSubsequences();
+
+ int lastA = a.size() - 1;
+ int lastB = b.size() - 1;
+ int bi = 0;
+ int ai;
+
+ int lastMatch = matches.length - 1;
+
+ for (ai = 0; ai <= lastMatch; ++ai) {
+ Integer bLine = matches[ai];
+
+ if (bLine == null) {
+ onANotB(ai, bi);
+ }
+ else {
+ while (bi < bLine.intValue()) {
+ onBNotA(ai, bi++);
+ }
+
+ onMatch(ai, bi++);
+ }
+ }
+
+ boolean calledFinishA = false;
+ boolean calledFinishB = false;
+
+ while (ai <= lastA || bi <= lastB) {
+
+ // last A?
+ if (ai == lastA + 1 && bi <= lastB) {
+ if (!calledFinishA && callFinishedA()) {
+ finishedA(lastA);
+ calledFinishA = true;
+ }
+ else {
+ while (bi <= lastB) {
+ onBNotA(ai, bi++);
+ }
+ }
+ }
+
+ // last B?
+ if (bi == lastB + 1 && ai <= lastA) {
+ if (!calledFinishB && callFinishedB()) {
+ finishedB(lastB);
+ calledFinishB = true;
+ }
+ else {
+ while (ai <= lastA) {
+ onANotB(ai++, bi);
+ }
+ }
+ }
+
+ if (ai <= lastA) {
+ onANotB(ai++, bi);
+ }
+
+ if (bi <= lastB) {
+ onBNotA(ai, bi++);
+ }
+ }
+ }
+
+ /**
+ * Override and return true in order to have finishedA
invoked
+ * at the last element in the a
array.
+ */
+ protected boolean callFinishedA()
+ {
+ return false;
+ }
+
+ /**
+ * Override and return true in order to have finishedB
invoked
+ * at the last element in the b
array.
+ */
+ 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 ai, int bi)
+ {
+ if (pending == null) {
+ pending = new Difference(ai, ai, bi, -1);
+ }
+ else {
+ pending.setDeleted(ai);
+ }
+ }
+
+ /**
+ * Invoked for elements in b
and not in a
.
+ */
+ protected void onBNotA(int ai, int bi)
+ {
+ if (pending == null) {
+ pending = new Difference(ai, -1, bi, bi);
+ }
+ else {
+ pending.setAdded(bi);
+ }
+ }
+
+ /**
+ * Invoked for elements matching in a
and b
.
+ */
+ protected void onMatch(int ai, int bi)
+ {
+ if (pending == null) {
+ // no current pending
+ }
+ else {
+ diffs.add(pending);
+ pending = null;
+ }
+ }
+
+ /**
+ * Compares the two objects, using the comparator provided with the
+ * constructor, if any.
+ */
+ protected boolean equals(Object x, Object y)
+ {
+ return comparator == null ? x.equals(y) : comparator.compare(x, y) == 0;
}
- /* Handle simple cases. */
- if (xoff == xlim)
- while (yoff < ylim)
- filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true;
- else if (yoff == ylim)
- while (xoff < xlim)
- filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true;
- else
- {
- /* Find a point of correspondence in the middle of the files. */
-
- int d = diag (xoff, xlim, yoff, ylim);
- int c = cost;
- //int f = fdiag[fdiagoff + d];
- int b = bdiag[bdiagoff + d];
-
- if (c == 1)
- {
- /* This should be impossible, because it implies that
- one of the two subsequences is empty,
- and that case was handled above without calling `diag'.
- Let's verify that this is true. */
- throw new IllegalArgumentException("Empty subsequence");
- }
- else
- {
- /* Use that point to split this problem into two subproblems. */
- compareseq (xoff, b, yoff, b - d);
- /* This used to use f instead of b,
- but that is incorrect!
- It is not necessarily the case that diagonal d
- has a snake from b to f. */
- compareseq (b, xlim, b - d, ylim);
- }
- }
- }
-
- /** Discard lines from one file that have no matches in the other file.
- */
-
- private void discard_confusing_lines() {
- filevec[0].discard_confusing_lines(filevec[1]);
- filevec[1].discard_confusing_lines(filevec[0]);
- }
-
- private boolean inhibit = false;
-
- /** Adjust inserts/deletes of blank lines to join changes
- as much as possible.
- */
-
- private void shift_boundaries() {
- if (inhibit)
- return;
- filevec[0].shift_boundaries(filevec[1]);
- filevec[1].shift_boundaries(filevec[0]);
- }
-
- /** Scan the tables of which lines are inserted and deleted,
- producing an edit script in reverse order. */
-
- private change build_reverse_script() {
- change script = null;
- final boolean[] changed0 = filevec[0].changed_flag;
- final boolean[] changed1 = filevec[1].changed_flag;
- final int len0 = filevec[0].buffered_lines;
- final int len1 = filevec[1].buffered_lines;
-
- /* Note that changedN[len0] does exist, and contains 0. */
-
- int i0 = 0, i1 = 0;
-
- while (i0 < len0 || i1 < len1)
- {
- if (changed0[1+i0] || changed1[1+i1])
- {
- int line0 = i0, line1 = i1;
-
- /* Find # lines changed here in each file. */
- while (changed0[1+i0]) ++i0;
- while (changed1[1+i1]) ++i1;
-
- /* Record this change. */
- script = new change(line0, line1, i0 - line0, i1 - line1, script);
- }
-
- /* We have reached lines in the two files that match each other. */
- i0++; i1++;
- }
-
- return script;
- }
-
- /** Scan the tables of which lines are inserted and deleted,
- producing an edit script in forward order. */
-
- private change build_script() {
- change script = null;
- final boolean[] changed0 = filevec[0].changed_flag;
- final boolean[] changed1 = filevec[1].changed_flag;
- final int len0 = filevec[0].buffered_lines;
- final int len1 = filevec[1].buffered_lines;
- int i0 = len0, i1 = len1;
-
- /* Note that changedN[-1] does exist, and contains 0. */
-
- while (i0 >= 0 || i1 >= 0)
- {
- if (changed0[i0] || changed1[i1])
- {
- int line0 = i0, line1 = i1;
-
- /* Find # lines changed here in each file. */
- while (changed0[i0]) --i0;
- while (changed1[i1]) --i1;
-
- /* Record this change. */
- script = new change(i0, i1, line0 - i0, line1 - i1, script);
- }
-
- /* We have reached lines in the two files that match each other. */
- i0--; i1--;
- }
-
- return script;
- }
-
- /**
- * Convenience method to get a diff on the two files.
- */
- public change diff() {
- return diff_2 (false);
- }
-
- /* Report the differences of two files. DEPTH is the current directory
- depth. */
- public change diff_2(final boolean reverse) {
-
- /* Some lines are obviously insertions or deletions
- because they don't match anything. Detect them now,
- and avoid even thinking about them in the main comparison algorithm. */
-
- discard_confusing_lines ();
-
- /* Now do the main comparison algorithm, considering just the
- undiscarded lines. */
-
- xvec = filevec[0].undiscarded;
- yvec = filevec[1].undiscarded;
-
- int diags =
- filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
- fdiag = new int[diags];
- fdiagoff = filevec[1].nondiscarded_lines + 1;
- bdiag = new int[diags];
- bdiagoff = filevec[1].nondiscarded_lines + 1;
-
- compareseq (0, filevec[0].nondiscarded_lines,
- 0, filevec[1].nondiscarded_lines);
- fdiag = null;
- bdiag = null;
-
- /* Modify the results slightly to make them prettier
- in cases where that can validly be done. */
-
- shift_boundaries ();
-
- /* Get the results of comparison in the form of a chain
- of `struct change's -- an edit script. */
-
- if (reverse)
- return build_reverse_script();
- else
- return build_script();
- }
-
- /** The result of comparison is an "edit script": a chain of change objects.
- Each change represents one place where some lines are deleted
- and some are inserted.
-
- LINE0 and LINE1 are the first affected lines in the two files (origin 0).
- DELETED is the number of lines deleted here from file 0.
- INSERTED is the number of lines inserted here in file 1.
-
- If DELETED is 0 then LINE0 is the number of the line before
- which the insertion was done; vice versa for INSERTED and LINE1. */
-
- public static class change {
- /** Previous or next edit command. */
- public change link;
- /** # lines of file 1 changed here. */
- public final int inserted;
- /** # lines of file 0 changed here. */
- public final int deleted;
- /** Line number of 1st deleted line. */
- public final int line0;
- /** Line number of 1st inserted line. */
- public final int line1;
-
- /** Cons an additional entry onto the front of an edit script OLD.
- LINE0 and LINE1 are the first affected lines in the two files (origin 0).
- DELETED is the number of lines deleted here from file 0.
- INSERTED is the number of lines inserted here in file 1.
-
- If DELETED is 0 then LINE0 is the number of the line before
- which the insertion was done; vice versa for INSERTED and LINE1. */
- change(int line0, int line1, int deleted, int inserted, change old) {
- this.line0 = line0;
- this.line1 = line1;
- this.inserted = inserted;
- this.deleted = deleted;
- this.link = old;
- //System.err.println(line0+","+line1+","+inserted+","+deleted);
- }
- }
-
- /** Data on one input file being compared.
- */
-
- class file_data {
-
- /** Allocate changed array for the results of comparison. */
- void clear() {
- /* Allocate a flag for each line of each file, saying whether that line
- is an insertion or deletion.
- Allocate an extra element, always zero, at each end of each vector.
- */
- changed_flag = new boolean[buffered_lines + 2];
- }
-
- /** Return equiv_count[I] as the number of lines in this file
- that fall in equivalence class I.
- @return the array of equivalence class counts.
+ /**
+ * Returns an array of the longest common subsequences.
*/
- int[] equivCount() {
- int[] equiv_count = new int[equiv_max];
- for (int i = 0; i < buffered_lines; ++i)
- ++equiv_count[equivs[i]];
- return equiv_count;
+ public Integer[] getLongestCommonSubsequences()
+ {
+ int aStart = 0;
+ int aEnd = a.size() - 1;
+
+ int bStart = 0;
+ int bEnd = b.size() - 1;
+
+ TreeMap matches = new TreeMap();
+
+ while (aStart <= aEnd && bStart <= bEnd && equals(a.get(aStart), b.get(bStart))) {
+ matches.put(Integer.valueOf(aStart++), Integer.valueOf(bStart++));
+ }
+
+ while (aStart <= aEnd && bStart <= bEnd && equals(a.get(aEnd), b.get(bEnd))) {
+ matches.put(Integer.valueOf(aEnd--), Integer.valueOf(bEnd--));
+ }
+
+ Map bMatches = null;
+ if (comparator == null) {
+ if (a.size() > 0 && a.get(0) instanceof Comparable) {
+ // this uses the Comparable interface
+ bMatches = new TreeMap();
+ }
+ else {
+ // this just uses hashCode()
+ bMatches = new HashMap();
+ }
+ }
+ else {
+ // we don't really want them sorted, but this is the only Map
+ // implementation (as of JDK 1.4) that takes a comparator.
+ bMatches = new TreeMap(comparator);
+ }
+
+ for (int bi = bStart; bi <= bEnd; ++bi) {
+ Object element = b.get(bi);
+ Object key = element;
+ List positions = (List) bMatches.get(key);
+
+ if (positions == null) {
+ positions = new ArrayList();
+ bMatches.put(key, positions);
+ }
+
+ positions.add(Integer.valueOf(bi));
+ }
+
+ thresh = new TreeMap();
+ Map links = new HashMap();
+
+ for (int i = aStart; i <= aEnd; ++i) {
+ Object aElement = a.get(i);
+ List positions = (List) bMatches.get(aElement);
+
+ if (positions != null) {
+ Integer k = Integer.valueOf(0);
+ ListIterator pit = positions.listIterator(positions.size());
+ while (pit.hasPrevious()) {
+ Integer j = (Integer) pit.previous();
+
+ k = insert(j, k);
+
+ if (k == null) {
+ // nothing
+ }
+ else {
+ Object value = k.intValue() > 0 ? links.get(Integer.valueOf(k.intValue() - 1)) : null;
+ links.put(k, new Object[] { value, Integer.valueOf(i), j });
+ }
+ }
+ }
+ }
+
+ if (thresh.size() > 0) {
+ Integer ti = (Integer) thresh.lastKey();
+ Object[] link = (Object[])links.get(ti);
+ while (link != null) {
+ Integer x = (Integer)link[1];
+ Integer y = (Integer)link[2];
+ matches.put(x, y);
+ link = (Object[])link[0];
+ }
+ }
+
+ int size = matches.size() == 0 ? 0 : 1 + ((Integer) matches.lastKey()).intValue();
+ Integer[] ary = new Integer[size];
+ for (Iterator it = matches.keySet().iterator(); it.hasNext();) {
+ Integer idx = (Integer) it.next();
+ Integer val = (Integer) matches.get(idx);
+ ary[idx.intValue()] = val;
+ }
+ return ary;
}
- /** Discard lines that have no matches in another file.
-
- A line which is discarded will not be considered by the actual
- comparison algorithm; it will be as if that line were not in the file.
- The file's `realindexes' table maps virtual line numbers
- (which don't count the discarded lines) into real line numbers;
- this is how the actual comparison algorithm produces results
- that are comprehensible when the discarded lines are counted.
-
- When we discard a line, we also mark it as a deletion or insertion
- so that it will be printed in the output.
- @param f the other file
+ /**
+ * Returns whether the integer is not zero (including if it is not null).
*/
- void discard_confusing_lines(file_data f) {
- clear();
- /* Set up table of which lines are going to be discarded. */
- final byte[] discarded = discardable(f.equivCount());
-
- /* Don't really discard the provisional lines except when they occur
- in a run of discardables, with nonprovisionals at the beginning
- and end. */
- filterDiscards(discarded);
-
- /* Actually discard the lines. */
- discard(discarded);
+ protected static boolean isNonzero(Integer i)
+ {
+ return i != null && i.intValue() != 0;
}
- /** Mark to be discarded each line that matches no line of another file.
- If a line matches many lines, mark it as provisionally discardable.
- @see equivCount()
- @param counts The count of each equivalence number for the other file.
- @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
- for each line
+ /**
+ * Returns whether the value in the map for the given index is greater than
+ * the given value.
*/
-
- private byte[] discardable(final int[] counts) {
- final int end = buffered_lines;
- final byte[] discards = new byte[end];
- final int[] equivs = this.equivs;
- int many = 5;
- int tem = end / 64;
-
- /* Multiply MANY by approximate square root of number of lines.
- That is the threshold for provisionally discardable lines. */
- while ((tem = tem >> 2) > 0)
- many *= 2;
-
- for (int i = 0; i < end; i++)
- {
- int nmatch;
- if (equivs[i] == 0)
- continue;
- nmatch = counts[equivs[i]];
- if (nmatch == 0)
- discards[i] = 1;
- else if (nmatch > many)
- discards[i] = 2;
- }
- return discards;
+ protected boolean isGreaterThan(Integer index, Integer val)
+ {
+ Integer lhs = (Integer) thresh.get(index);
+ return lhs != null && val != null && lhs.compareTo(val) > 0;
}
- /** Don't really discard the provisional lines except when they occur
- in a run of discardables, with nonprovisionals at the beginning
- and end. */
-
- private void filterDiscards(final byte[] discards) {
- final int end = buffered_lines;
-
- for (int i = 0; i < end; i++)
- {
- /* Cancel provisional discards not in middle of run of discards. */
- if (discards[i] == 2)
- discards[i] = 0;
- else if (discards[i] != 0)
- {
- /* We have found a nonprovisional discard. */
- int j;
- int length;
- int provisional = 0;
-
- /* Find end of this run of discardable lines.
- Count how many are provisionally discardable. */
- for (j = i; j < end; j++)
- {
- if (discards[j] == 0)
- break;
- if (discards[j] == 2)
- ++provisional;
- }
-
- /* Cancel provisional discards at end, and shrink the run. */
- while (j > i && discards[j - 1] == 2) {
- discards[--j] = 0; --provisional;
- }
-
- /* Now we have the length of a run of discardable lines
- whose first and last are not provisional. */
- length = j - i;
-
- /* If 1/4 of the lines in the run are provisional,
- cancel discarding of all provisional lines in the run. */
- if (provisional * 4 > length)
- {
- while (j > i)
- if (discards[--j] == 2)
- discards[j] = 0;
- }
- else
- {
- int consec;
- int minimum = 1;
- int tem = length / 4;
-
- /* MINIMUM is approximate square root of LENGTH/4.
- A subrun of two or more provisionals can stand
- when LENGTH is at least 16.
- A subrun of 4 or more can stand when LENGTH >= 64. */
- while ((tem = tem >> 2) > 0)
- minimum *= 2;
- minimum++;
-
- /* Cancel any subrun of MINIMUM or more provisionals
- within the larger run. */
- for (j = 0, consec = 0; j < length; j++)
- if (discards[i + j] != 2)
- consec = 0;
- else if (minimum == ++consec)
- /* Back up to start of subrun, to cancel it all. */
- j -= consec;
- else if (minimum < consec)
- discards[i + j] = 0;
-
- /* Scan from beginning of run
- until we find 3 or more nonprovisionals in a row
- or until the first nonprovisional at least 8 lines in.
- Until that point, cancel any provisionals. */
- for (j = 0, consec = 0; j < length; j++)
- {
- if (j >= 8 && discards[i + j] == 1)
- break;
- if (discards[i + j] == 2) {
- consec = 0; discards[i + j] = 0;
- }
- else if (discards[i + j] == 0)
- consec = 0;
- else
- consec++;
- if (consec == 3)
- break;
- }
-
- /* I advances to the last line of the run. */
- i += length - 1;
-
- /* Same thing, from end. */
- for (j = 0, consec = 0; j < length; j++)
- {
- if (j >= 8 && discards[i - j] == 1)
- break;
- if (discards[i - j] == 2) {
- consec = 0; discards[i - j] = 0;
- }
- else if (discards[i - j] == 0)
- consec = 0;
- else
- consec++;
- if (consec == 3)
- break;
- }
- }
- }
- }
- }
-
- /** Actually discard the lines.
- @param discards flags lines to be discarded
+ /**
+ * Returns whether the value in the map for the given index is less than
+ * the given value.
*/
- private void discard(final byte[] discards) {
- final int end = buffered_lines;
- int j = 0;
- for (int i = 0; i < end; ++i)
- if (no_discards || discards[i] == 0)
- {
- undiscarded[j] = equivs[i];
- realindexes[j++] = i;
- }
- else
- changed_flag[1+i] = true;
- nondiscarded_lines = j;
+ protected boolean isLessThan(Integer index, Integer val)
+ {
+ Integer lhs = (Integer) thresh.get(index);
+ return lhs != null && (val == null || lhs.compareTo(val) < 0);
}
- file_data(Object[] data,Hashtable h) {
- buffered_lines = data.length;
-
- equivs = new int[buffered_lines];
- undiscarded = new int[buffered_lines];
- realindexes = new int[buffered_lines];
-
- for (int i = 0; i < data.length; ++i) {
- Integer ir = (Integer)h.get(data[i]);
- if (ir == null)
- h.put(data[i],new Integer(equivs[i] = equiv_max++));
- else
- equivs[i] = ir.intValue();
- }
+ /**
+ * Returns the value for the greatest key in the map.
+ */
+ protected Integer getLastValue()
+ {
+ return (Integer) thresh.get(thresh.lastKey());
}
- /** Adjust inserts/deletes of blank lines to join changes
- as much as possible.
-
- We do something when a run of changed lines include a blank
- line at one end and have an excluded blank line at the other.
- We are free to choose which blank line is included.
- `compareseq' always chooses the one at the beginning,
- but usually it is cleaner to consider the following blank line
- to be the "change". The only exception is if the preceding blank line
- would join this change to other changes.
- @param f the file being compared against
- */
-
- void shift_boundaries(file_data f) {
- final boolean[] changed = changed_flag;
- final boolean[] other_changed = f.changed_flag;
- int i = 0;
- int j = 0;
- int i_end = buffered_lines;
- int preceding = -1;
- int other_preceding = -1;
-
- for (;;)
- {
- int start, end, other_start;
-
- /* Scan forwards to find beginning of another run of changes.
- Also keep track of the corresponding point in the other file. */
-
- while (i < i_end && !changed[1+i])
- {
- while (other_changed[1+j++])
- /* Non-corresponding lines in the other file
- will count as the preceding batch of changes. */
- other_preceding = j;
- i++;
- }
-
- if (i == i_end)
- break;
-
- start = i;
- other_start = j;
-
- for (;;)
- {
- /* Now find the end of this run of changes. */
-
- while (i < i_end && changed[1+i]) i++;
- end = i;
-
- /* If the first changed line matches the following unchanged one,
- and this run does not follow right after a previous run,
- and there are no lines deleted from the other file here,
- then classify the first changed line as unchanged
- and the following line as changed in its place. */
-
- /* You might ask, how could this run follow right after another?
- Only because the previous run was shifted here. */
-
- if (end != i_end
- && equivs[start] == equivs[end]
- && !other_changed[1+j]
- && end != i_end
- && !((preceding >= 0 && start == preceding)
- || (other_preceding >= 0
- && other_start == other_preceding)))
- {
- changed[1+end++] = true;
- changed[1+start++] = false;
- ++i;
- /* Since one line-that-matches is now before this run
- instead of after, we must advance in the other file
- to keep in synch. */
- ++j;
- }
- else
- break;
- }
-
- preceding = i;
- other_preceding = j;
- }
+ /**
+ * Adds the given value to the "end" of the threshold map, that is, with the
+ * greatest index/key.
+ */
+ protected void append(Integer value)
+ {
+ Integer addIdx = null;
+ if (thresh.size() == 0) {
+ addIdx = Integer.valueOf(0);
+ }
+ else {
+ Integer lastKey = (Integer) thresh.lastKey();
+ addIdx = Integer.valueOf(lastKey.intValue() + 1);
+ }
+ thresh.put(addIdx, value);
}
- /** Number of elements (lines) in this file. */
- final int buffered_lines;
+ /**
+ * Inserts the given values into the threshold map.
+ */
+ protected Integer insert(Integer j, Integer k)
+ {
+ if (isNonzero(k) && isGreaterThan(k, j) && isLessThan(Integer.valueOf(k.intValue() - 1), j)) {
+ thresh.put(k, j);
+ }
+ else {
+ int high = -1;
+
+ if (isNonzero(k)) {
+ high = k.intValue();
+ }
+ else if (thresh.size() > 0) {
+ high = ((Integer) thresh.lastKey()).intValue();
+ }
- /** Vector, indexed by line number, containing an equivalence code for
- each line. It is this vector that is actually compared with that
- of another file to generate differences. */
- private final int[] equivs;
+ // off the end?
+ if (high == -1 || j.compareTo(getLastValue()) > 0) {
+ append(j);
+ k = Integer.valueOf(high + 1);
+ }
+ else {
+ // binary search for insertion point:
+ int low = 0;
+
+ while (low <= high) {
+ int index = (high + low) / 2;
+ Integer val = (Integer) thresh.get(Integer.valueOf(index));
+ int cmp = j.compareTo(val);
- /** Vector, like the previous one except that
- the elements for discarded lines have been squeezed out. */
- final int[] undiscarded;
+ if (cmp == 0) {
+ return null;
+ }
+ else if (cmp > 0) {
+ low = index + 1;
+ }
+ else {
+ high = index - 1;
+ }
+ }
+
+ thresh.put(Integer.valueOf(low), j);
+ k = Integer.valueOf(low);
+ }
+ }
- /** Vector mapping virtual line numbers (not counting discarded lines)
- to real ones (counting those lines). Both are origin-0. */
- final int[] realindexes;
+ return k;
+ }
- /** Total number of nondiscarded lines. */
- int nondiscarded_lines;
+ /**
+ * This is the orignal class used for representation of a single change.
+ * We replace this with our own {@link Change} class for reasons of compatibility
+ * and easier scripting.
+ */
+ class Difference
+ {
+ public static final int NONE = -1;
- /** Array, indexed by real origin-1 line number,
- containing true for a line that is an insertion or a deletion.
- The results of comparison are stored here. */
- boolean[] changed_flag;
+ /**
+ * The point at which the deletion starts.
+ */
+ private int delStart = NONE;
- }
+ /**
+ * The point at which the deletion ends.
+ */
+ private int delEnd = NONE;
+
+ /**
+ * The point at which the addition starts.
+ */
+ private int addStart = NONE;
+
+ /**
+ * The point at which the addition ends.
+ */
+ private int addEnd = NONE;
+
+ /**
+ * Creates the difference for the given start and end points for the
+ * deletion and addition.
+ */
+ public Difference(int delStart, int delEnd, int addStart, int addEnd)
+ {
+ this.delStart = delStart;
+ this.delEnd = delEnd;
+ this.addStart = addStart;
+ this.addEnd = addEnd;
+ }
+
+ /**
+ * The point at which the deletion starts, if any. A value equal to
+ * NONE
means this is an addition.
+ */
+ public int getDeletedStart()
+ {
+ return delStart;
+ }
+
+ /**
+ * The point at which the deletion ends, if any. A value equal to
+ * NONE
means this is an addition.
+ */
+ public int getDeletedEnd()
+ {
+ return delEnd;
+ }
+
+ /**
+ * The point at which the addition starts, if any. A value equal to
+ * NONE
means this must be an addition.
+ */
+ public int getAddedStart()
+ {
+ return addStart;
+ }
+
+ /**
+ * The point at which the addition ends, if any. A value equal to
+ * NONE
means this must be an addition.
+ */
+ public int getAddedEnd()
+ {
+ return addEnd;
+ }
+
+ /**
+ * Sets the point as deleted. The start and end points will be modified to
+ * include the given line.
+ */
+ public void setDeleted(int line)
+ {
+ delStart = Math.min(line, delStart);
+ delEnd = Math.max(line, delEnd);
+ }
+
+ /**
+ * Sets the point as added. The start and end points will be modified to
+ * include the given line.
+ */
+ public void setAdded(int line)
+ {
+ addStart = Math.min(line, addStart);
+ addEnd = Math.max(line, addEnd);
+ }
+
+ /**
+ * Compares this object to the other for equality. Both objects must be of
+ * type Difference, with the same starting and ending points.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof Difference) {
+ Difference other = (Difference)obj;
+
+ return (delStart == other.delStart &&
+ delEnd == other.delEnd &&
+ addStart == other.addStart &&
+ addEnd == other.addEnd);
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a string representation of this difference.
+ */
+ public String toString()
+ {
+ StringBuffer buf = new StringBuffer();
+ buf.append("del: [" + delStart + ", " + delEnd + "]");
+ buf.append(" ");
+ buf.append("add: [" + addStart + ", " + addEnd + "]");
+ return buf.toString();
+ }
+
+ }
+
+
+ /**
+ * A legacy adapter that is compatible to the interface of the old GPL licenced Diff.
+ */
+ public static class Change {
+
+ public final Change link;
+ public final int line0;
+ public final int line1;
+ public final int inserted;
+ public final int deleted;
+
+ public static Change fromList(List diffs) {
+ Iterator iter = diffs.iterator();
+ return iter.hasNext() ? new Change(iter, 0, 0) : null;
+ }
+
+ private Change(Iterator iter, int prev0, int prev1) {
+ Difference diff = (Difference) iter.next();
+ if (diff.getDeletedEnd() == Difference.NONE) {
+ line0 = prev0 + diff.getAddedStart() - prev1;
+ deleted = 0;
+ } else {
+ line0 = diff.getDeletedStart();
+ deleted = diff.getDeletedEnd() - line0 + 1;
+ }
+ if (diff.getAddedEnd() == Difference.NONE) {
+ line1 = prev1 + diff.getDeletedStart() - prev0;
+ inserted = 0;
+ } else {
+ line1 = diff.getAddedStart();
+ inserted = diff.getAddedEnd() - line1 + 1;
+ }
+ this.link = iter.hasNext() ? new Change(iter, line0 + deleted, line1 + inserted) : null;
+ }
+ }
}
+