From 0216f2e1ef467cd6d4c0742255f017e04ef0ad49 Mon Sep 17 00:00:00 2001 From: "dm.naumenko@gmail.com" Date: Fri, 10 Jun 2011 13:05:56 +0000 Subject: [PATCH] DiffRowGenerator updates: - unit tests - ignore white spaces option implemented - parametrize equalizer for diff algorithm --- build.xml | 6 +- pom.xml | 5 ++ src/difflib/DiffRowGenerator.java | 116 ++++++++++++++++++------------ src/difflib/DiffUtils.java | 16 +++++ src/difflib/myers/Equalizer.java | 5 ++ src/difflib/myers/MyersDiff.java | 75 +++++++++++-------- 6 files changed, 144 insertions(+), 79 deletions(-) create mode 100644 src/difflib/myers/Equalizer.java diff --git a/build.xml b/build.xml index 5921cf1..99fa1ad 100644 --- a/build.xml +++ b/build.xml @@ -14,7 +14,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -48,7 +48,7 @@ - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2a3800f..764886a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,6 +8,11 @@ The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java http://code.google.com/p/java-diff-utils/ 2009 + + org.sonatype.oss + oss-parent + 7 + scm:svn:http://java-diff-utils.googlecode.com/svn/trunk/ scm:svn:https://java-diff-utils.googlecode.com/svn/trunk/ diff --git a/src/difflib/DiffRowGenerator.java b/src/difflib/DiffRowGenerator.java index 90df961..8b0ef99 100644 --- a/src/difflib/DiffRowGenerator.java +++ b/src/difflib/DiffRowGenerator.java @@ -16,27 +16,28 @@ package difflib; import difflib.DiffRow.Tag; +import difflib.myers.Equalizer; import java.util.*; /** * This class for generating DiffRows for side-by-sidy view. - * You can customize the way of generating. For example, show inline diffs on not, ignoring + * You can customize the way of generating. For example, show inline diffs on not, ignoring * white spaces or/and blank lines and so on. All parameters for generating are optional. If you do * not specify them, the class will use the default values. - * + * * These values are: * showInlineDiffs = false; * ignoreWhiteSpaces = true; * ignoreBlankLines = true; * ... - * + * * For instantiating the DiffRowGenerator you should use the its builder. Like in example * * DiffRowGenerator generator = new DiffRowGenerator.Builder().showInlineDiffs(true). * ignoreWhiteSpaces(true).columnWidth(100).build(); * - * + * * @author Dmitry Naumenko */ public class DiffRowGenerator { @@ -48,7 +49,8 @@ public class DiffRowGenerator { private final String InlineOldCssClass; private final String InlineNewCssClass; private final int columnWidth; - + private final Equalizer equalizer; + /** * This class used for building the DiffRowGenerator. * @author dmitry @@ -56,24 +58,24 @@ public class DiffRowGenerator { */ public static class Builder { private boolean showInlineDiffs = false; - private boolean ignoreWhiteSpaces = true; - private boolean ignoreBlankLines = true; + private boolean ignoreWhiteSpaces = false; + private boolean ignoreBlankLines = false; private String InlineOldTag = "span"; private String InlineNewTag = "span"; private String InlineOldCssClass = "editOldInline"; private String InlineNewCssClass = "editNewInline"; private int columnWidth = 80; - + /** * Show inline diffs in generating diff rows or not. * @param val the value to set. Default: false. - * @return builder with configured showInlineDiff parameter + * @return builder with configured showInlineDiff parameter */ public Builder showInlineDiffs(boolean val) { showInlineDiffs = val; return this; } - + /** * Ignore white spaces in generating diff rows or not. * @param val the value to set. Default: true. @@ -83,7 +85,7 @@ public class DiffRowGenerator { ignoreWhiteSpaces = val; return this; } - + /** * Ignore blank lines in generating diff rows or not. * @param val the value to set. Default: true. @@ -93,7 +95,7 @@ public class DiffRowGenerator { ignoreBlankLines = val; return this; } - + /** * Set the tag used for displaying changes in the original text. * @param tag the tag to set. Without angle brackets. Default: span. @@ -103,7 +105,7 @@ public class DiffRowGenerator { InlineOldTag = tag; return this; } - + /** * Set the tag used for displaying changes in the revised text. * @param tag the tag to set. Without angle brackets. Default: span. @@ -113,7 +115,7 @@ public class DiffRowGenerator { InlineNewTag = tag; return this; } - + /** * Set the css class used for displaying changes in the original text. * @param cssClass the tag to set. Without any quotes, just word. Default: editOldInline. @@ -123,7 +125,7 @@ public class DiffRowGenerator { InlineOldCssClass = cssClass; return this; } - + /** * Set the css class used for displaying changes in the revised text. * @param cssClass the tag to set. Without any quotes, just word. Default: editNewInline. @@ -133,7 +135,7 @@ public class DiffRowGenerator { InlineNewCssClass = cssClass; return this; } - + /** * Set the column with of generated lines of original and revised texts. * @param width the width to set. Making it < 0 doesn't have any sense. Default 80. @@ -145,7 +147,7 @@ public class DiffRowGenerator { } return this; } - + /** * Build the DiffRowGenerator. If some parameters is not set, the default values are used. * @return the customized DiffRowGenerator @@ -154,7 +156,7 @@ public class DiffRowGenerator { return new DiffRowGenerator(this); } } - + private DiffRowGenerator(Builder builder) { showInlineDiffs = builder.showInlineDiffs; ignoreWhiteSpaces = builder.ignoreWhiteSpaces; // @@ -164,27 +166,47 @@ public class DiffRowGenerator { InlineOldCssClass = builder.InlineOldCssClass; InlineNewCssClass = builder.InlineNewCssClass; columnWidth = builder.columnWidth; // + equalizer = new Equalizer() { + public boolean equals(Object original, Object revised) { + if (ignoreWhiteSpaces) { + original = ((String)original).trim().replaceAll("\\s+", " "); + revised = ((String)revised).trim().replaceAll("\\s+", " "); + } + return original.equals(revised); + } + }; } - + /** * Get the DiffRows describing the difference between original and revised texts using the * given patch. Useful for displaying side-by-side diff. - * + * * @param original the original text - * @param revised the revised text + * @param revised the revised text * @return the DiffRows between original and revised texts */ public List generateDiffRows(List original, List revised) { - return generateDiffRows(original, revised, DiffUtils.diff(original, revised)); + return generateDiffRows(original, revised, DiffUtils.diff(original, revised, equalizer)); } - + + private List removeBlankLines(List lines) { + List result = new ArrayList(); + for (String line: lines) { + if (line.trim().length() == 0) { + result.add(""); + } + result.add(line); + } + return result; + } + /** * Generates the DiffRows describing the difference between original and revised texts using the * given patch. Useful for displaying side-by-side diff. - * + * * @param original the original text * @param revised the revised text - * @param patch the given patch + * @param patch the given patch * @return the DiffRows between original and revised texts */ @SuppressWarnings("unchecked") @@ -192,11 +214,11 @@ public class DiffRowGenerator { // normalize the lines (expand tabs, escape html entities) original = StringUtills.normalize(original); revised = StringUtills.normalize(revised); - + // wrap to the column width original = StringUtills.wrapText(original, this.columnWidth); revised = StringUtills.wrapText(revised, this.columnWidth); - + List diffRows = new ArrayList(); int endPos = 0; final List deltaList = patch.getDeltas(); @@ -204,19 +226,19 @@ public class DiffRowGenerator { Delta delta = deltaList.get(i); Chunk orig = delta.getOriginal(); Chunk rev = delta.getRevised(); - + // We should normalize and wrap lines in deltas too. orig.setLines(StringUtills.normalize((List) orig.getLines())); rev.setLines(StringUtills.normalize((List) rev.getLines())); - + orig.setLines(StringUtills.wrapText((List) orig.getLines(), this.columnWidth)); rev.setLines(StringUtills.wrapText((List) rev.getLines(), this.columnWidth)); - + // catch the equal prefix for each chunk for (String line : original.subList(endPos, orig.getPosition())) { diffRows.add(new DiffRow(Tag.EQUAL, line, line)); } - + // Inserted DiffRow if (delta.getClass().equals(InsertDelta.class)) { endPos = orig.last() + 1; @@ -225,7 +247,7 @@ public class DiffRowGenerator { } continue; } - + // Deleted DiffRow if (delta.getClass().equals(DeleteDelta.class)) { endPos = orig.last() + 1; @@ -234,7 +256,7 @@ public class DiffRowGenerator { } continue; } - + if (showInlineDiffs) { addInlineDiffs(delta); } @@ -257,14 +279,14 @@ public class DiffRowGenerator { } endPos = orig.last() + 1; } - + // Copy the final matching chunk if any. for (String line : original.subList(endPos, original.size())) { diffRows.add(new DiffRow(Tag.EQUAL, line, line)); } return diffRows; } - + /** * Add the inline diffs for given delta * @param delta the given delta @@ -313,12 +335,12 @@ public class DiffRowGenerator { delta.getRevised().setLines(Arrays.asList(revResult.toString().split("\n"))); } } - + /** * Wrap the elements in the sequence with the given tag * @param startPosition the position from which tag should start. The counting start from a zero. - * @param endPosition the position before which tag should should be closed. - * @param tag the tag name without angle brackets, just a word + * @param endPosition the position before which tag should should be closed. + * @param tag the tag name without angle brackets, just a word * @param cssClass the optional css class */ public static LinkedList wrapInTag(LinkedList sequence, int startPosition, @@ -334,23 +356,23 @@ public class DiffRowGenerator { } tagBuilder.append(">"); String startTag = tagBuilder.toString(); - + tagBuilder.delete(0, tagBuilder.length()); - + tagBuilder.append(""); String endTag = tagBuilder.toString(); - + result.add(startPosition, startTag); result.add(endPosition, endTag); return result; } - + /** * Wrap the given line with the given tag * @param line the given line - * @param tag the tag name without angle brackets, just a word + * @param tag the tag name without angle brackets, just a word * @param cssClass the optional css class * @return the wrapped string */ @@ -365,17 +387,17 @@ public class DiffRowGenerator { } tagBuilder.append(">"); String startTag = tagBuilder.toString(); - + tagBuilder.delete(0, tagBuilder.length()); - + tagBuilder.append(""); String endTag = tagBuilder.toString(); - + return startTag + line + endTag; } - + /** * The helper method for joining collections * @param diff --git a/src/difflib/DiffUtils.java b/src/difflib/DiffUtils.java index dc218ba..e3993d3 100644 --- a/src/difflib/DiffUtils.java +++ b/src/difflib/DiffUtils.java @@ -15,6 +15,7 @@ */ package difflib; +import difflib.myers.Equalizer; import difflib.myers.MyersDiff; import java.util.ArrayList; @@ -44,6 +45,21 @@ public class DiffUtils { return DiffUtils.diff(original, revised, defaultDiffAlgorithm); } + /** + * Compute the difference between the original and revised texts with default diff algorithm + * + * @param original the original text + * @param revised the revised text + * @param equalizer the equalizer object to replace the default compare algorithm (Object.equals) + * @return the patch describing the difference between the original and revised texts + */ + public static Patch diff(List original, List revised, Equalizer equalizer) { + if (equalizer != null) { + return DiffUtils.diff(original, revised, new MyersDiff(equalizer)); + } + return DiffUtils.diff(original, revised, defaultDiffAlgorithm); + } + /** * Compute the difference between the original and revised texts with given diff algorithm * diff --git a/src/difflib/myers/Equalizer.java b/src/difflib/myers/Equalizer.java new file mode 100644 index 0000000..4afe882 --- /dev/null +++ b/src/difflib/myers/Equalizer.java @@ -0,0 +1,5 @@ +package difflib.myers; + +public interface Equalizer { + public boolean equals(Object original, Object revised); +} diff --git a/src/difflib/myers/MyersDiff.java b/src/difflib/myers/MyersDiff.java index fc63509..a4f16aa 100644 --- a/src/difflib/myers/MyersDiff.java +++ b/src/difflib/myers/MyersDiff.java @@ -65,31 +65,44 @@ import java.util.List; /** * A clean-room implementation of * Eugene Myers differencing algorithm. - * + * *

