diff --git a/.gitignore b/.gitignore index 8aabf2c..6c0de98 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ target .project .settings *.iml - +.idea diff --git a/.idea/ant.xml b/.idea/ant.xml deleted file mode 100644 index 6ddee46..0000000 --- a/.idea/ant.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index 5267941..0000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index b385f01..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 7c62b52..0000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/libraries/Maven__junit_junit_4_8_1.xml b/.idea/libraries/Maven__junit_junit_4_8_1.xml deleted file mode 100644 index 21ab8f0..0000000 --- a/.idea/libraries/Maven__junit_junit_4_8_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 9835fae..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index fd2ff8f..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 1e7cce4..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index cce6fd9..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/README.md b/README.md index 5d5bbcc..7b31268 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,18 @@ A fork of [java-diff-utils](https://code.google.com/p/java-diff-utils/) # Changelog +## 2.0.0 + +- Change groupId and artifactId to prevent conflit with origin library: now 'com.github.java-diff-utils:java-diff-utils' instead of 'jp.skypencil.java-diff-utils:diffutils' +- Adds the ability to differentiate the inserted and deleted tags and class-names in inline-diff +- Default class-name is now `null` for deleted and inserted data, and "`change`" for change data +- Default tag for deleted data is `del` +- Default tag for inserted data is `ins` +- can now customize diff algorithm in `DiffRowGenerator.Builder` +- fix "equal" lines when lines isn't really equals (when Equalizer return equals on different strings) +- fix imbrication tag bug in lineDiff (when inline is on a multi-line chunk) +- Adds tha ability to skip data + ## 1.5.0 - make Equalizer configurable. ([pull #1](https://github.com/eller86/java-diff-utils/pull/1)) diff --git a/pom.xml b/pom.xml index aba4014..74bcb63 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,9 @@ 4.0.0 - jp.skypencil.java-diff-utils - diffutils + com.github.java-diff-utils + java-diff-utils jar - 1.5.1-SNAPSHOT + 2.0.0-SNAPSHOT java-diff-utils The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. @@ -15,9 +15,9 @@ 7 - scm:git:git@github.com:eller86/java-diff-utils.git - scm:git:git@github.com:eller86/java-diff-utils.git - git@github.com:eller86/java-diff-utils.git + scm:git:git@github.com:jcronier/java-diff-utils.git + scm:git:git@github.com:jcronier/java-diff-utils.git + git@github.com:jcronier/java-diff-utils.git @@ -31,6 +31,10 @@ andreaskumlehn https://github.com/andreaskumlehn + + JY Cr + https://github.com/jcronier + @@ -42,60 +46,48 @@ + 1.7 UTF-8 + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + ${maven.compiler.target} com.google.guava guava - 15.0 + 18.0 com.google.code.findbugs jsr305 - 2.0.2 + 3.0.0 true org.hamcrest hamcrest-library - 1.2.1 + 1.3 test junit junit - 4.11 + 4.12 test - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.7 - 1.7 - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - maven-jar-plugin - 2.4 + 2.6 ${project.build.outputDirectory}/META-INF/MANIFEST.MF @@ -105,7 +97,7 @@ org.apache.felix maven-bundle-plugin - 2.3.7 + 2.5.3 bundle-manifest @@ -116,9 +108,6 @@ - - - - + \ No newline at end of file diff --git a/src/main/java/difflib/DiffAlgorithm.java b/src/main/java/difflib/DiffAlgorithm.java index 21ccd16..5a2ddea 100644 --- a/src/main/java/difflib/DiffAlgorithm.java +++ b/src/main/java/difflib/DiffAlgorithm.java @@ -15,7 +15,9 @@ */ package difflib; -import java.util.*; +import java.util.List; + +import difflib.myers.Equalizer; /** * The general interface for computing diffs between two lists of elements of type T. @@ -44,4 +46,10 @@ public interface DiffAlgorithm { * @return The patch representing the diff of the given sequences. Never {@code null}. */ public Patch diff(List original, List revised); + + /** + * Get equalizer use to compare data. + * @return + */ + public Equalizer getEqualizer(); } diff --git a/src/main/java/difflib/DiffRow.java b/src/main/java/difflib/DiffRow.java index 59b22df..2f975f0 100644 --- a/src/main/java/difflib/DiffRow.java +++ b/src/main/java/difflib/DiffRow.java @@ -41,7 +41,7 @@ public DiffRow(@Nonnull Tag tag, @Nullable String oldLine, @Nullable String newL } public static enum Tag { - INSERT, DELETE, CHANGE, EQUAL + INSERT, DELETE, CHANGE, EQUAL, SKIP } /** diff --git a/src/main/java/difflib/DiffRowGenerator.java b/src/main/java/difflib/DiffRowGenerator.java index 130aa33..0dfe346 100644 --- a/src/main/java/difflib/DiffRowGenerator.java +++ b/src/main/java/difflib/DiffRowGenerator.java @@ -15,10 +15,14 @@ */ package difflib; -import difflib.DiffRow.Tag; -import difflib.myers.Equalizer; - -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -27,6 +31,10 @@ import com.google.common.base.Joiner; import com.google.common.collect.Lists; +import difflib.DiffRow.Tag; +import difflib.myers.Equalizer; +import difflib.myers.MyersDiff; + /** * 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 @@ -40,26 +48,50 @@ * ... * * 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(); + * ignoreWhiteSpaces(true).columnWidth(100).build(); * * * @author Dmitry Naumenko - */ + */ public class DiffRowGenerator { - private static final Joiner LF_JOINER = Joiner.on("\n"); + private static final String NEW_LINE = "\n"; + private static final Joiner LF_JOINER = Joiner.on(NEW_LINE); + private static final Joiner JOINER = Joiner.on(""); + + private static final String DEFAULT_TAG_DELETE = "del"; + private static final String DEFAULT_TAG_INSERT = "ins"; + private static final String DEFAULT_TAG_CHANGE = "span"; + private static final String DEFAULT_CSSCLASS_DELETE = null; + private static final String DEFAULT_CSSCLASS_INSERT = null; + private static final String DEFAULT_CSSCLASS_CHANGE = "change"; + private static final DiffAlgorithm DEFAULT_DIFFALGORITHM = new MyersDiff(new Equalizer() { + public boolean equals(String original, String revised) { + return Objects.equals(original, revised); + } + + @Override + public boolean skip(String original) { + return false; + } + }); private final boolean showInlineDiffs; private final boolean ignoreWhiteSpaces; - private final String InlineOldTag; - private final String InlineNewTag; - private final String InlineOldCssClass; - private final String InlineNewCssClass; + private final String inlineOriginDeleteTag; + private final String inlineRevisedInsertTag; + private final String inlineOriginChangeTag; + private final String inlineRevisedChangeTag; + private final String inlineOriginDeleteCssClass; + private final String inlineRevisedInsertCssClass; + private final String inlineOriginChangeCssClass; + private final String inlineRevisedChangeCssClass; private final int columnWidth; @Nullable private final String defaultString; - private final Equalizer equalizer; + private final DiffAlgorithm diffAlgorithm; /** * This class used for building the DiffRowGenerator. @@ -69,18 +101,18 @@ public class DiffRowGenerator { public static class Builder { private boolean showInlineDiffs = false; private boolean ignoreWhiteSpaces = false; - private String InlineOldTag = "span"; - private String InlineNewTag = "span"; - private String InlineOldCssClass = "editOldInline"; - private String InlineNewCssClass = "editNewInline"; - private int columnWidth = 80; + private String inlineOriginDeleteTag = DEFAULT_TAG_DELETE; + private String inlineOriginChangeTag = DEFAULT_TAG_CHANGE; + private String inlineRevisedInsertTag = DEFAULT_TAG_INSERT; + private String inlineRevisedChangeTag = DEFAULT_TAG_CHANGE; + private String inlineOriginDeleteCssClass = DEFAULT_CSSCLASS_DELETE; + private String inlineRevisedInsertCssClass = DEFAULT_CSSCLASS_INSERT; + private String inlineOriginChangeCssClass = DEFAULT_CSSCLASS_CHANGE; + private String inlineRevisedChangeCssClass = DEFAULT_CSSCLASS_CHANGE; + private int columnWidth = -1; @Nullable private String defaultString = ""; - private Equalizer stringEqualizer = new Equalizer() { - public boolean equals(String original, String revised) { - return Objects.equals(original, revised); - } - }; + private DiffAlgorithm diffAlgorithm = DEFAULT_DIFFALGORITHM; /** * Show inline diffs in generating diff rows or not. @@ -104,53 +136,96 @@ public Builder ignoreWhiteSpaces(boolean val) { /** * Set the tag used for displaying changes in the original text. - * @param tag the tag to set. Without angle brackets. Default: span. - * @return builder with configured ignoreBlankLines parameter + * @param tag the tag to set. Without angle brackets. Default: {@value #DEFAULT_TAG_DELETE}. + * @return builder with configured inlineOriginDeleteTag parameter + * @deprecated Use {@link #inlineOriginDeleteTag(String)} */ + @Deprecated public Builder InlineOldTag(String tag) { - InlineOldTag = tag; + inlineOriginDeleteTag = tag; + return this; + } + + /** + * Set the tag used for displaying delete data in the original text. + * @param tag the tag to set. Without angle brackets. Default: {@value #DEFAULT_TAG_DELETE}. + * @return builder with configured inlineOriginDeleteTag parameter + */ + public Builder inlineOriginDeleteTag(String tag) { + inlineOriginDeleteTag = 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. - * @return builder with configured ignoreBlankLines parameter + * @param tag the tag to set. Without angle brackets. Default: {@value #DEFAULT_TAG_INSERT}. + * @return builder with configured inlineRevisedInsertTag parameter + * @deprecated Use {@link #inlineRevisedInsertTag(String)} */ public Builder InlineNewTag(String tag) { - InlineNewTag = tag; + inlineRevisedInsertTag = tag; + return this; + } + + /** + * Set the tag used for displaying changes in the revised text. + * @param tag the tag to set. Without angle brackets. Default: {@value #DEFAULT_TAG_INSERT}. + * @return builder with configured inlineRevisedInsertTag parameter + */ + public Builder inlineRevisedInsertTag(String tag) { + inlineRevisedInsertTag = 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. - * @return builder with configured ignoreBlankLines parameter + * @param cssClass the tag to set. Without any quotes, just word. Default: {@value #DEFAULT_CSSCLASS_DELETE}. + * @return builder with configured inlineOriginDeleteCssClass parameter + * @deprecated Use {@link #inlineOriginDeleteCssClass(String)} */ public Builder InlineOldCssClass(String cssClass) { - InlineOldCssClass = cssClass; + inlineOriginDeleteCssClass = cssClass; + return this; + } + + /** + * Set the css class used for displaying delete data in the original text. + * @param cssClass the tag to set. Without any quotes, just word. Default: {@value #DEFAULT_CSSCLASS_DELETE}. + * @return builder with configured inlineOriginDeleteCssClass parameter + */ + public Builder inlineOriginDeleteCssClass(String cssClass) { + inlineOriginDeleteCssClass = 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. - * @return builder with configured ignoreBlankLines parameter + * @param cssClass the tag to set. Without any quotes, just word. Default: {@value #DEFAULT_CSSCLASS_INSERT}. + * @return builder with configured inlineRevisedInsertCssClass parameter + * @deprecated Use {@link #inlineRevisedInsertCssClass(String)} */ public Builder InlineNewCssClass(String cssClass) { - InlineNewCssClass = cssClass; + inlineRevisedInsertCssClass = cssClass; + return this; + } + + /** + * Set the css class used for displaying insert data in the revised text. + * @param cssClass the tag to set. Without any quotes, just word. Default: {@value #DEFAULT_CSSCLASS_INSERT}. + * @return builder with configured inlineRevisedInsertCssClass parameter + */ + public Builder inlineRevisedInsertCssClass(String cssClass) { + inlineRevisedInsertCssClass = 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. - * @return builder with configured ignoreBlankLines parameter + * @param width the width to set. Making it < 0 disable line breaking. + * @return builder with configured columnWidth parameter */ public Builder columnWidth(int width) { - if (width > 0) { - columnWidth = width; - } + columnWidth = width; return this; } @@ -166,7 +241,18 @@ public Builder defaultString(@Nullable String defaultString) { * @return builder with configured stringEqualizer */ public Builder stringEqualizer(Equalizer stringEqualizer) { - this.stringEqualizer = stringEqualizer; + this.diffAlgorithm = new MyersDiff<>(stringEqualizer); + return this; + } + + /** + * Set the custom {@link DiffAlgorithm} to use while comparing the lines + * of the revisions. + * @param stringEqualizer to use (custom one) + * @return builder with configured stringEqualizer + */ + public Builder diffAlgorithm(DiffAlgorithm diffAlgorithm) { + this.diffAlgorithm = diffAlgorithm; return this; } @@ -182,14 +268,23 @@ public DiffRowGenerator build() { private DiffRowGenerator(Builder builder) { showInlineDiffs = builder.showInlineDiffs; - ignoreWhiteSpaces = builder.ignoreWhiteSpaces; // - InlineOldTag = builder.InlineOldTag; - InlineNewTag = builder.InlineNewTag; - InlineOldCssClass = builder.InlineOldCssClass; - InlineNewCssClass = builder.InlineNewCssClass; - columnWidth = builder.columnWidth; // + ignoreWhiteSpaces = builder.ignoreWhiteSpaces; + + inlineOriginDeleteTag = builder.inlineOriginDeleteTag; + inlineOriginDeleteCssClass = builder.inlineOriginDeleteCssClass; + + inlineOriginChangeTag = builder.inlineOriginChangeTag; + inlineOriginChangeCssClass = builder.inlineOriginChangeCssClass; + + inlineRevisedInsertTag = builder.inlineRevisedInsertTag; + inlineRevisedInsertCssClass = builder.inlineRevisedInsertCssClass; + + inlineRevisedChangeTag = builder.inlineRevisedChangeTag; + inlineRevisedChangeCssClass = builder.inlineRevisedChangeCssClass; + + columnWidth = builder.columnWidth; defaultString = builder.defaultString; - equalizer = builder.stringEqualizer; + diffAlgorithm = builder.diffAlgorithm; } /** @@ -202,7 +297,7 @@ private DiffRowGenerator(Builder builder) { */ public List generateDiffRows(List original, List revised) { if (ignoreWhiteSpaces) { - Function whiteSpaceReplacer = new Function(){ + Function whiteSpaceReplacer = new Function() { @Override public String apply(String string) { if (string == null) { @@ -215,7 +310,7 @@ public String apply(String string) { original = Lists.transform(original, whiteSpaceReplacer); revised = Lists.transform(revised, whiteSpaceReplacer); } - return generateDiffRows(original, revised, DiffUtils.diff(original, revised, equalizer)); + return generateDiffRows(original, revised, DiffUtils.diff(original, revised, diffAlgorithm)); } /** @@ -233,43 +328,58 @@ public List generateDiffRows(List original, List revise revised = StringUtills.normalize(revised); // wrap to the column width - original = StringUtills.wrapText(original, this.columnWidth); - revised = StringUtills.wrapText(revised, this.columnWidth); - + if (columnWidth > 0) { + original = StringUtills.wrapText(original, this.columnWidth); + revised = StringUtills.wrapText(revised, this.columnWidth); + } List diffRows = new ArrayList(); - int endPos = 0; + int orgEndPos = 0; + int revEndPos = 0; final List> deltaList = patch.getDeltas(); + + Equalizer equalizer = diffAlgorithm.getEqualizer(); + for (int i = 0; i < deltaList.size(); i++) { 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)); + orig.setLines(StringUtills.normalize(orig.getLines())); + rev.setLines(StringUtills.normalize(rev.getLines())); - // catch the equal prefix for each chunk - for (String line : original.subList(endPos, orig.getPosition())) { - diffRows.add(new DiffRow(Tag.EQUAL, line, line)); + if (columnWidth > 0) { + orig.setLines(StringUtills.wrapText(orig.getLines(), this.columnWidth)); + rev.setLines(StringUtills.wrapText(rev.getLines(), this.columnWidth)); } + // catch the equal prefix for each chunk + copyEqualsLines(equalizer, diffRows, original, orgEndPos, orig.getPosition(), revised, revEndPos, + rev.getPosition()); // Inserted DiffRow - if (delta.getClass().equals(InsertDelta.class)) { - endPos = orig.last() + 1; - for (String line : (List) rev.getLines()) { - diffRows.add(new DiffRow(Tag.INSERT, defaultString, line)); + if (delta.getClass() == InsertDelta.class) { + orgEndPos = orig.last() + 1; + revEndPos = rev.last() + 1; + for (String line : rev.getLines()) { + if (equalizer.skip(line)) { + diffRows.add(new DiffRow(Tag.SKIP, defaultString, line)); + } else { + diffRows.add(new DiffRow(Tag.INSERT, defaultString, line)); + } } continue; } // Deleted DiffRow - if (delta.getClass().equals(DeleteDelta.class)) { - endPos = orig.last() + 1; - for (String line : (List) orig.getLines()) { - diffRows.add(new DiffRow(Tag.DELETE, line, defaultString)); + if (delta.getClass() == DeleteDelta.class) { + orgEndPos = orig.last() + 1; + revEndPos = rev.last() + 1; + for (String line : orig.getLines()) { + if (equalizer.skip(line)) { + diffRows.add(new DiffRow(Tag.SKIP, line, defaultString)); + } else { + diffRows.add(new DiffRow(Tag.DELETE, line, defaultString)); + } } continue; } @@ -280,78 +390,113 @@ public List generateDiffRows(List original, List revise // the changed size is match if (orig.size() == rev.size()) { for (int j = 0; j < orig.size(); j++) { - diffRows.add(new DiffRow(Tag.CHANGE, (String) orig.getLines().get(j), - (String) rev.getLines().get(j))); + addChangeDiffRow(equalizer, diffRows, orig.getLines().get(j), rev.getLines().get(j), defaultString); } } else if (orig.size() > rev.size()) { for (int j = 0; j < orig.size(); j++) { - diffRows.add(new DiffRow(Tag.CHANGE, (String) orig.getLines().get(j), rev - .getLines().size() > j ? (String) rev.getLines().get(j) : defaultString)); + final String orgLine = orig.getLines().get(j); + final String revLine = rev.getLines().size() > j ? rev.getLines().get(j) : defaultString; + addChangeDiffRow(equalizer, diffRows, orgLine, revLine, defaultString); } } else { for (int j = 0; j < rev.size(); j++) { - diffRows.add(new DiffRow(Tag.CHANGE, orig.getLines().size() > j ? (String) orig - .getLines().get(j) : defaultString, (String) rev.getLines().get(j))); + final String orgLine = orig.getLines().size() > j ? orig.getLines().get(j) : defaultString; + final String revLine = rev.getLines().get(j); + addChangeDiffRow(equalizer, diffRows, orgLine, revLine, defaultString); } } - endPos = orig.last() + 1; + orgEndPos = orig.last() + 1; + revEndPos = rev.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)); - } + copyEqualsLines(equalizer, diffRows, original, orgEndPos, original.size(), revised, revEndPos, revised.size()); return diffRows; } + private static final void addChangeDiffRow(Equalizer equalizer, List diffRows, String orgLine, + String revLine, String defaultString) { + boolean skipOrg = equalizer.skip(orgLine); + boolean skipRev = equalizer.skip(revLine); + if (skipOrg && skipRev) { + diffRows.add(new DiffRow(Tag.SKIP, orgLine, revLine)); + } else if (skipOrg) { + diffRows.add(new DiffRow(Tag.SKIP, orgLine, defaultString)); + diffRows.add(new DiffRow(Tag.CHANGE, defaultString, revLine)); + } else if (skipRev) { + diffRows.add(new DiffRow(Tag.CHANGE, orgLine, defaultString)); + diffRows.add(new DiffRow(Tag.SKIP, defaultString, revLine)); + } else { + diffRows.add(new DiffRow(Tag.CHANGE, orgLine, revLine)); + } + } + + protected void copyEqualsLines(Equalizer equalizer, List diffRows, List original, + int originalStartPos, int originalEndPos, List revised, int revisedStartPos, int revisedEndPos) { + String[][] lines = new String[originalEndPos - originalStartPos][2]; + int idx = 0; + for (String line : original.subList(originalStartPos, originalEndPos)) { + lines[idx++][0] = line; + } + idx = 0; + for (String line : revised.subList(revisedStartPos, revisedEndPos)) { + lines[idx++][1] = line; + } + for (String[] line : lines) { + String orgLine = line[0]; + String revLine = line[1]; + if (equalizer.skip(orgLine) && equalizer.skip(revLine)) { + diffRows.add(new DiffRow(Tag.SKIP, orgLine, revLine)); + } else { + diffRows.add(new DiffRow(Tag.EQUAL, orgLine, revLine)); + } + } + } + /** * Add the inline diffs for given delta + * * @param delta the given delta */ private void addInlineDiffs(Delta delta) { - List orig = (List) delta.getOriginal().getLines(); - List rev = (List) delta.getRevised().getLines(); - LinkedList origList = new LinkedList(); - for (Character character : LF_JOINER.join(orig).toCharArray()) { - origList.add(character.toString()); - } - LinkedList revList = new LinkedList(); - for (Character character : LF_JOINER.join(rev).toCharArray()) { - revList.add(character.toString()); - } + List orig = delta.getOriginal().getLines(); + List rev = delta.getRevised().getLines(); + LinkedList origList = charArrayToStringList(LF_JOINER.join(orig).toCharArray()); + LinkedList revList = charArrayToStringList(LF_JOINER.join(rev).toCharArray()); + List> inlineDeltas = DiffUtils.diff(origList, revList).getDeltas(); - if (inlineDeltas.size() < 3) { - Collections.reverse(inlineDeltas); - for (Delta inlineDelta : inlineDeltas) { - Chunk inlineOrig = inlineDelta.getOriginal(); - Chunk inlineRev = inlineDelta.getRevised(); - if (inlineDelta.getClass().equals(DeleteDelta.class)) { - origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig - .getPosition() - + inlineOrig.size() + 1, this.InlineOldTag, this.InlineOldCssClass); - } else if (inlineDelta.getClass().equals(InsertDelta.class)) { - revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() - + inlineRev.size() + 1, this.InlineNewTag, this.InlineNewCssClass); - } else if (inlineDelta.getClass().equals(ChangeDelta.class)) { - origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig - .getPosition() - + inlineOrig.size() + 1, this.InlineOldTag, this.InlineOldCssClass); - revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() - + inlineRev.size() + 1, this.InlineNewTag, this.InlineNewCssClass); - } - } - StringBuilder origResult = new StringBuilder(), revResult = new StringBuilder(); - for (String character : origList) { - origResult.append(character); + Collections.reverse(inlineDeltas); + for (Delta inlineDelta : inlineDeltas) { + Chunk inlineOrig = inlineDelta.getOriginal(); + Chunk inlineRev = inlineDelta.getRevised(); + if (inlineDelta.getClass().equals(DeleteDelta.class)) { + origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig.getPosition() + inlineOrig.size() + + 1, this.inlineOriginDeleteTag, this.inlineOriginDeleteCssClass); + } else if (inlineDelta.getClass().equals(InsertDelta.class)) { + revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size() + 1, + this.inlineRevisedInsertTag, this.inlineRevisedInsertCssClass); + } else if (inlineDelta.getClass().equals(ChangeDelta.class)) { + origList = wrapInTag(origList, inlineOrig.getPosition(), inlineOrig.getPosition() + inlineOrig.size() + + 1, this.inlineOriginChangeTag, this.inlineOriginChangeCssClass); + revList = wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size() + 1, + this.inlineRevisedChangeTag, this.inlineRevisedChangeCssClass); } - for (String character : revList) { - revResult.append(character); - } - delta.getOriginal().setLines(Arrays.asList(origResult.toString().split("\n"))); - delta.getRevised().setLines(Arrays.asList(revResult.toString().split("\n"))); } + + delta.getOriginal().setLines(Arrays.asList(JOINER.join(origList).split(NEW_LINE))); + delta.getRevised().setLines(Arrays.asList(JOINER.join(revList).split(NEW_LINE))); } + private static final LinkedList charArrayToStringList(char[] cs) { + LinkedList result = new LinkedList(); + for (Character character : cs) { + result.add(character.toString()); + } + return result; + } + + private static final Pattern PATTERN_CRLF = Pattern.compile("([\\n\\r]+)"); + /** * 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. @@ -359,8 +504,8 @@ private void addInlineDiffs(Delta delta) { * @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, - int endPosition, String tag, String cssClass) { + public static LinkedList wrapInTag(LinkedList sequence, int startPosition, int endPosition, + String tag, String cssClass) { LinkedList result = (LinkedList) sequence.clone(); StringBuilder tagBuilder = new StringBuilder(); tagBuilder.append("<"); @@ -371,17 +516,26 @@ public static LinkedList wrapInTag(LinkedList sequence, int star tagBuilder.append("\""); } tagBuilder.append(">"); - String startTag = tagBuilder.toString(); + final String startTag = tagBuilder.toString(); tagBuilder.delete(0, tagBuilder.length()); tagBuilder.append(""); - String endTag = tagBuilder.toString(); + final String endTag = tagBuilder.toString(); result.add(startPosition, startTag); result.add(endPosition, endTag); + final String joinTag = new StringBuilder(Matcher.quoteReplacement(endTag)).append("$1") + .append(Matcher.quoteReplacement(startTag)).toString(); + + for (int i = startPosition + 1; i < endPosition; ++i) { + final String val = result.get(i); + if (val.contains("\n") || val.contains("\r")) { + result.set(i, PATTERN_CRLF.matcher(val).replaceAll(joinTag)); + } + } return result; } @@ -393,7 +547,7 @@ public static LinkedList wrapInTag(LinkedList sequence, int star * @return the wrapped string */ public static String wrapInTag(String line, String tag, String cssClass) { - StringBuilder tagBuilder = new StringBuilder(); + final StringBuilder tagBuilder = new StringBuilder(); tagBuilder.append("<"); tagBuilder.append(tag); if (cssClass != null) { @@ -402,15 +556,18 @@ public static String wrapInTag(String line, String tag, String cssClass) { tagBuilder.append("\""); } tagBuilder.append(">"); - String startTag = tagBuilder.toString(); + final String startTag = tagBuilder.toString(); tagBuilder.delete(0, tagBuilder.length()); tagBuilder.append(""); - String endTag = tagBuilder.toString(); + final String endTag = tagBuilder.toString(); + + final String joinTag = new StringBuilder(Matcher.quoteReplacement(endTag)).append("$1") + .append(Matcher.quoteReplacement(startTag)).toString(); - return startTag + line + endTag; + return startTag + PATTERN_CRLF.matcher(line).replaceAll(joinTag) + endTag; } -} +} \ No newline at end of file diff --git a/src/main/java/difflib/myers/Equalizer.java b/src/main/java/difflib/myers/Equalizer.java index bff7b65..4202ca3 100644 --- a/src/main/java/difflib/myers/Equalizer.java +++ b/src/main/java/difflib/myers/Equalizer.java @@ -9,13 +9,21 @@ * @param T The type of the compared elements in the 'lines'. */ public interface Equalizer { - - /** - * Indicates if two elements are equal according to the diff mechanism. - * @param original The original element. Must not be {@code null}. - * @param revised The revised element. Must not be {@code null}. - * @return Returns true if the elements are equal. - */ + + /** + * Indicates if two elements are equal according to the diff mechanism. + * @param original The original element. Must not be {@code null}. + * @param revised The revised element. Must not be {@code null}. + * @return Returns true if the elements are equal. + */ + @CheckReturnValue + public boolean equals(@Nullable T original, @Nullable T revised); + + /** + * Indicates if elements must be skipped. + * @param original + * @return + */ @CheckReturnValue - public boolean equals(@Nullable T original, @Nullable T revised); + public boolean skip(@Nullable T original); } diff --git a/src/main/java/difflib/myers/MyersDiff.java b/src/main/java/difflib/myers/MyersDiff.java index 40be478..9cccc85 100644 --- a/src/main/java/difflib/myers/MyersDiff.java +++ b/src/main/java/difflib/myers/MyersDiff.java @@ -75,11 +75,21 @@ * @param T The type of the compared elements in the 'lines'. */ public class MyersDiff implements DiffAlgorithm { - - /** Default equalizer. */ - private final Equalizer DEFAULT_EQUALIZER = new Equalizer() { + /** Default equalizer. */ + private final Equalizer DEFAULT_EQUALIZER = new Equalizer() { public boolean equals(final T original, final T revised) { - return original.equals(revised); + if (original == null && revised == null) { + return true; + } + if (original != null) { + return original.equals(revised); + } + return false; + } + + @Override + public boolean skip(T original) { + return false; } }; @@ -295,5 +305,8 @@ public static T[] copyOfRange2(U[] original, int from, int to, System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); return copy; } - -} + + public Equalizer getEqualizer() { + return equalizer; + } +} \ No newline at end of file