See the paper at * http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps

* * @author Juanco Anez */ public class MyersDiff implements DiffAlgorithm { + private static final Equalizer DEFAULT_EQUALIZER = new Equalizer() { + public boolean equals(Object original, Object revised) { + return original.equals(revised); + } + }; + + private Equalizer equalizer = DEFAULT_EQUALIZER; + + /** * Constructs an instance of the Myers differencing algorithm. */ public MyersDiff() { } - + + public MyersDiff(Equalizer equalizer) { + this.equalizer = equalizer; + } + /** * {@inheritDoc} - * + * * Return empty diff if get the error while procession the difference. */ public Patch diff(List original, List revised) { return diff(original.toArray(), revised.toArray()); } - + /** * {@inheritDoc} - * + * * Return empty diff if get the error while procession the difference. */ public Patch diff(Object[] orig, Object[] rev) { @@ -102,7 +115,7 @@ public class MyersDiff implements DiffAlgorithm { } return new Patch(); } - + /** * Computes the minimum diffpath that expresses de differences * between the original and revised sequences, according @@ -113,22 +126,22 @@ public class MyersDiff implements DiffAlgorithm { * @return A minimum {@link PathNode Path} accross the differences graph. * @throws DifferentiationFailedException if a diff path could not be found. */ - public static PathNode buildPath(Object[] orig, Object[] rev) + public PathNode buildPath(Object[] orig, Object[] rev) throws DifferentiationFailedException { if (orig == null) throw new IllegalArgumentException("original sequence is null"); if (rev == null) throw new IllegalArgumentException("revised sequence is null"); - + // these are local constants final int N = orig.length; final int M = rev.length; - + final int MAX = N + M + 1; final int size = 1 + 2 * MAX; final int middle = size / 2; final PathNode diagonal[] = new PathNode[size]; - + diagonal[middle + 1] = new Snake(0, -1, null); for (int d = 0; d < MAX; d++) { for (int k = -d; k <= d; k += 2) { @@ -136,7 +149,7 @@ public class MyersDiff implements DiffAlgorithm { final int kplus = kmiddle + 1; final int kminus = kmiddle - 1; PathNode prev = null; - + int i; if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) { i = diagonal[kplus].i; @@ -145,36 +158,40 @@ public class MyersDiff implements DiffAlgorithm { i = diagonal[kminus].i + 1; prev = diagonal[kminus]; } - + diagonal[kminus] = null; // no longer used - + int j = i - k; - + PathNode node = new DiffNode(i, j, prev); - + // orig and rev are zero-based // but the algorithm is one-based // that's why there's no +1 when indexing the sequences - while (i < N && j < M && orig[i].equals(rev[j])) { + while (i < N && j < M && equals(orig[i], rev[j])) { i++; j++; } if (i > node.i) node = new Snake(i, j, node); - + diagonal[kmiddle] = node; - + if (i >= N && j >= M) { return diagonal[kmiddle]; } } diagonal[middle + d - 1] = null; - + } // According to Myers, this cannot happen throw new DifferentiationFailedException("could not find a diff path"); } - + + private boolean equals(Object orig, Object rev) { + return equalizer.equals(orig, rev); + } + /** * Constructs a {@link Patch} from a difference path. * @@ -185,14 +202,14 @@ public class MyersDiff implements DiffAlgorithm { * @throws DifferentiationFailedException if a {@link Patch} could * not be built from the given path. */ - public static Patch buildRevision(PathNode path, Object[] orig, Object[] rev) { + public Patch buildRevision(PathNode path, Object[] orig, Object[] rev) { if (path == null) throw new IllegalArgumentException("path is null"); if (orig == null) throw new IllegalArgumentException("original sequence is null"); if (rev == null) throw new IllegalArgumentException("revised sequence is null"); - + Patch patch = new Patch(); if (path.isSnake()) path = path.prev; @@ -201,11 +218,11 @@ public class MyersDiff implements DiffAlgorithm { throw new IllegalStateException("bad diffpath: found snake when looking for diff"); int i = path.i; int j = path.j; - + path = path.prev; int ianchor = path.i; int janchor = path.j; - + Chunk original = new Chunk(ianchor, copyOfRange(orig, ianchor, i)); Chunk revised = new Chunk(janchor, copyOfRange(rev, janchor, j)); Delta delta = null; @@ -216,23 +233,23 @@ public class MyersDiff implements DiffAlgorithm { } else { delta = new ChangeDelta(original, revised); } - + patch.addDelta(delta); if (path.isSnake()) path = path.prev; } return patch; } - + /** * Copied here from JDK 1.6 */ - + @SuppressWarnings("unchecked") public static T[] copyOfRange(T[] original, int from, int to) { return copyOfRange(original, from, to, (Class) original.getClass()); } - + /** * Copied here from JDK 1.6 */ @@ -247,5 +264,5 @@ public class MyersDiff implements DiffAlgorithm { System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); return copy; } - + }