From 04b5c7e167d0108999bb5a138360fb9beda3c548 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 16 Jan 2021 00:05:09 +0100 Subject: [PATCH 001/107] started decompression idea --- .../com/github/difflib/DiffUtilsTest.java | 8 ++++ .../difflib/text/DiffRowGeneratorTest.java | 40 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index 000b095a..5d1ceb43 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -227,4 +227,12 @@ public void testDiff_ProblemIssue42() { assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); } + + @Test + public void testDiffIssue114() { + final Patch patch = DiffUtils.diff(Arrays.asList("A", "B", "C", "D", "E"), Arrays.asList("a", "C", "", "E"), true); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 63c8e454..c978cc05 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -600,23 +600,41 @@ public void testLinefeedInStandardTagsWithLineWidthIssue81() { System.out.println(deltas); } - + @Test - public void testIssue86WrongInlineDiff() throws IOException { + public void testIssue86WrongInlineDiff() throws IOException { String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_original.txt")).collect(joining("\n")); String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_revised.txt")).collect(joining("\n")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(false) - .inlineDiffByWord(true) - .oldTag( f -> "~" ) - .newTag( f -> "**" ) - .build(); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(false) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); List rows = generator.generateDiffRows( Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); - + + for (DiffRow diff : rows) { + System.out.println(diff); + } + } + + @Test + public void testCorrectChangeIssue114() throws IOException { + List original = Arrays.asList("A", "B", "C", "D", "E"); + List revised = Arrays.asList("a", "C", "", "E"); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows(original, revised); + for (DiffRow diff : rows) { System.out.println(diff); } From ffd14fb67dee86202e53010a3698d7b824e11ea6 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 16 Jan 2021 00:08:19 +0100 Subject: [PATCH 002/107] started decompression idea --- .../src/test/java/com/github/difflib/DiffUtilsTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index 5d1ceb43..000b095a 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -227,12 +227,4 @@ public void testDiff_ProblemIssue42() { assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); } - - @Test - public void testDiffIssue114() { - final Patch patch = DiffUtils.diff(Arrays.asList("A", "B", "C", "D", "E"), Arrays.asList("a", "C", "", "E"), true); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } } From 7e8d2867ada88c4fc41bd27fa8a05ff68dad8be6 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 16 Jan 2021 02:22:47 +0100 Subject: [PATCH 003/107] fixes #114 - decompressing deltas --- .../github/difflib/text/DiffRowGenerator.java | 124 +++++++++++++----- .../difflib/text/DiffRowGeneratorTest.java | 31 ++++- 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index c315c45c..1a10f5d6 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -17,8 +17,11 @@ import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; +import com.github.difflib.patch.DeleteDelta; import com.github.difflib.patch.DeltaType; +import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; import com.github.difflib.text.DiffRow.Tag; import java.util.*; @@ -30,14 +33,16 @@ import static java.util.stream.Collectors.toList; /** - * 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 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. + * 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 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; ... + * These values are: showInlineDiffs = false; ignoreWhiteSpaces = true; + * ignoreBlankLines = true; ... * - * For instantiating the DiffRowGenerator you should use the its builder. Like in example + * 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(); * @@ -100,8 +105,8 @@ protected final static List splitStringPreserveDelimiter(String str, Pat /** * 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 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 tagGenerator the tag generator */ @@ -187,7 +192,7 @@ private DiffRowGenerator(Builder builder) { reportLinesUnchanged = builder.reportLinesUnchanged; lineNormalizer = builder.lineNormalizer; processDiffs = builder.processDiffs; - + replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; Objects.requireNonNull(inlineDiffSplitter); @@ -195,8 +200,8 @@ private DiffRowGenerator(Builder builder) { } /** - * Get the DiffRows describing the difference between original and revised texts using the given - * patch. Useful for displaying side-by-side diff. + * 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 @@ -207,8 +212,9 @@ public List generateDiffRows(List original, List revise } /** - * Generates the DiffRows describing the difference between original and revised texts using the - * given patch. Useful for displaying side-by-side diff. + * 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 patch the given patch @@ -218,6 +224,9 @@ public List generateDiffRows(final List original, Patch List diffRows = new ArrayList<>(); int endPos = 0; final List> deltaList = patch.getDeltas(); + + decompressDeltas(deltaList); + for (AbstractDelta delta : deltaList) { Chunk orig = delta.getSource(); Chunk rev = delta.getTarget(); @@ -263,6 +272,44 @@ public List generateDiffRows(final List original, Patch return diffRows; } + /** + * Decompresses ChangeDeltas with different source and target size to a ChangeDelta with same size and + * a following InsertDelta or DeleteDelta. With this problems of building DiffRows getting smaller. + * @param deltaList + */ + private void decompressDeltas(final List> deltaList) { + for (int idx = 0; idx < deltaList.size(); idx++) { + AbstractDelta delta = deltaList.get(idx); + if (delta.getType() == DeltaType.CHANGE && delta.getSource().size() != delta.getTarget().size()) { + //System.out.println("decompress this " + delta); + + List> corrected = new ArrayList<>(); + int minSize = Math.min(delta.getSource().size(), delta.getTarget().size()); + Chunk orig = delta.getSource(); + Chunk rev = delta.getTarget(); + + deltaList.set(idx, new ChangeDelta( + new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)), + new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize)))); + + if (orig.getLines().size() < rev.getLines().size()) { + deltaList.add(idx + 1, new InsertDelta( + new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()), + new Chunk<>(rev.getPosition() + minSize, rev.getLines().subList(minSize, rev.getLines().size())))); + } else { + deltaList.add(idx + 1, new DeleteDelta( + new Chunk<>(orig.getPosition() + minSize, orig.getLines().subList(minSize, orig.getLines().size())), + new Chunk<>(rev.getPosition() + minSize, Collections.emptyList()))); + } + + //System.out.println(" to " + corrected); + } + idx++; + } + + //System.out.println("got now " + deltaList); + } + private DiffRow buildDiffRow(Tag type, String orgline, String newline) { if (reportLinesUnchanged) { return new DiffRow(type, orgline, newline); @@ -436,8 +483,8 @@ public Builder ignoreWhiteSpaces(boolean val) { } /** - * Give the originial old and new text lines to Diffrow without any additional processing - * and without any tags to highlight the change. + * Give the originial old and new text lines to Diffrow without any + * additional processing and without any tags to highlight the change. * * @param val the value to set. Default: false. * @return builder with configured reportLinesUnWrapped parameter @@ -492,8 +539,8 @@ public Builder newTag(Function generator) { } /** - * Processor for diffed text parts. Here e.g. whitecharacters could be replaced by something - * visible. + * Processor for diffed text parts. Here e.g. whitecharacters could be + * replaced by something visible. * * @param processDiffs * @return @@ -504,10 +551,11 @@ public Builder processDiffs(Function processDiffs) { } /** - * Set the column width of generated lines of original and revised texts. + * Set the column width of generated lines of original and revised + * texts. * - * @param width the width to set. Making it < 0 doesn't make any sense. Default 80. - * @return builder with config of column width + * @param width the width to set. Making it < 0 doesn't make any sense. + * Default 80. @return builder with config of column width */ public Builder columnWidth(int width) { if (width >= 0) { @@ -517,7 +565,8 @@ public Builder columnWidth(int width) { } /** - * Build the DiffRowGenerator. If some parameters is not set, the default values are used. + * Build the DiffRowGenerator. If some parameters is not set, the + * default values are used. * * @return the customized DiffRowGenerator */ @@ -526,8 +575,8 @@ public DiffRowGenerator build() { } /** - * Merge the complete result within the original text. This makes sense for one line - * display. + * Merge the complete result within the original text. This makes sense + * for one line display. * * @param mergeOriginalRevised * @return @@ -538,9 +587,9 @@ public Builder mergeOriginalRevised(boolean mergeOriginalRevised) { } /** - * Per default each character is separatly processed. This variant introduces processing by - * word, which does not deliver in word changes. Therefore the whole word will be tagged as - * changed: + * Per default each character is separatly processed. This variant + * introduces processing by word, which does not deliver in word + * changes. Therefore the whole word will be tagged as changed: * *
          * false:    (aBa : aba) --  changed: a(B)a : a(b)a
@@ -553,8 +602,9 @@ public Builder inlineDiffByWord(boolean inlineDiffByWord) {
         }
 
         /**
-         * To provide some customized splitting a splitter can be provided. Here someone could think
-         * about sentence splitter, comma splitter or stuff like that.
+         * To provide some customized splitting a splitter can be provided. Here
+         * someone could think about sentence splitter, comma splitter or stuff
+         * like that.
          *
          * @param inlineDiffSplitter
          * @return
@@ -565,9 +615,10 @@ public Builder inlineDiffBySplitter(Function> inlineDiffSpl
         }
 
         /**
-         * By default DiffRowGenerator preprocesses lines for HTML output. Tabs and special HTML
-         * characters like "<" are replaced with its encoded value. To change this you can
-         * provide a customized line normalizer here.
+         * By default DiffRowGenerator preprocesses lines for HTML output. Tabs
+         * and special HTML characters like "<" are replaced with its encoded
+         * value. To change this you can provide a customized line normalizer
+         * here.
          *
          * @param lineNormalizer
          * @return
@@ -587,13 +638,14 @@ public Builder equalizer(BiPredicate equalizer) {
             this.equalizer = equalizer;
             return this;
         }
-        
+
         /**
-         * Sometimes it happens that a change contains multiple lines. If there is no correspondence
-         * in old and new. To keep the merged line more readable the linefeeds could be replaced
-         * by spaces.
+         * Sometimes it happens that a change contains multiple lines. If there
+         * is no correspondence in old and new. To keep the merged line more
+         * readable the linefeeds could be replaced by spaces.
+         *
          * @param replace
-         * @return 
+         * @return
          */
         public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) {
             this.replaceOriginalLinefeedInChangesWithSpaces = replace;
diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
index c978cc05..8a87e729 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
@@ -163,7 +163,7 @@ public void testGeneratorWithMerge3() {
         assertEquals(6, rows.size());
         assertEquals("[CHANGE,test,anything]", rows.get(0).toString());
         assertEquals("[CHANGE,anything ,]", rows.get(1).toString());
-        assertEquals("[CHANGE, ,]", rows.get(2).toString());
+        assertEquals("[DELETE, ,]", rows.get(2).toString());
         assertEquals("[EQUAL,other,other]", rows.get(3).toString());
         assertEquals("[INSERT,test,test]", rows.get(4).toString());
         assertEquals("[INSERT,test2,test2]", rows.get(5).toString());
@@ -343,7 +343,7 @@ public void testGeneratorIssue22() {
                 Arrays.asList(aa.split("\n")),
                 Arrays.asList(bb.split("\n")));
 
-        assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [CHANGE,,**This is the second line.**]]",
+        assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**]]",
                 rows.toString());
 
         System.out.println("|original|new|");
@@ -367,7 +367,7 @@ public void testGeneratorIssue22_2() {
                 Arrays.asList(aa.split("\n")),
                 Arrays.asList(bb.split("\n")));
 
-        assertEquals("[[CHANGE,This is a test ~for diffutils~.,This is a test **senctence**.], [CHANGE,~This is the second line.~,]]",
+        assertEquals("[[CHANGE,This is a test ~for diffutils~.,This is a test **senctence**.], [DELETE,~This is the second line.~,]]",
                 rows.toString());
     }
 
@@ -385,7 +385,7 @@ public void testGeneratorIssue22_3() {
                 Arrays.asList(aa.split("\n")),
                 Arrays.asList(bb.split("\n")));
 
-        assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [CHANGE,,**This is the second line.**], [CHANGE,,**And one more.**]]",
+        assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**], [INSERT,,**And one more.**]]",
                 rows.toString());
     }
 
@@ -627,6 +627,26 @@ public void testCorrectChangeIssue114() throws IOException {
         List original = Arrays.asList("A", "B", "C", "D", "E");
         List revised = Arrays.asList("a", "C", "", "E");
 
+        DiffRowGenerator generator = DiffRowGenerator.create()
+                .showInlineDiffs(false)
+                .inlineDiffByWord(true)
+                .oldTag(f -> "~")
+                .newTag(f -> "**")
+                .build();
+        List rows = generator.generateDiffRows(original, revised);
+
+        for (DiffRow diff : rows) {
+            System.out.println(diff);
+        }
+
+        assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL");
+    }
+    
+    @Test
+    public void testCorrectChangeIssue114_2() throws IOException {
+        List original = Arrays.asList("A", "B", "C", "D", "E");
+        List revised = Arrays.asList("a", "C", "", "E");
+
         DiffRowGenerator generator = DiffRowGenerator.create()
                 .showInlineDiffs(true)
                 .inlineDiffByWord(true)
@@ -638,5 +658,8 @@ public void testCorrectChangeIssue114() throws IOException {
         for (DiffRow diff : rows) {
             System.out.println(diff);
         }
+
+        assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL");
+        assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]");
     }
 }

From 507ced4493b0d8efbdfae83b546a480de428f14b Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sat, 16 Jan 2021 02:30:04 +0100
Subject: [PATCH 004/107] fixes #114 - decompressing deltas

---
 .../com/github/difflib/text/DiffRowGeneratorTest.java     | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
index 8a87e729..389ba1f1 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
@@ -608,7 +608,7 @@ public void testIssue86WrongInlineDiff() throws IOException {
 
         DiffRowGenerator generator = DiffRowGenerator.create()
                 .showInlineDiffs(true)
-                .mergeOriginalRevised(false)
+                .mergeOriginalRevised(true)
                 .inlineDiffByWord(true)
                 .oldTag(f -> "~")
                 .newTag(f -> "**")
@@ -617,9 +617,9 @@ public void testIssue86WrongInlineDiff() throws IOException {
                 Arrays.asList(original.split("\n")),
                 Arrays.asList(revised.split("\n")));
 
-        for (DiffRow diff : rows) {
-            System.out.println(diff);
-        }
+        rows.stream()
+                .filter(item -> item.getTag() != DiffRow.Tag.EQUAL)
+                .forEach(System.out::println);
     }
 
     @Test

From 73ec9e93207284672ac8134aab6442992d6be4b9 Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sat, 16 Jan 2021 23:21:01 +0100
Subject: [PATCH 005/107] fixes #114 - decompressing deltas - refactoring

---
 CHANGELOG.md                                  |   7 +
 .../github/difflib/text/DiffRowGenerator.java | 130 +++++++++---------
 2 files changed, 72 insertions(+), 65 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f195bdb..3bdb8014 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,13 @@ This project uses a custom versioning scheme (and not [Semantic Versioning](http
 
 ### Changed
 
+## [4.10]
+
+### Changed
+
+* bugfixing on new UnifiedDiff reader / writer for multifile useage
+* bugfix for wrong DiffRow type while transforming from a patch that removed a line in one changeset
+
 ## [4.9]
 
 ### Changed
diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java
index 1a10f5d6..6cd13b38 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java
@@ -225,89 +225,89 @@ public List generateDiffRows(final List original, Patch
         int endPos = 0;
         final List> deltaList = patch.getDeltas();
 
-        decompressDeltas(deltaList);
+        for (AbstractDelta originalDelta : deltaList) {
+            for (AbstractDelta delta : decompressDeltas(originalDelta)) {
+                endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta);
+            }
+        }
 
-        for (AbstractDelta delta : deltaList) {
-            Chunk orig = delta.getSource();
-            Chunk rev = delta.getTarget();
+        // Copy the final matching chunk if any.
+        for (String line : original.subList(endPos, original.size())) {
+            diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
+        }
+        return diffRows;
+    }
 
-            for (String line : original.subList(endPos, orig.getPosition())) {
-                diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
-            }
+    /**
+     * Transforms one patch delta into a DiffRow object.
+     */
+    private int transformDeltaIntoDiffRow(final List original, int endPos, List diffRows, AbstractDelta delta) {
+        Chunk orig = delta.getSource();
+        Chunk rev = delta.getTarget();
+
+        for (String line : original.subList(endPos, orig.getPosition())) {
+            diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
+        }
 
-            // Inserted DiffRow
-            if (delta.getType() == DeltaType.INSERT) {
-                endPos = orig.last() + 1;
+        switch (delta.getType()) {
+            case INSERT:
                 for (String line : rev.getLines()) {
                     diffRows.add(buildDiffRow(Tag.INSERT, "", line));
                 }
-                continue;
-            }
-
-            // Deleted DiffRow
-            if (delta.getType() == DeltaType.DELETE) {
-                endPos = orig.last() + 1;
+                break;
+            case DELETE:
                 for (String line : orig.getLines()) {
                     diffRows.add(buildDiffRow(Tag.DELETE, line, ""));
                 }
-                continue;
-            }
-
-            if (showInlineDiffs) {
-                diffRows.addAll(generateInlineDiffs(delta));
-            } else {
-                for (int j = 0; j < Math.max(orig.size(), rev.size()); j++) {
-                    diffRows.add(buildDiffRow(Tag.CHANGE,
-                            orig.getLines().size() > j ? orig.getLines().get(j) : "",
-                            rev.getLines().size() > j ? rev.getLines().get(j) : ""));
+                break;
+            default:
+                if (showInlineDiffs) {
+                    diffRows.addAll(generateInlineDiffs(delta));
+                } else {
+                    for (int j = 0; j < Math.max(orig.size(), rev.size()); j++) {
+                        diffRows.add(buildDiffRow(Tag.CHANGE,
+                                orig.getLines().size() > j ? orig.getLines().get(j) : "",
+                                rev.getLines().size() > j ? rev.getLines().get(j) : ""));
+                    }
                 }
-            }
-            endPos = orig.last() + 1;
         }
 
-        // Copy the final matching chunk if any.
-        for (String line : original.subList(endPos, original.size())) {
-            diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
-        }
-        return diffRows;
+        return orig.last() + 1;
     }
 
     /**
-     * Decompresses ChangeDeltas with different source and target size to a ChangeDelta with same size and
-     * a following InsertDelta or DeleteDelta. With this problems of building DiffRows getting smaller.
-     * @param deltaList 
+     * Decompresses ChangeDeltas with different source and target size to a
+     * ChangeDelta with same size and a following InsertDelta or DeleteDelta.
+     * With this problems of building DiffRows getting smaller.
+     *
+     * @param deltaList
      */
-    private void decompressDeltas(final List> deltaList) {
-        for (int idx = 0; idx < deltaList.size(); idx++) {
-            AbstractDelta delta = deltaList.get(idx);
-            if (delta.getType() == DeltaType.CHANGE && delta.getSource().size() != delta.getTarget().size()) {
-                //System.out.println("decompress this " + delta);
-                
-                List> corrected = new ArrayList<>();
-                int minSize = Math.min(delta.getSource().size(), delta.getTarget().size());
-                Chunk orig = delta.getSource();
-                Chunk rev = delta.getTarget();
-                
-                deltaList.set(idx, new ChangeDelta(
-                        new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)),
-                        new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize))));
-                
-                if (orig.getLines().size() < rev.getLines().size()) {
-                    deltaList.add(idx + 1, new InsertDelta(
-                            new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()),
-                            new Chunk<>(rev.getPosition() + minSize, rev.getLines().subList(minSize, rev.getLines().size()))));
-                } else {
-                    deltaList.add(idx + 1, new DeleteDelta(
-                            new Chunk<>(orig.getPosition() + minSize, orig.getLines().subList(minSize, orig.getLines().size())),
-                            new Chunk<>(rev.getPosition() + minSize, Collections.emptyList())));
-                }
-                
-                //System.out.println(" to " + corrected);
+    private List> decompressDeltas(AbstractDelta delta) {
+        if (delta.getType() == DeltaType.CHANGE && delta.getSource().size() != delta.getTarget().size()) {
+            List> deltas = new ArrayList<>();
+            //System.out.println("decompress this " + delta);
+
+            int minSize = Math.min(delta.getSource().size(), delta.getTarget().size());
+            Chunk orig = delta.getSource();
+            Chunk rev = delta.getTarget();
+
+            deltas.add(new ChangeDelta(
+                    new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)),
+                    new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize))));
+
+            if (orig.getLines().size() < rev.getLines().size()) {
+                deltas.add(new InsertDelta(
+                        new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()),
+                        new Chunk<>(rev.getPosition() + minSize, rev.getLines().subList(minSize, rev.getLines().size()))));
+            } else {
+                deltas.add(new DeleteDelta(
+                        new Chunk<>(orig.getPosition() + minSize, orig.getLines().subList(minSize, orig.getLines().size())),
+                        new Chunk<>(rev.getPosition() + minSize, Collections.emptyList())));
             }
-            idx++;
+            return deltas;
         }
-        
-        //System.out.println("got now " + deltaList);
+
+        return Collections.singletonList(delta);
     }
 
     private DiffRow buildDiffRow(Tag type, String orgline, String newline) {

From 57df629662f8a90c4aa8f687b501e4932606c80e Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sat, 6 Feb 2021 21:41:11 +0100
Subject: [PATCH 006/107]

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 2427bd65..2f29ab86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
     
     The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java.
     https://github.com/java-diff-utils/java-diff-utils
-    2009
+    2009 
 	
     
         

From 0905d0e5f768b9c0fcfff9be0c0871079da6c9d2 Mon Sep 17 00:00:00 2001
From: Tobias 
Date: Sat, 6 Feb 2021 23:35:55 +0100
Subject: [PATCH 007/107] Update issue templates

---
 .github/ISSUE_TEMPLATE/simple.md | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 .github/ISSUE_TEMPLATE/simple.md

diff --git a/.github/ISSUE_TEMPLATE/simple.md b/.github/ISSUE_TEMPLATE/simple.md
new file mode 100644
index 00000000..59f8b243
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/simple.md
@@ -0,0 +1,10 @@
+---
+name: Simple
+about: just a simple issue
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+

From b17bbb12c3ff723471dcdfed6b631e9313a127cb Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 7 Feb 2021 00:47:27 +0100
Subject: [PATCH 008/107] fixes #115

---
 .../github/difflib/patch/AbstractDelta.java   |  4 +--
 .../java/com/github/difflib/patch/Chunk.java  |  8 +++---
 .../com/github/difflib/patch/VerifyChunk.java | 26 +++++++++++++++++++
 3 files changed, 32 insertions(+), 6 deletions(-)
 create mode 100644 java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java

diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
index a089d74d..4c287a16 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
@@ -54,8 +54,8 @@ public DeltaType getType() {
      * @param target
      * @throws PatchFailedException 
      */
-    protected void verifyChunk(List target) throws PatchFailedException {
-        getSource().verify(target);
+    protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException {
+        return getSource().verifyChunk(target);
     }
     
     public abstract void applyTo(List target) throws PatchFailedException;
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
index f108bf5e..03c60737 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
@@ -92,16 +92,16 @@ public Chunk(int position, T[] lines) {
      * @param target the sequence to verify against.
      * @throws com.github.difflib.patch.PatchFailedException
      */
-    public void verify(List target) throws PatchFailedException {
+    public VerifyChunk verifyChunk(List target) throws PatchFailedException {
         if (position > target.size() || last() > target.size()) {
-            throw new PatchFailedException("Incorrect Chunk: the position of chunk > target size");
+            return VerifyChunk.POSITION_OUT_OF_TARGET;
         }
         for (int i = 0; i < size(); i++) {
             if (!target.get(position + i).equals(lines.get(i))) {
-                throw new PatchFailedException(
-                        "Incorrect Chunk: the chunk content doesn't match the target");
+                return VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET;
             }
         }
+        return VerifyChunk.OK;
     }
 
     /**
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java
new file mode 100644
index 00000000..076f633a
--- /dev/null
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 java-diff-utils.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.difflib.patch;
+
+/**
+ *
+ * @author tw
+ */
+public enum VerifyChunk {
+    OK,
+    POSITION_OUT_OF_TARGET,
+    CONTENT_DOES_NOT_MATCH_TARGET
+}

From b9fea86e43ee84f80c8fe80955eee56995b2c6c3 Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 7 Feb 2021 13:22:20 +0100
Subject: [PATCH 009/107] remove verification from applyTo

---
 .../src/main/java/com/github/difflib/patch/Patch.java           | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
index 8e8066d7..d1824478 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
@@ -57,7 +57,7 @@ public List applyTo(List target) throws PatchFailedException {
         ListIterator> it = getDeltas().listIterator(deltas.size());
         while (it.hasPrevious()) {
             AbstractDelta delta = it.previous();
-            delta.applyTo(result);
+            delta.verifyAntApplyTo(result);
         }
         return result;
     }

From 307574225a45d3995f222e767112eef6741d6af2 Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 7 Feb 2021 13:22:34 +0100
Subject: [PATCH 010/107] remove verification from applyTo

---
 .../java/com/github/difflib/patch/AbstractDelta.java | 12 ++++++++++--
 .../java/com/github/difflib/patch/ChangeDelta.java   |  5 ++---
 .../java/com/github/difflib/patch/DeleteDelta.java   |  5 ++---
 .../java/com/github/difflib/patch/EqualDelta.java    |  5 ++---
 .../java/com/github/difflib/patch/InsertDelta.java   |  5 ++---
 5 files changed, 18 insertions(+), 14 deletions(-)

diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
index 4c287a16..7db3ac50 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java
@@ -57,10 +57,18 @@ public DeltaType getType() {
     protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException {
         return getSource().verifyChunk(target);
     }
+   
+    protected VerifyChunk verifyAntApplyTo(List target) throws PatchFailedException {
+        final VerifyChunk verify = verifyChunkToFitTarget(target);
+        if (verify == VerifyChunk.OK) {
+            applyTo(target);
+        }
+        return verify;
+    }
     
-    public abstract void applyTo(List target) throws PatchFailedException;
+    protected abstract void applyTo(List target) throws PatchFailedException;
     
-    public abstract void restore(List target);
+    protected abstract void restore(List target);
     
     /**
      * Create a new delta of the actual instance with customized chunk data.
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java
index 39b82a55..ddb37665 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java
@@ -39,8 +39,7 @@ public ChangeDelta(Chunk source, Chunk target) {
     }
 
     @Override
-    public void applyTo(List target) throws PatchFailedException {
-        verifyChunk(target);
+    protected void applyTo(List target) throws PatchFailedException {
         int position = getSource().getPosition();
         int size = getSource().size();
         for (int i = 0; i < size; i++) {
@@ -54,7 +53,7 @@ public void applyTo(List target) throws PatchFailedException {
     }
 
     @Override
-    public void restore(List target) {
+    protected void restore(List target) {
         int position = getTarget().getPosition();
         int size = getTarget().size();
         for (int i = 0; i < size; i++) {
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java
index f4ee14c4..890b8575 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java
@@ -36,8 +36,7 @@ public DeleteDelta(Chunk original, Chunk revised) {
     }
 
     @Override
-    public void applyTo(List target) throws PatchFailedException {
-        verifyChunk(target);
+    protected void applyTo(List target) throws PatchFailedException {
         int position = getSource().getPosition();
         int size = getSource().size();
         for (int i = 0; i < size; i++) {
@@ -46,7 +45,7 @@ public void applyTo(List target) throws PatchFailedException {
     }
 
     @Override
-    public void restore(List target) {
+    protected void restore(List target) {
         int position = this.getTarget().getPosition();
         List lines = this.getSource().getLines();
         for (int i = 0; i < lines.size(); i++) {
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java
index 167b0b70..e13cf52e 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java
@@ -28,12 +28,11 @@ public EqualDelta(Chunk source, Chunk target) {
     }
 
     @Override
-    public void applyTo(List target) throws PatchFailedException {
-        verifyChunk(target);
+    protected void applyTo(List target) throws PatchFailedException {
     }
 
     @Override
-    public void restore(List target) {
+    protected void restore(List target) {
     }
 
     @Override
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java
index 0f6a65fb..6cff9103 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java
@@ -36,8 +36,7 @@ public InsertDelta(Chunk original, Chunk revised) {
     }
 
     @Override
-    public void applyTo(List target) throws PatchFailedException {
-        verifyChunk(target);
+    protected void applyTo(List target) throws PatchFailedException {
         int position = this.getSource().getPosition();
         List lines = this.getTarget().getLines();
         for (int i = 0; i < lines.size(); i++) {
@@ -46,7 +45,7 @@ public void applyTo(List target) throws PatchFailedException {
     }
 
     @Override
-    public void restore(List target) {
+    protected void restore(List target) {
         int position = getTarget().getPosition();
         int size = getTarget().size();
         for (int i = 0; i < size; i++) {

From f71275de08cbf7fad74f9c2faf06a180e0a153bf Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 7 Feb 2021 13:22:52 +0100
Subject: [PATCH 011/107] upgraded some versions

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 2f29ab86..7d02aa26 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,13 +65,13 @@
             
                 org.junit.jupiter
                 junit-jupiter
-                5.6.2
+                5.7.1
                 test
             
             
                 org.assertj
                 assertj-core
-                3.15.0
+                3.19.0
                 test
             
         

From ce05cbf9c9fa2ba67f418234577945981205e39b Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Wed, 10 Feb 2021 07:34:55 +0100
Subject: [PATCH 012/107]

---
 .../github/difflib/patch/ConflictOutput.java  | 32 +++++++++++++++++++
 .../java/com/github/difflib/patch/Patch.java  | 28 ++++++++++++----
 2 files changed, 54 insertions(+), 6 deletions(-)
 create mode 100644 java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java

diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java
new file mode 100644
index 00000000..44b714fa
--- /dev/null
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java
@@ -0,0 +1,32 @@
+/*-
+ * #%L
+ * java-diff-utils
+ * %%
+ * Copyright (C) 2009 - 2017 java-diff-utils
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ * #L%
+ */
+package com.github.difflib.patch;
+
+import java.util.List;
+
+/**
+ *
+ * @author tw
+ */
+@FunctionalInterface
+public interface ConflictOutput {
+
+    public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) throws PatchFailedException;
+}
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
index d1824478..3d0cddf7 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
@@ -29,8 +29,9 @@
 import java.util.ListIterator;
 
 /**
- * Describes the patch holding all deltas between the original and revised texts.
- * 
+ * Describes the patch holding all deltas between the original and revised
+ * texts.
+ *
  * @author Dmitry Naumenko
  * @param  The type of the compared elements in the 'lines'.
  */
@@ -57,10 +58,25 @@ public List applyTo(List target) throws PatchFailedException {
         ListIterator> it = getDeltas().listIterator(deltas.size());
         while (it.hasPrevious()) {
             AbstractDelta delta = it.previous();
-            delta.verifyAntApplyTo(result);
+            VerifyChunk valid = delta.verifyAntApplyTo(result);
+            if (valid != VerifyChunk.OK) {
+                
+            }
         }
         return result;
     }
+    
+    private ConflictOutput conflictOutput = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> {
+        throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString());
+    };
+    
+    /**
+     * Alter normal conflict output behaviour to e.g. inclide some conflict statements in the result, like git does it.
+     */
+    public Patch withConflictOutput(ConflictOutput conflictOutput) {
+        this.conflictOutput = conflictOutput;
+        return this;
+    }
 
     /**
      * Restore the text to original. Opposite to applyTo() method.
@@ -114,14 +130,14 @@ public static  Patch generate(List original, List revised, List patch = new Patch<>(_changes.size());
         int startOriginal = 0;
         int startRevised = 0;
-        
+
         List changes = _changes;
-        
+
         if (includeEquals) {
             changes = new ArrayList(_changes);
             Collections.sort(changes, comparing(d -> d.startOriginal));
         }
-        
+
         for (Change change : changes) {
 
             if (includeEquals && startOriginal < change.startOriginal) {

From 8880ec545b61e507c29d7c180bae7fa30c721b0a Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 21 Feb 2021 23:04:17 +0100
Subject: [PATCH 013/107] introduced conflict processing

---
 .../github/difflib/patch/ConflictOutput.java  |  3 ++-
 .../java/com/github/difflib/patch/Patch.java  | 16 +++++++++++-----
 .../difflib/GenerateUnifiedDiffTest.java      | 19 ++++++++++++++++++-
 3 files changed, 31 insertions(+), 7 deletions(-)

diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java
index 44b714fa..2dfff6a5 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java
@@ -19,6 +19,7 @@
  */
 package com.github.difflib.patch;
 
+import java.io.Serializable;
 import java.util.List;
 
 /**
@@ -26,7 +27,7 @@
  * @author tw
  */
 @FunctionalInterface
-public interface ConflictOutput {
+public interface ConflictOutput extends Serializable {
 
     public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) throws PatchFailedException;
 }
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
index 3d0cddf7..c693fe29 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
@@ -60,18 +60,24 @@ public List applyTo(List target) throws PatchFailedException {
             AbstractDelta delta = it.previous();
             VerifyChunk valid = delta.verifyAntApplyTo(result);
             if (valid != VerifyChunk.OK) {
-                
+                conflictOutput.processConflict(valid, delta, result);
             }
         }
         return result;
     }
-    
-    private ConflictOutput conflictOutput = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> {
+
+    /**
+     * Standard Patch behaviour to throw an exception for pathching conflicts.
+     */
+    public final ConflictOutput CONFLICT_PRODUCES_EXCEPTION = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> {
         throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString());
     };
-    
+
+    private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION;
+
     /**
-     * Alter normal conflict output behaviour to e.g. inclide some conflict statements in the result, like git does it.
+     * Alter normal conflict output behaviour to e.g. inclide some conflict
+     * statements in the result, like git does it.
      */
     public Patch withConflictOutput(ConflictOutput conflictOutput) {
         this.conflictOutput = conflictOutput;
diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
index c1674108..a631b8df 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
@@ -13,6 +13,7 @@
 import java.util.List;
 import static java.util.stream.Collectors.joining;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 import org.junit.jupiter.api.Test;
@@ -133,7 +134,7 @@ public void testNewFileCreation() {
      * Issue 89
      */
     @Test
-    public void testChagngePosition() throws IOException {
+    public void testChangePosition() throws IOException {
         final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue89_patch.txt");
         final Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patchLines);
         List realRemoveListOne = Collections.singletonList(3);
@@ -191,4 +192,20 @@ private void verify(List origLines, List revLines,
             fail(e.getMessage());
         }
     }
+    
+    
+    @Test
+    public void testFailingPatchByException() throws IOException {
+        final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt");
+        final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt");
+        final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines);
+        
+        //make original not fitting
+        baseLines.set(40, baseLines.get(40) + " corrupted ");
+        
+        assertThrows(PatchFailedException.class, () -> DiffUtils.patch(baseLines, p));
+        
+        
+        
+    }
 }

From 8d38e1012eaefa19c4108e3e279c3a85fcc88bbc Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sat, 27 Feb 2021 01:32:45 +0100
Subject: [PATCH 014/107] first test with conflict output

---
 .../com/github/difflib/patch/PatchTest.java   | 45 +++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java
index 84208da9..311b6aac 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java
@@ -14,6 +14,7 @@
 import org.junit.jupiter.api.Test;
 
 import com.github.difflib.DiffUtils;
+import java.util.ArrayList;
 
 public class PatchTest {
 
@@ -78,4 +79,48 @@ public void testPatch_Serializable() throws IOException, ClassNotFoundException
         }
 
     }
+
+    @Test
+    public void testPatch_Change_withExceptionProcessor() {
+        final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd");
+        final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd");
+
+        final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to);
+
+        changeTest_from.set(2, "CDC");
+
+        patch.withConflictOutput(new ConflictOutput() {
+            @Override
+            public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) throws PatchFailedException {
+                if (result.size() > delta.getSource().getPosition()) {
+                    List orgData = new ArrayList<>();
+
+                    for (int i = 0; i < delta.getSource().size(); i++) {
+                        orgData.add(result.get(delta.getSource().getPosition()));
+                        result.remove(delta.getSource().getPosition());
+                    }
+
+                    orgData.add(0, "<<<<<< HEAD");
+                    orgData.add("======");
+                    orgData.addAll(delta.getSource().getLines());
+                    orgData.add(">>>>>>> PATCH");
+                    
+                    result.addAll(delta.getSource().getPosition(), orgData);
+
+                } else {
+                    throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+                }
+            }
+        });
+
+        try {
+            List data = DiffUtils.patch(changeTest_from, patch);
+            assertEquals(9, data.size());
+            
+            assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data);
+            
+        } catch (PatchFailedException e) {
+            fail(e.getMessage());
+        }
+    }
 }

From 2a1239b3a2f0a67bc1439232b6656695c8959392 Mon Sep 17 00:00:00 2001
From: Tobias Warneke 
Date: Sun, 14 Mar 2021 22:44:20 +0100
Subject: [PATCH 015/107] fixes #117

---
 CHANGELOG.md                                  |  3 +-
 .../java/com/github/difflib/patch/Chunk.java  | 16 +++---
 .../java/com/github/difflib/patch/Patch.java  | 24 +++++++++
 .../unifieddiff/UnifiedDiffReader.java        | 41 +++++++++++-----
 .../com/github/difflib/patch/PatchTest.java   | 25 +---------
 .../unifieddiff/UnifiedDiffReaderTest.java    | 37 +++++++++++++-
 .../unifieddiff/problem_diff_issue117.diff    | 49 +++++++++++++++++++
 7 files changed, 148 insertions(+), 47 deletions(-)
 create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue117.diff

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3bdb8014..ad47f32b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,8 +13,9 @@ This project uses a custom versioning scheme (and not [Semantic Versioning](http
 
 ### Changed
 
-* bugfixing on new UnifiedDiff reader / writer for multifile useage
+* bugfixing on new UnifiedDiff reader / writer for multifile usage
 * bugfix for wrong DiffRow type while transforming from a patch that removed a line in one changeset
+* introduced change position into UnifiedDiff reader
 
 ## [4.9]
 
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
index 03c60737..8ff19875 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
@@ -25,10 +25,11 @@
  * Holds the information about the part of text involved in the diff process
  *
  * 

- * Text is represented as Object[] because the diff engine is capable of handling more - * than plain ascci. In fact, arrays or lists of any type that implements - * {@link java.lang.Object#hashCode hashCode()} and {@link java.lang.Object#equals equals()} - * correctly can be subject to differencing using this library. + * Text is represented as Object[] because the diff engine is + * capable of handling more than plain ascci. In fact, arrays or lists of any + * type that implements {@link java.lang.Object#hashCode hashCode()} and + * {@link java.lang.Object#equals equals()} correctly can be subject to + * differencing using this library. *

* * @author CONFLICT_PRODUCES_MERGE_CONFLICT = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { + if (result.size() > delta.getSource().getPosition()) { + List orgData = new ArrayList<>(); + + for (int i = 0; i < delta.getSource().size(); i++) { + orgData.add(result.get(delta.getSource().getPosition())); + result.remove(delta.getSource().getPosition()); + } + + orgData.add(0, "<<<<<< HEAD"); + orgData.add("======"); + orgData.addAll(delta.getSource().getLines()); + orgData.add(">>>>>>> PATCH"); + + result.addAll(delta.getSource().getPosition(), orgData); + + } else { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + }; + private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; /** diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index 1a69cccc..8a1064fb 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -53,7 +53,7 @@ public final class UnifiedDiffReader { private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); - + private final UnifiedDiffLine DELETED_FILE_MODE = new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); @@ -94,9 +94,9 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (!CHUNK.validLine(line)) { initFileIfNecessary(); while (line != null && !CHUNK.validLine(line)) { - if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, + if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, + FROM_FILE, TO_FILE, + RENAME_FROM, RENAME_TO, NEW_FILE_MODE, DELETED_FILE_MODE)) { throw new UnifiedDiffParserException("expected file start line not found"); } @@ -117,8 +117,8 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { } } line = READER.readLine(); - - if ("\\ No newline at end of file".equals(line)) { + + if ("\\ No newline at end of file".equals(line)) { actualFile.setNoNewLineAtTheEndOfTheFile(true); line = READER.readLine(); } @@ -153,11 +153,12 @@ static String[] parseFileNames(String line) { private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName()); /** - * To parse a diff file use this method. + * To parse a diff file use this method. + * * @param stream This is the diff file data. * @return In a UnifiedDiff structure this diff file data is returned. * @throws IOException - * @throws UnifiedDiffParserException + * @throws UnifiedDiffParserException */ public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException { UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream))); @@ -198,27 +199,35 @@ private void processDiff(MatchResult match, String line) { actualFile.setToFile(fromTo[1]); actualFile.setDiffCommand(line); } - + private void processSimilarityIndex(MatchResult match, String line) { actualFile.setSimilarityIndex(Integer.valueOf(match.group(1))); } private List originalTxt = new ArrayList<>(); private List revisedTxt = new ArrayList<>(); + private List addLineIdxList = new ArrayList<>(); + private List delLineIdxList = new ArrayList<>(); private int old_ln; private int old_size; private int new_ln; private int new_size; + private int delLineIdx = 0; + private int addLineIdx = 0; private void finalizeChunk() { if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { actualFile.getPatch().addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, originalTxt), new Chunk<>( - new_ln - 1, revisedTxt))); + old_ln - 1, originalTxt, delLineIdxList), new Chunk<>( + new_ln - 1, revisedTxt, addLineIdxList))); old_ln = 0; new_ln = 0; originalTxt.clear(); revisedTxt.clear(); + addLineIdxList.clear(); + delLineIdxList.clear(); + delLineIdx = 0; + addLineIdx = 0; } } @@ -226,16 +235,22 @@ private void processNormalLine(MatchResult match, String line) { String cline = line.substring(1); originalTxt.add(cline); revisedTxt.add(cline); + delLineIdx++; + addLineIdx++; } private void processAddLine(MatchResult match, String line) { String cline = line.substring(1); revisedTxt.add(cline); + addLineIdx++; + addLineIdxList.add(new_ln - 1 + addLineIdx); } private void processDelLine(MatchResult match, String line) { String cline = line.substring(1); originalTxt.add(cline); + delLineIdx++; + delLineIdxList.add(old_ln - 1 + delLineIdx); } private void processChunk(MatchResult match, String chunkStart) { @@ -273,7 +288,7 @@ private void processToFile(MatchResult match, String line) { actualFile.setToFile(extractFileName(line)); actualFile.setToTimestamp(extractTimestamp(line)); } - + private void processRenameFrom(MatchResult match, String line) { actualFile.setRenameFrom(match.group(1)); } @@ -286,7 +301,7 @@ private void processNewFileMode(MatchResult match, String line) { //initFileIfNecessary(); actualFile.setNewFileMode(match.group(1)); } - + private void processDeletedFileMode(MatchResult match, String line) { //initFileIfNecessary(); actualFile.setDeletedFileMode(match.group(1)); diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java index 311b6aac..91d514a3 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.Test; import com.github.difflib.DiffUtils; -import java.util.ArrayList; public class PatchTest { @@ -89,29 +88,7 @@ public void testPatch_Change_withExceptionProcessor() { changeTest_from.set(2, "CDC"); - patch.withConflictOutput(new ConflictOutput() { - @Override - public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) throws PatchFailedException { - if (result.size() > delta.getSource().getPosition()) { - List orgData = new ArrayList<>(); - - for (int i = 0; i < delta.getSource().size(); i++) { - orgData.add(result.get(delta.getSource().getPosition())); - result.remove(delta.getSource().getPosition()); - } - - orgData.add(0, "<<<<<< HEAD"); - orgData.add("======"); - orgData.addAll(delta.getSource().getLines()); - orgData.add(">>>>>>> PATCH"); - - result.addAll(delta.getSource().getPosition(), orgData); - - } else { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - } - }); + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); try { List data = DiffUtils.patch(changeTest_from, patch); diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 38cf3133..38d26882 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -295,8 +295,41 @@ public void testParseIssue110() throws IOException { assertThat(file.getSimilarityIndex()).isEqualTo(87); assertThat(file.getRenameFrom()).isEqualTo("service-type-database/build-db.in"); assertThat(file.getRenameTo()).isEqualTo("service-type-database/build-db"); - + assertThat(file.getFromFile()).isEqualTo("service-type-database/build-db.in"); - assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); + assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); + } + + @Test + public void testParseIssue117() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue117.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getSource().getChangePosition()) + .containsExactly(24, 27); + assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getTarget().getChangePosition()) + .containsExactly(24, 27); + + assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getSource().getChangePosition()) + .containsExactly(64); + assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getTarget().getChangePosition()) + .containsExactly(64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74); + +// diff.getFiles().forEach(f -> { +// System.out.println("File: " + f.getFromFile()); +// f.getPatch().getDeltas().forEach(delta -> { +// +// System.out.println(delta); +// System.out.println("Source: "); +// System.out.println(delta.getSource().getPosition()); +// System.out.println(delta.getSource().getChangePosition()); +// +// System.out.println("Target: "); +// System.out.println(delta.getTarget().getPosition()); +// System.out.println(delta.getTarget().getChangePosition()); +// }); +// }); } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue117.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue117.diff new file mode 100644 index 00000000..4f19485a --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue117.diff @@ -0,0 +1,49 @@ +diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java +index a142548..b7e3549 100644 +--- a/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java ++++ b/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java +@@ -21,10 +21,10 @@ + final class StringUtils { + + /** +- * Replaces all opening an closing tags with < or >. ++ * Replaces all opening and closing tags with < or >. + * + * @param str +- * @return ++ * @return str with some HTML meta characters escaped. + */ + public static String htmlEntites(String str) { + return str.replace("<", "<").replace(">", ">"); +@@ -61,7 +61,17 @@ public static String wrapText(String line, int columnWidth) { + StringBuilder b = new StringBuilder(line); + + for (int count = 0; length > widthIndex; count++) { +- b.insert(widthIndex + delimiter * count, "
"); ++ int breakPoint = widthIndex + delimiter * count; ++ if (Character.isHighSurrogate(b.charAt(breakPoint - 1)) && ++ Character.isLowSurrogate(b.charAt(breakPoint))) { ++ // Shift a breakpoint that would split a supplemental code-point. ++ breakPoint += 1; ++ if (breakPoint == b.length()) { ++ // Break before instead of after if this is the last code-point. ++ breakPoint -= 2; ++ } ++ } ++ b.insert(breakPoint, "
"); + widthIndex += columnWidth; + } + +diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java +index c4b2acc..6867072 100644 +--- a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java ++++ b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java +@@ -49,6 +49,8 @@ public void testWrapText_String_int() { + assertEquals("te
st", StringUtils.wrapText("test", 2)); + assertEquals("tes
t", StringUtils.wrapText("test", 3)); + assertEquals("test", StringUtils.wrapText("test", 10)); ++ assertEquals(".\uD800\uDC01
.", StringUtils.wrapText(".\uD800\uDC01.", 2)); ++ assertEquals("..
\uD800\uDC01", StringUtils.wrapText("..\uD800\uDC01", 3)); + } + + @Test \ No newline at end of file From bf4ec94cc9c76fe9b8501e6deba3cb68629d7db4 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 14 Mar 2021 23:42:10 +0100 Subject: [PATCH 016/107] fixes #117 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad47f32b..fef10d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ This project uses a custom versioning scheme (and not [Semantic Versioning](http * bugfixing on new UnifiedDiff reader / writer for multifile usage * bugfix for wrong DiffRow type while transforming from a patch that removed a line in one changeset * introduced change position into UnifiedDiff reader +* introduced first version of conflict output possibility (like GIT merge conflict) + * moved verification to `AbstractDelta` + * introduced `ConflictOutput` to `Patch` to add optional behaviour to patch conflicts ## [4.9] From 91a42b2793472fccefa424d145e9aada08eedd7c Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Apr 2021 21:14:32 +0200 Subject: [PATCH 017/107] fixes #120 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0f364f1..93899e85 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java- ## Examples -Look [here](https://github.com/wumpz/java-diff-utils/wiki) to find more helpful informations and examples. +Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful informations and examples. These two outputs are generated using this java-diff-utils. The source code can also be found at the *Examples* page: From 94674a0ad70563083714c1242c154cb0f89fad41 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Apr 2021 22:00:20 +0200 Subject: [PATCH 018/107] fixes #119 --- .../difflib/GenerateUnifiedDiffTest.java | 14 ++++++++++++- .../difflib/text/DiffRowGeneratorTest.java | 21 +++++++++++++++++++ .../difflib/text/issue_119_original.txt | 4 ++++ .../github/difflib/text/issue_119_revised.txt | 4 ++++ 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_original.txt create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_revised.txt diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java index a631b8df..e13d41aa 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.List; import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -204,8 +205,19 @@ public void testFailingPatchByException() throws IOException { baseLines.set(40, baseLines.get(40) + " corrupted "); assertThrows(PatchFailedException.class, () -> DiffUtils.patch(baseLines, p)); + } + + @Test + public void testWrongContextLength() throws IOException { + List original = fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_original.txt"); + List revised = fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_revised.txt"); + + Patch patch = DiffUtils.diff(original, revised); + List udiff = UnifiedDiffUtils.generateUnifiedDiff("a/$filename", "b/$filename", + original, patch, 3); + //System.out.println(udiff.stream().collect(joining("\n"))); - + assertThat(udiff).contains("@@ -1,4 +1,4 @@"); } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 389ba1f1..d9a3c4ff 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -662,4 +662,25 @@ public void testCorrectChangeIssue114_2() throws IOException { assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]"); } + + @Test + public void testIssue119WrongContextLength() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_original.txt")).collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_revised.txt")).collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList(original.split("\n")), + Arrays.asList(revised.split("\n"))); + + rows.stream() + .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) + .forEach(System.out::println); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_original.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_original.txt new file mode 100644 index 00000000..2eea1b48 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_original.txt @@ -0,0 +1,4 @@ +const world: string = 'world', + p: number | undefined = 42; + +console.log(`Hello, ${world}!`); \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_revised.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_revised.txt new file mode 100644 index 00000000..522c44e8 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_119_revised.txt @@ -0,0 +1,4 @@ +const world: string = 'world'; +const p: number | undefined = 42; + +console.log(`Hello, ${world}!`); \ No newline at end of file From 5a11752126883c963091e2f029e446c26d2e3d7e Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Apr 2021 23:21:51 +0200 Subject: [PATCH 019/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.10 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index f3b2c1ad..e62e3c02 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.10-SNAPSHOT + 4.10 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 8f8fd0b5..6ae7e5f1 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.10-SNAPSHOT + 4.10 UTF-8 diff --git a/pom.xml b/pom.xml index 7d02aa26..8b0ccd32 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.10-SNAPSHOT + 4.10 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.10 GitHub Issues From f03df6cf3894f9c8741c8c12372d441bf59e3b32 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Apr 2021 23:21:52 +0200 Subject: [PATCH 020/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index e62e3c02..ae872975 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.10 + 4.11-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 6ae7e5f1..734f3d5f 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.10 + 4.11-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 8b0ccd32..50dd9510 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.10 + 4.11-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.10 + HEAD GitHub Issues From d384ab6178998d9d2dab6d77834693ed367b8b44 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Apr 2021 23:33:53 +0200 Subject: [PATCH 021/107] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fef10d30..7662f7e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ This project uses a custom versioning scheme (and not [Semantic Versioning](http ### Changed +## [4.11] + +### Changed + ## [4.10] ### Changed From 3d5343c2830cdc115c9062a391899d2817153283 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 5 May 2021 22:14:56 +0200 Subject: [PATCH 022/107] fixes #107 refinement --- .../unifieddiff/UnifiedDiffReader.java | 15 +- .../unifieddiff/UnifiedDiffReaderTest.java | 34 +- .../unifieddiff/problem_diff_issue107_3.diff | 24 + .../unifieddiff/problem_diff_issue107_4.diff | 2963 +++++++++++++++++ .../unifieddiff/problem_diff_issue107_5.diff | 835 +++++ 5 files changed, 3866 insertions(+), 5 deletions(-) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_3.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_4.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_5.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index 8a1064fb..da75e6ff 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -106,6 +106,8 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (line != null) { processLine(line, CHUNK); while ((line = READER.readLine()) != null) { + line = checkForNoNewLineAtTheEndOfTheFile(line); + if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { throw new UnifiedDiffParserException("expected data line not found"); } @@ -118,10 +120,7 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { } line = READER.readLine(); - if ("\\ No newline at end of file".equals(line)) { - actualFile.setNoNewLineAtTheEndOfTheFile(true); - line = READER.readLine(); - } + line = checkForNoNewLineAtTheEndOfTheFile(line); } if (line == null || (line.startsWith("--") && !line.startsWith("---"))) { break; @@ -142,6 +141,14 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { return data; } + private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException { + if ("\\ No newline at end of file".equals(line)) { + actualFile.setNoNewLineAtTheEndOfTheFile(true); + return READER.readLine(); + } + return line; + } + static String[] parseFileNames(String line) { String[] split = line.split(" "); return new String[]{ diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 38d26882..fdc80fd8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -277,12 +277,44 @@ public void testParseIssue107_2() throws IOException { assertThat(diff.getFiles().size()).isEqualTo(2); - final UnifiedDiffFile file = diff.getFiles().get(0); UnifiedDiffFile file1 = diff.getFiles().get(0); assertThat(file1.getFromFile()).isEqualTo("Main.java"); assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); } + + @Test + public void testParseIssue107_3() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_3.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(1); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Billion laughs attack.md"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + + } + + @Test + public void testParseIssue107_4() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_4.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(27); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("README.md"); + } + + @Test + public void testParseIssue107_5() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_5.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } @Test public void testParseIssue110() throws IOException { diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_3.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_3.diff new file mode 100644 index 00000000..181b800d --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_3.diff @@ -0,0 +1,24 @@ +diff -r 4d1de0a006b7 -r ace1482360cb Billion laughs attack.md +--- a/Billion laughs attack.md Mon Feb 24 13:37:17 2020 +0000 ++++ b/Billion laughs attack.md Mon Feb 24 14:22:50 2020 +0000 +@@ -11,14 +11,12 @@ + + *The problem is*: when you stay with the mouth open and eyes closed then they may throw trash and you get sick. + +- +-SnakeYAML Engine [has a way to restrict the amount of aliases for collections](https://bitbucket.org/asomov/snakeyaml-engine/src/default/src/test/java/org/snakeyaml/engine/usecases/references/ReferencesTest.java) to fail early without allocation too much resources. ++SnakeYAML 1.26+ [has a way to restrict the amount of aliases for collections](https://bitbucket.org/asomov/snakeyaml/src/default/src/test/java/org/yaml/snakeyaml/issues/issue377/ReferencesTest.java) to fail early without allocation too much resources. + + # Solution # + +-1. If the YAML is not coming from untrusted source (it is merely a configuration file) then it is a false positive. Just ignore it. The quality of NVD database is very low and contains tons of issues which appear to be false positives. +-2. Migrate to [SnakeYAML Engine](https://bitbucket.org/asomov/snakeyaml-engine/src/default/). It has a configuration option to restrict aliases for collections (the aliases for scalars cannot grow and they are not restricted) +-3. Check how it is done in SnakeYAML Engine and build your own SnakeYAML version with the same change. +-4. Read the YAML and check its quality before giving the document to SnakeYAML (count `*` and `&` for instance) ++1. Update to SnakeYAML 1.26. It has a configuration option to restrict aliases for collections (the aliases for scalars cannot grow and they are not restricted) ++2. If the YAML is not coming from untrusted source (it is merely a configuration file) then it is a false positive. Just ignore it. The quality of NVD database is very low and contains tons of issues which appear to be false positives. ++3. Read the YAML and check its quality before giving the document to SnakeYAML (count `*` and `&` for instance) + +-Enjoy. +\ No newline at end of file ++Enjoy. diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_4.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_4.diff new file mode 100644 index 00000000..f2dfd8f0 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_4.diff @@ -0,0 +1,2963 @@ +diff --git a/.travis/travis_configure_ssl.sh b/.travis/travis_configure_ssl.sh +index c01affe09c..e07aa70da5 100755 +--- a/.travis/travis_configure_ssl.sh ++++ b/.travis/travis_configure_ssl.sh +@@ -22,16 +22,7 @@ set_conf_property "ssl_cert_file" "server.crt" + set_conf_property "ssl_key_file" "server.key" + set_conf_property "ssl_ca_file" "root.crt" + +-enable_ssl_property "testsinglecertfactory" +-enable_ssl_property "sslhostnossl9" +-enable_ssl_property "sslhostgh9" +-enable_ssl_property "sslhostbh9" +-enable_ssl_property "sslhostsslgh9" +-enable_ssl_property "sslhostsslbh9" +-enable_ssl_property "sslhostsslcertgh9" +-enable_ssl_property "sslhostsslcertbh9" +-enable_ssl_property "sslcertgh9" +-enable_ssl_property "sslcertbh9" ++enable_ssl_property "enable_ssl_tests" + + PG_DATA_DIR="/etc/postgresql/${PG_VERSION}/main/" + sudo cp certdir/server/pg_hba.conf "/etc/postgresql/${PG_VERSION}/main/pg_hba.conf" +diff --git a/README.md b/README.md +index e018dcfffb..ce63ac21bc 100644 +--- a/README.md ++++ b/README.md +@@ -111,7 +111,7 @@ In addition to the standard connection parameters the driver supports a number o + | password | String | null | The database user's password. | + | ssl | Boolean | false | Control use of SSL (true value causes SSL to be required) | + | sslfactory | String | null | Provide a SSLSocketFactory class when using SSL. | +-| sslfactoryarg | String | null | Argument forwarded to constructor of SSLSocketFactory class. | ++| sslfactoryarg (deprecated) | String | null | Argument forwarded to constructor of SSLSocketFactory class. | + | sslmode | String | null | Parameter governing the use of SSL. | + | sslcert | String | null | The location of the client's SSL certificate | + | sslkey | String | null | The location of the client's PKCS#8 SSL key | +@@ -144,7 +144,7 @@ In addition to the standard connection parameters the driver supports a number o + | hostRecheckSeconds | Integer | 10 | Specifies period (seconds) after which the host status is checked again in case it has changed | + | loadBalanceHosts | Boolean | false | If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates | + | socketFactory | String | null | Specify a socket factory for socket creation | +-| socketFactoryArg | String | null | Argument forwarded to constructor of SocketFactory class. | ++| socketFactoryArg (deprecated) | String | null | Argument forwarded to constructor of SocketFactory class. | + | autosave | String | never | Specifies what the driver should do if a query fails, possible values: always, never, conservative | + | preferQueryMode | String | extended | Specifies which mode is used to execute queries to database, possible values: extended, extendedForPrepared, extendedCacheEverything, simple | + | reWriteBatchedInserts | Boolean | false | Enable optimization to rewrite and collapse compatible INSERT statements that are batched. | +diff --git a/build.properties b/build.properties +index 91e4cd10a7..548d2aaf94 100644 +--- a/build.properties ++++ b/build.properties +@@ -19,3 +19,4 @@ preparethreshold=5 + loggerLevel=OFF + loggerFile=target/pgjdbc-tests.log + protocolVersion=0 ++sslpassword=sslpwd +diff --git a/certdir/README b/certdir/README +deleted file mode 100644 +index 8bbbf5b1c3..0000000000 +--- a/certdir/README ++++ /dev/null +@@ -1,57 +0,0 @@ +- +-To run the SSL tests, the following properties are used: +- +-certdir: directory where the certificates and keys are store +- +-ssl<8|9>: a connection string to the appropriate database +-TYPE is the TYPE or METHOD field from pg_hba.conf that is: host, hostnossl, +-hostssl and the special types hostsslcert, that corresponds +-to a hostssl type with clientcert=1 and cert that corresponds +-to a hostssl type with cert authentication. 'gh' means, the server certificate +-matches the hostname (good hostname), 'bh' means it is not (bad +-hostname). It can be simulated with a single database, if two names +-can be used i.e. localhost and 127.0.0.1. ssloff points to a database, +-where ssl is off. The last number is the server major version +- +-For each connection, the following files should be placed into certdir: +-goodclient.crt, badclient.crt, goodclient.pk8, badclient.pk8, goodroot.crt, badroot.crt +-optionally prefixed by the value of sslprefix property, if +-different files are necessary for different connect strings. +- +-This directory contains example certificates generated by the following +-commands: +- +-openssl req -x509 -newkey rsa:1024 -days 3650 -keyout goodclient.key -out goodclient.crt +-#Common name is test, password is sslpwd +- +-openssl req -x509 -newkey rsa:1024 -days 3650 -keyout badclient.key -out badclient.crt +-#Common name is test, password is sslpwd +- +-openssl req -x509 -newkey rsa:1024 -days 3650 -nodes -keyout badroot.key -out badroot.crt +-#Common name is localhost +-rm badroot.key +- +-openssl pkcs8 -topk8 -in goodclient.key -out goodclient.pk8 -outform DER -v1 PBE-MD5-DES +-openssl pkcs8 -topk8 -in badclient.key -out badclient.pk8 -outform DER -v1 PBE-MD5-DES +-cp goodclient.crt server/root.crt +-cd server +-openssl req -x509 -newkey rsa:1024 -nodes -days 3650 -keyout server.key -out server.crt +-cp server.crt ../goodroot.crt +-#Common name is localhost, no password +- +-The subdirectory server contains what should be copied to the PGDATA directory. +-If you do not overwrite the pg_hba.conf then remember to comment out all lines +-starting with "host all". +- +-For the tests the sslinfo module must be installed into every database. +-The ssl=on must be set in postgresql.conf +- +-The following command creates the databases and installs the sslinfo module. +- +-for db in hostssldb hostnossldb certdb hostsslcertdb; do +- createdb $db +- psql $db -c "create extension sslinfo" +-done +- +-The username for connecting to postgres as specified in build.local.properties tests has to be "test". +- +diff --git a/certdir/README.md b/certdir/README.md +new file mode 100644 +index 0000000000..ff041e3c49 +--- /dev/null ++++ b/certdir/README.md +@@ -0,0 +1,44 @@ ++To run the SSL tests, the following properties are used: ++ ++* certdir: directory where the certificates and keys are store ++* enable_ssl_tests: enables SSL tests ++ ++In order to configure PostgreSQL for SSL tests, the following changes should be applied: ++ ++* Copy server/server.crt, server/server.key, and server/root.crt to $PGDATA directory ++* In $PGDATA directory: chmod 0600 server.crt server.key root.crt ++* Set ssl=on in postgresql.conf ++* Set ssl_cert_file=server.crt in postgresql.conf ++* Set ssl_key_file=server.key in postgresql.conf ++* Set ssl_ca_file=root.crt in postgresql.conf ++* Add databases for SSL tests. Note: sslinfo extension is used in tests to tell if connection is using SSL or not ++ ++ for db in hostssldb hostnossldb certdb hostsslcertdb; do ++ createdb $db ++ psql $db -c "create extension sslinfo" ++ done ++* Add test databases to pg_hba.conf. If you do not overwrite the pg_hba.conf then remember to comment out all lines ++ starting with "host all". ++* Uncomment enable_ssl_tests=true in ssltests.properties ++* The username for connecting to postgres as specified in build.local.properties tests has to be "test". ++ ++This directory contains example certificates generated by the following ++commands: ++ ++openssl req -x509 -newkey rsa:1024 -days 3650 -keyout goodclient.key -out goodclient.crt ++#Common name is test, password is sslpwd ++ ++openssl req -x509 -newkey rsa:1024 -days 3650 -keyout badclient.key -out badclient.crt ++#Common name is test, password is sslpwd ++ ++openssl req -x509 -newkey rsa:1024 -days 3650 -nodes -keyout badroot.key -out badroot.crt ++#Common name is localhost ++rm badroot.key ++ ++openssl pkcs8 -topk8 -in goodclient.key -out goodclient.pk8 -outform DER -v1 PBE-MD5-DES ++openssl pkcs8 -topk8 -in badclient.key -out badclient.pk8 -outform DER -v1 PBE-MD5-DES ++cp goodclient.crt server/root.crt ++cd server ++openssl req -x509 -newkey rsa:1024 -nodes -days 3650 -keyout server.key -out server.crt ++cp server.crt ../goodroot.crt ++#Common name is localhost, no password +diff --git a/docs/documentation/head/connect.md b/docs/documentation/head/connect.md +index 9763b76d8d..9881060512 100644 +--- a/docs/documentation/head/connect.md ++++ b/docs/documentation/head/connect.md +@@ -88,7 +88,7 @@ Connection conn = DriverManager.getConnection(url); + establishing a SSL connection. For more information see the section + called [“Custom SSLSocketFactory”](ssl-factory.html). + +-* **sslfactoryarg** = String ++* **sslfactoryarg** (deprecated) = String + + This value is an optional argument to the constructor of the sslfactory + class provided above. For more information see the section called [“Custom SSLSocketFactory”](ssl-factory.html). +@@ -408,7 +408,7 @@ Connection conn = DriverManager.getConnection(url); + This class must have a zero argument constructor or a single argument constructor taking a String argument. + This argument may optionally be supplied by `socketFactoryArg`. + +-* **socketFactoryArg** = String ++* **socketFactoryArg** (deprecated) = String + + This value is an optional argument to the constructor of the socket factory + class provided above. +diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java +index e56e05eb73..4afefe2356 100644 +--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java ++++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java +@@ -173,17 +173,18 @@ + "Enable optimization that disables column name sanitiser"), + + /** +- * Control use of SSL (any non-null value causes SSL to be required). ++ * Control use of SSL: empty or {@code true} values imply {@code sslmode==verify-full} + */ + SSL("ssl", null, "Control use of SSL (any non-null value causes SSL to be required)"), + + /** +- * Parameter governing the use of SSL. The allowed values are {@code require}, {@code verify-ca}, +- * {@code verify-full}, or {@code disable} ({@code allow} and {@code prefer} are not implemented) +- * If not set, the {@code ssl} property may be checked to enable SSL mode. ++ * Parameter governing the use of SSL. The allowed values are {@code disable}, {@code allow}, ++ * {@code prefer}, {@code require}, {@code verify-ca}, {@code verify-full}. ++ * If {@code ssl} property is empty or set to {@code true} it implies {@code verify-full}. ++ * Default mode is "require" + */ +- SSL_MODE("sslmode", null, "Parameter governing the use of SSL",false, +- "disable", "require", "verify-ca", "verify-full"), ++ SSL_MODE("sslmode", null, "Parameter governing the use of SSL", false, ++ "disable", "allow", "prefer", "require", "verify-ca", "verify-full"), + + /** + * Classname of the SSL Factory to use (instance of {@code javax.net.ssl.SSLSocketFactory}). +@@ -192,7 +193,9 @@ + + /** + * The String argument to give to the constructor of the SSL Factory. ++ * @deprecated use {@code ..Factory(Properties)} constructor. + */ ++ @Deprecated + SSL_FACTORY_ARG("sslfactoryarg", null, + "Argument forwarded to constructor of SSLSocketFactory class."), + +@@ -279,7 +282,9 @@ + + /** + * The String argument to give to the constructor of the Socket Factory. ++ * @deprecated use {@code ..Factory(Properties)} constructor. + */ ++ @Deprecated + SOCKET_FACTORY_ARG("socketFactoryArg", null, + "Argument forwarded to constructor of SocketFactory class."), + +diff --git a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java +index 63065730a0..6f0f3a2bea 100644 +--- a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java ++++ b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java +@@ -21,6 +21,7 @@ + import java.io.Writer; + import java.net.InetSocketAddress; + import java.net.Socket; ++import java.net.SocketTimeoutException; + import java.sql.SQLException; + import javax.net.SocketFactory; + +@@ -109,7 +110,19 @@ public SocketFactory getSocketFactory() { + * @throws IOException if something wrong happens + */ + public boolean hasMessagePending() throws IOException { +- return pg_input.available() > 0 || connection.getInputStream().available() > 0; ++ if (pg_input.available() > 0) { ++ return true; ++ } ++ // In certain cases, available returns 0, yet there are bytes ++ int soTimeout = getNetworkTimeout(); ++ setNetworkTimeout(1); ++ try { ++ return pg_input.peek() != -1; ++ } catch (SocketTimeoutException e) { ++ return false; ++ } finally { ++ setNetworkTimeout(soTimeout); ++ } + } + + /** +diff --git a/pgjdbc/src/main/java/org/postgresql/core/SocketFactoryFactory.java b/pgjdbc/src/main/java/org/postgresql/core/SocketFactoryFactory.java +index e7cfd3262d..09efa75f07 100644 +--- a/pgjdbc/src/main/java/org/postgresql/core/SocketFactoryFactory.java ++++ b/pgjdbc/src/main/java/org/postgresql/core/SocketFactoryFactory.java +@@ -6,6 +6,7 @@ + package org.postgresql.core; + + import org.postgresql.PGProperty; ++import org.postgresql.ssl.LibPQFactory; + import org.postgresql.util.GT; + import org.postgresql.util.ObjectFactory; + import org.postgresql.util.PSQLException; +@@ -14,6 +15,7 @@ + import java.util.Properties; + + import javax.net.SocketFactory; ++import javax.net.ssl.SSLSocketFactory; + + /** + * Instantiates {@link SocketFactory} based on the {@link PGProperty#SOCKET_FACTORY}. +@@ -44,4 +46,28 @@ public static SocketFactory getSocketFactory(Properties info) throws PSQLExcepti + } + } + ++ /** ++ * Instantiates {@link SSLSocketFactory} based on the {@link PGProperty#SSL_FACTORY}. ++ * ++ * @param info connection properties ++ * @return SSL socket factory ++ * @throws PSQLException if something goes wrong ++ */ ++ public static SSLSocketFactory getSslSocketFactory(Properties info) throws PSQLException { ++ String classname = PGProperty.SSL_FACTORY.get(info); ++ if (classname == null ++ || "org.postgresql.ssl.jdbc4.LibPQFactory".equals(classname) ++ || "org.postgresql.ssl.LibPQFactory".equals(classname)) { ++ return new LibPQFactory(info); ++ } ++ try { ++ return (SSLSocketFactory) ObjectFactory.instantiate(classname, info, true, ++ PGProperty.SSL_FACTORY_ARG.get(info)); ++ } catch (Exception e) { ++ throw new PSQLException( ++ GT.tr("The SSLSocketFactory class provided {0} could not be instantiated.", classname), ++ PSQLState.CONNECTION_FAILURE, e); ++ } ++ } ++ + } +diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java +index 749af95889..c83ce34c59 100644 +--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java ++++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java +@@ -21,6 +21,7 @@ + import org.postgresql.hostchooser.HostChooserFactory; + import org.postgresql.hostchooser.HostRequirement; + import org.postgresql.hostchooser.HostStatus; ++import org.postgresql.jdbc.SslMode; + import org.postgresql.sspi.ISSPIClient; + import org.postgresql.util.GT; + import org.postgresql.util.HostSpec; +@@ -42,7 +43,6 @@ + import java.util.logging.Level; + import java.util.logging.LogRecord; + import java.util.logging.Logger; +- + import javax.net.SocketFactory; + + /** +@@ -82,32 +82,71 @@ private ISSPIClient createSSPI(PGStream pgStream, + } + } + +- @Override +- public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, String database, +- Properties info) throws SQLException { +- // Extract interesting values from the info properties: +- // - the SSL setting +- boolean requireSSL; +- boolean trySSL; +- String sslmode = PGProperty.SSL_MODE.get(info); +- if (sslmode == null) { // Fall back to the ssl property +- // assume "true" if the property is set but empty +- requireSSL = trySSL = PGProperty.SSL.getBoolean(info) || "".equals(PGProperty.SSL.get(info)); +- } else { +- if ("disable".equals(sslmode)) { +- requireSSL = trySSL = false; +- } else if ("require".equals(sslmode) || "verify-ca".equals(sslmode) +- || "verify-full".equals(sslmode)) { +- requireSSL = trySSL = true; ++ private PGStream tryConnect(String user, String database, ++ Properties info, SocketFactory socketFactory, HostSpec hostSpec, ++ SslMode sslMode) ++ throws SQLException, IOException { ++ int connectTimeout = PGProperty.CONNECT_TIMEOUT.getInt(info) * 1000; ++ ++ PGStream newStream = new PGStream(socketFactory, hostSpec, connectTimeout); ++ ++ // Construct and send an ssl startup packet if requested. ++ newStream = enableSSL(newStream, sslMode, info, connectTimeout); ++ ++ // Set the socket timeout if the "socketTimeout" property has been set. ++ int socketTimeout = PGProperty.SOCKET_TIMEOUT.getInt(info); ++ if (socketTimeout > 0) { ++ newStream.getSocket().setSoTimeout(socketTimeout * 1000); ++ } ++ ++ // Enable TCP keep-alive probe if required. ++ boolean requireTCPKeepAlive = PGProperty.TCP_KEEP_ALIVE.getBoolean(info); ++ newStream.getSocket().setKeepAlive(requireTCPKeepAlive); ++ ++ // Try to set SO_SNDBUF and SO_RECVBUF socket options, if requested. ++ // If receiveBufferSize and send_buffer_size are set to a value greater ++ // than 0, adjust. -1 means use the system default, 0 is ignored since not ++ // supported. ++ ++ // Set SO_RECVBUF read buffer size ++ int receiveBufferSize = PGProperty.RECEIVE_BUFFER_SIZE.getInt(info); ++ if (receiveBufferSize > -1) { ++ // value of 0 not a valid buffer size value ++ if (receiveBufferSize > 0) { ++ newStream.getSocket().setReceiveBufferSize(receiveBufferSize); + } else { +- throw new PSQLException(GT.tr("Invalid sslmode value: {0}", sslmode), +- PSQLState.CONNECTION_UNABLE_TO_CONNECT); ++ LOGGER.log(Level.WARNING, "Ignore invalid value for receiveBufferSize: {0}", receiveBufferSize); + } + } + +- boolean requireTCPKeepAlive = PGProperty.TCP_KEEP_ALIVE.getBoolean(info); ++ // Set SO_SNDBUF write buffer size ++ int sendBufferSize = PGProperty.SEND_BUFFER_SIZE.getInt(info); ++ if (sendBufferSize > -1) { ++ if (sendBufferSize > 0) { ++ newStream.getSocket().setSendBufferSize(sendBufferSize); ++ } else { ++ LOGGER.log(Level.WARNING, "Ignore invalid value for sendBufferSize: {0}", sendBufferSize); ++ } ++ } + +- int connectTimeout = PGProperty.CONNECT_TIMEOUT.getInt(info) * 1000; ++ if (LOGGER.isLoggable(Level.FINE)) { ++ LOGGER.log(Level.FINE, "Receive Buffer Size is {0}", newStream.getSocket().getReceiveBufferSize()); ++ LOGGER.log(Level.FINE, "Send Buffer Size is {0}", newStream.getSocket().getSendBufferSize()); ++ } ++ ++ List paramList = getParametersForStartup(user, database, info); ++ sendStartupPacket(newStream, paramList); ++ ++ // Do authentication (until AuthenticationOk). ++ doAuthentication(newStream, hostSpec.getHost(), user, info); ++ ++ return newStream; ++ } ++ ++ @Override ++ public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, String database, ++ Properties info) throws SQLException { ++ SslMode sslMode = SslMode.of(info); + + HostRequirement targetServerType; + String targetServerTypeStr = PGProperty.TARGET_SERVER_TYPE.get(info); +@@ -149,59 +188,62 @@ public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, Strin + + PGStream newStream = null; + try { +- newStream = new PGStream(socketFactory, hostSpec, connectTimeout); +- +- // Construct and send an ssl startup packet if requested. +- if (trySSL) { +- newStream = enableSSL(newStream, requireSSL, info, connectTimeout); +- } +- +- // Set the socket timeout if the "socketTimeout" property has been set. +- int socketTimeout = PGProperty.SOCKET_TIMEOUT.getInt(info); +- if (socketTimeout > 0) { +- newStream.getSocket().setSoTimeout(socketTimeout * 1000); +- } +- +- // Enable TCP keep-alive probe if required. +- newStream.getSocket().setKeepAlive(requireTCPKeepAlive); +- +- // Try to set SO_SNDBUF and SO_RECVBUF socket options, if requested. +- // If receiveBufferSize and send_buffer_size are set to a value greater +- // than 0, adjust. -1 means use the system default, 0 is ignored since not +- // supported. +- +- // Set SO_RECVBUF read buffer size +- int receiveBufferSize = PGProperty.RECEIVE_BUFFER_SIZE.getInt(info); +- if (receiveBufferSize > -1) { +- // value of 0 not a valid buffer size value +- if (receiveBufferSize > 0) { +- newStream.getSocket().setReceiveBufferSize(receiveBufferSize); +- } else { +- LOGGER.log(Level.WARNING, "Ignore invalid value for receiveBufferSize: {0}", receiveBufferSize); +- } +- } ++ try { ++ newStream = tryConnect(user, database, info, socketFactory, hostSpec, sslMode); ++ } catch (SQLException e) { ++ if (sslMode == SslMode.PREFER ++ && PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(e.getSQLState())) { ++ // Try non-SSL connection to cover case like "non-ssl only db" ++ // Note: PREFER allows loss of encryption, so no significant harm is made ++ Throwable ex = null; ++ try { ++ newStream = ++ tryConnect(user, database, info, socketFactory, hostSpec, SslMode.DISABLE); ++ LOGGER.log(Level.FINE, "Downgraded to non-encrypted connection for host {0}", ++ hostSpec); ++ } catch (SQLException ee) { ++ ex = ee; ++ } catch (IOException ee) { ++ ex = ee; // Can't use multi-catch in Java 6 :( ++ } ++ if (ex != null) { ++ log(Level.FINE, "sslMode==PREFER, however non-SSL connection failed as well", ex); ++ // non-SSL failed as well, so re-throw original exception ++ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1" ++ // Add non-SSL exception as suppressed ++ e.addSuppressed(ex); ++ //#endif ++ throw e; ++ } ++ } else if (sslMode == SslMode.ALLOW ++ && PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(e.getSQLState())) { ++ // Try using SSL ++ Throwable ex = null; ++ try { ++ newStream = ++ tryConnect(user, database, info, socketFactory, hostSpec, SslMode.REQUIRE); ++ LOGGER.log(Level.FINE, "Upgraded to encrypted connection for host {0}", ++ hostSpec); ++ } catch (SQLException ee) { ++ ex = ee; ++ } catch (IOException ee) { ++ ex = ee; // Can't use multi-catch in Java 6 :( ++ } ++ if (ex != null) { ++ log(Level.FINE, "sslMode==ALLOW, however SSL connection failed as well", ex); ++ // non-SSL failed as well, so re-throw original exception ++ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1" ++ // Add SSL exception as suppressed ++ e.addSuppressed(ex); ++ //#endif ++ throw e; ++ } + +- // Set SO_SNDBUF write buffer size +- int sendBufferSize = PGProperty.SEND_BUFFER_SIZE.getInt(info); +- if (sendBufferSize > -1) { +- if (sendBufferSize > 0) { +- newStream.getSocket().setSendBufferSize(sendBufferSize); + } else { +- LOGGER.log(Level.WARNING, "Ignore invalid value for sendBufferSize: {0}", sendBufferSize); ++ throw e; + } + } + +- if (LOGGER.isLoggable(Level.FINE)) { +- LOGGER.log(Level.FINE, "Receive Buffer Size is {0}", newStream.getSocket().getReceiveBufferSize()); +- LOGGER.log(Level.FINE, "Send Buffer Size is {0}", newStream.getSocket().getSendBufferSize()); +- } +- +- List paramList = getParametersForStartup(user, database, info); +- sendStartupPacket(newStream, paramList); +- +- // Do authentication (until AuthenticationOk). +- doAuthentication(newStream, hostSpec.getHost(), user, info); +- + int cancelSignalTimeout = PGProperty.CANCEL_SIGNAL_TIMEOUT.getInt(info) * 1000; + + // Do final startup. +@@ -230,8 +272,8 @@ public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, Strin + // we trap this an return a more meaningful message for the end user + GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail); + knownStates.put(hostSpec, HostStatus.ConnectFail); +- log(Level.FINE, "ConnectException occurred while connecting to {0}", cex, hostSpec); + if (hostIter.hasNext()) { ++ log(Level.FINE, "ConnectException occurred while connecting to {0}", cex, hostSpec); + // still more addresses to try + continue; + } +@@ -242,8 +284,8 @@ public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, Strin + closeStream(newStream); + GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail); + knownStates.put(hostSpec, HostStatus.ConnectFail); +- log(Level.FINE, "IOException occurred while connecting to {0}", ioe, hostSpec); + if (hostIter.hasNext()) { ++ log(Level.FINE, "IOException occurred while connecting to {0}", ioe, hostSpec); + // still more addresses to try + continue; + } +@@ -251,10 +293,10 @@ public QueryExecutor openConnectionImpl(HostSpec[] hostSpecs, String user, Strin + PSQLState.CONNECTION_UNABLE_TO_CONNECT, ioe); + } catch (SQLException se) { + closeStream(newStream); +- log(Level.FINE, "SQLException occurred while connecting to {0}", se, hostSpec); + GlobalHostStatusTracker.reportHostStatus(hostSpec, HostStatus.ConnectFail); + knownStates.put(hostSpec, HostStatus.ConnectFail); + if (hostIter.hasNext()) { ++ log(Level.FINE, "SQLException occurred while connecting to {0}", se, hostSpec); + // still more addresses to try + continue; + } +@@ -340,8 +382,17 @@ private static String createPostgresTimeZone() { + return start + tz.substring(4); + } + +- private PGStream enableSSL(PGStream pgStream, boolean requireSSL, Properties info, int connectTimeout) +- throws IOException, SQLException { ++ private PGStream enableSSL(PGStream pgStream, SslMode sslMode, Properties info, ++ int connectTimeout) ++ throws IOException, PSQLException { ++ if (sslMode == SslMode.DISABLE) { ++ return pgStream; ++ } ++ if (sslMode == SslMode.ALLOW) { ++ // Allow ==> start with plaintext, use encryption if required by server ++ return pgStream; ++ } ++ + LOGGER.log(Level.FINEST, " FE=> SSLRequest"); + + // Send SSL request packet +@@ -357,7 +408,7 @@ private PGStream enableSSL(PGStream pgStream, boolean requireSSL, Properties inf + LOGGER.log(Level.FINEST, " <=BE SSLError"); + + // Server doesn't even know about the SSL handshake protocol +- if (requireSSL) { ++ if (sslMode.requireEncryption()) { + throw new PSQLException(GT.tr("The server does not support SSL."), + PSQLState.CONNECTION_REJECTED); + } +@@ -370,7 +421,7 @@ private PGStream enableSSL(PGStream pgStream, boolean requireSSL, Properties inf + LOGGER.log(Level.FINEST, " <=BE SSLRefused"); + + // Server does not support ssl +- if (requireSSL) { ++ if (sslMode.requireEncryption()) { + throw new PSQLException(GT.tr("The server does not support SSL."), + PSQLState.CONNECTION_REJECTED); + } +@@ -608,14 +659,19 @@ private void doAuthentication(PGStream pgStream, String host, String user, Prope + scramAuthenticator = new org.postgresql.jre8.sasl.ScramAuthenticator(user, password, pgStream); + scramAuthenticator.processServerMechanismsAndInit(); + scramAuthenticator.sendScramClientFirstMessage(); +- //#else +- if (true) { ++ // This works as follows: ++ // 1. When tests is run from IDE, it is assumed SCRAM library is on the classpath ++ // 2. In regular build for Java < 8 this `if` is deactivated and the code always throws ++ if (false) { ++ //#else + throw new PSQLException(GT.tr( + "SCRAM authentication is not supported by this driver. You need JDK >= 8 and pgjdbc >= 42.2.0 (not \".jre\" versions)", + areq), PSQLState.CONNECTION_REJECTED); ++ //#endif ++ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" + } +- //#endif + break; ++ //#endif + + //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2" + case AUTH_REQ_SASL_CONTINUE: +diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java +index 387a9ac1cd..4dc7f94000 100644 +--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java ++++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java +@@ -692,7 +692,7 @@ public synchronized void processNotifies(int timeoutMillis) throws SQLException + } + + try { +- while (pgStream.hasMessagePending() || timeoutMillis >= 0 ) { ++ while (timeoutMillis >= 0 || pgStream.hasMessagePending()) { + if (useTimeout && timeoutMillis >= 0) { + setSocketTimeout(timeoutMillis); + } +diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/SslMode.java b/pgjdbc/src/main/java/org/postgresql/jdbc/SslMode.java +new file mode 100644 +index 0000000000..bd2a3d0bcb +--- /dev/null ++++ b/pgjdbc/src/main/java/org/postgresql/jdbc/SslMode.java +@@ -0,0 +1,81 @@ ++/* ++ * Copyright (c) 2018, PostgreSQL Global Development Group ++ * See the LICENSE file in the project root for more information. ++ */ ++ ++package org.postgresql.jdbc; ++ ++import org.postgresql.PGProperty; ++import org.postgresql.util.GT; ++import org.postgresql.util.PSQLException; ++import org.postgresql.util.PSQLState; ++ ++import java.util.Properties; ++ ++public enum SslMode { ++ /** ++ * Do not use encrypted connections. ++ */ ++ DISABLE("disable"), ++ /** ++ * Start with non-encrypted connection, then try encrypted one. ++ */ ++ ALLOW("allow"), ++ /** ++ * Start with encrypted connection, fallback to non-encrypted (default). ++ */ ++ PREFER("prefer"), ++ /** ++ * Ensure connection is encrypted. ++ */ ++ REQUIRE("require"), ++ /** ++ * Ensure connection is encrypted, and client trusts server certificate. ++ */ ++ VERIFY_CA("verify-ca"), ++ /** ++ * Ensure connection is encrypted, client trusts server certificate, and server hostname matches ++ * the one listed in the server certificate. ++ */ ++ VERIFY_FULL("verify-full"), ++ ; ++ ++ public static final SslMode[] VALUES = values(); ++ ++ public final String value; ++ ++ SslMode(String value) { ++ this.value = value; ++ } ++ ++ public boolean requireEncryption() { ++ return this.compareTo(REQUIRE) >= 0; ++ } ++ ++ public boolean verifyCertificate() { ++ return this == VERIFY_CA || this == VERIFY_FULL; ++ } ++ ++ public boolean verifyPeerName() { ++ return this == VERIFY_FULL; ++ } ++ ++ public static SslMode of(Properties info) throws PSQLException { ++ String sslmode = PGProperty.SSL_MODE.get(info); ++ // If sslmode is not set, fallback to ssl parameter ++ if (sslmode == null) { ++ if (PGProperty.SSL.getBoolean(info) || "".equals(PGProperty.SSL.get(info))) { ++ return VERIFY_FULL; ++ } ++ return PREFER; ++ } ++ ++ for (SslMode sslMode : VALUES) { ++ if (sslMode.value.equalsIgnoreCase(sslmode)) { ++ return sslMode; ++ } ++ } ++ throw new PSQLException(GT.tr("Invalid sslmode value: {0}", sslmode), ++ PSQLState.CONNECTION_UNABLE_TO_CONNECT); ++ } ++} +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/DefaultJavaSSLFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/DefaultJavaSSLFactory.java +new file mode 100644 +index 0000000000..9757827e0f +--- /dev/null ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/DefaultJavaSSLFactory.java +@@ -0,0 +1,20 @@ ++/* ++ * Copyright (c) 2017, PostgreSQL Global Development Group ++ * See the LICENSE file in the project root for more information. ++ */ ++ ++package org.postgresql.ssl; ++ ++import java.util.Properties; ++import javax.net.ssl.SSLSocketFactory; ++ ++/** ++ * Socket factory that uses Java's default truststore to validate server certificate. ++ * Note: it always validates server certificate, so it might result to downgrade to non-encrypted ++ * connection when default truststore lacks certificates to validate server. ++ */ ++public class DefaultJavaSSLFactory extends WrappedFactory { ++ public DefaultJavaSSLFactory(Properties info) { ++ _factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); ++ } ++} +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LazyKeyManager.java b/pgjdbc/src/main/java/org/postgresql/ssl/LazyKeyManager.java +similarity index 99% +rename from pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LazyKeyManager.java +rename to pgjdbc/src/main/java/org/postgresql/ssl/LazyKeyManager.java +index 4585f1a968..be4db4153b 100644 +--- a/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LazyKeyManager.java ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/LazyKeyManager.java +@@ -3,7 +3,7 @@ + * See the LICENSE file in the project root for more information. + */ + +-package org.postgresql.ssl.jdbc4; ++package org.postgresql.ssl; + + import org.postgresql.util.GT; + import org.postgresql.util.PSQLException; +@@ -222,6 +222,7 @@ public PrivateKey getPrivateKey(String alias) { + } + try { + PBEKeySpec pbeKeySpec = new PBEKeySpec(pwdcb.getPassword()); ++ pwdcb.clearPassword(); + // Now create the Key from the PBEKeySpec + SecretKeyFactory skFac = SecretKeyFactory.getInstance(ePKInfo.getAlgName()); + Key pbeKey = skFac.generateSecret(pbeKeySpec); +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/LibPQFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/LibPQFactory.java +new file mode 100644 +index 0000000000..c0c34bd274 +--- /dev/null ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/LibPQFactory.java +@@ -0,0 +1,220 @@ ++/* ++ * Copyright (c) 2004, PostgreSQL Global Development Group ++ * See the LICENSE file in the project root for more information. ++ */ ++ ++package org.postgresql.ssl; ++ ++import org.postgresql.PGProperty; ++import org.postgresql.jdbc.SslMode; ++import org.postgresql.ssl.NonValidatingFactory.NonValidatingTM; ++import org.postgresql.util.GT; ++import org.postgresql.util.ObjectFactory; ++import org.postgresql.util.PSQLException; ++import org.postgresql.util.PSQLState; ++ ++import java.io.Console; ++import java.io.FileInputStream; ++import java.io.FileNotFoundException; ++import java.io.IOException; ++import java.security.GeneralSecurityException; ++import java.security.KeyManagementException; ++import java.security.KeyStore; ++import java.security.KeyStoreException; ++import java.security.NoSuchAlgorithmException; ++import java.security.cert.Certificate; ++import java.security.cert.CertificateFactory; ++import java.util.Properties; ++import javax.net.ssl.KeyManager; ++import javax.net.ssl.SSLContext; ++import javax.net.ssl.TrustManager; ++import javax.net.ssl.TrustManagerFactory; ++import javax.security.auth.callback.Callback; ++import javax.security.auth.callback.CallbackHandler; ++import javax.security.auth.callback.PasswordCallback; ++import javax.security.auth.callback.UnsupportedCallbackException; ++ ++/** ++ * Provide an SSLSocketFactory that is compatible with the libpq behaviour. ++ */ ++public class LibPQFactory extends WrappedFactory { ++ ++ LazyKeyManager km; ++ ++ /** ++ * @param info the connection parameters The following parameters are used: ++ * sslmode,sslcert,sslkey,sslrootcert,sslhostnameverifier,sslpasswordcallback,sslpassword ++ * @throws PSQLException if security error appears when initializing factory ++ */ ++ public LibPQFactory(Properties info) throws PSQLException { ++ try { ++ SSLContext ctx = SSLContext.getInstance("TLS"); // or "SSL" ? ++ ++ // Determining the default file location ++ String pathsep = System.getProperty("file.separator"); ++ String defaultdir; ++ boolean defaultfile = false; ++ if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows ++ defaultdir = System.getenv("APPDATA") + pathsep + "postgresql" + pathsep; ++ } else { ++ defaultdir = System.getProperty("user.home") + pathsep + ".postgresql" + pathsep; ++ } ++ ++ // Load the client's certificate and key ++ String sslcertfile = PGProperty.SSL_CERT.get(info); ++ if (sslcertfile == null) { // Fall back to default ++ defaultfile = true; ++ sslcertfile = defaultdir + "postgresql.crt"; ++ } ++ String sslkeyfile = PGProperty.SSL_KEY.get(info); ++ if (sslkeyfile == null) { // Fall back to default ++ defaultfile = true; ++ sslkeyfile = defaultdir + "postgresql.pk8"; ++ } ++ ++ // Determine the callback handler ++ CallbackHandler cbh; ++ String sslpasswordcallback = PGProperty.SSL_PASSWORD_CALLBACK.get(info); ++ if (sslpasswordcallback != null) { ++ try { ++ cbh = (CallbackHandler) ObjectFactory.instantiate(sslpasswordcallback, info, false, null); ++ } catch (Exception e) { ++ throw new PSQLException( ++ GT.tr("The password callback class provided {0} could not be instantiated.", ++ sslpasswordcallback), ++ PSQLState.CONNECTION_FAILURE, e); ++ } ++ } else { ++ cbh = new ConsoleCallbackHandler(PGProperty.SSL_PASSWORD.get(info)); ++ } ++ ++ // If the properties are empty, give null to prevent client key selection ++ km = new LazyKeyManager(("".equals(sslcertfile) ? null : sslcertfile), ++ ("".equals(sslkeyfile) ? null : sslkeyfile), cbh, defaultfile); ++ ++ TrustManager[] tm; ++ SslMode sslMode = SslMode.of(info); ++ if (!sslMode.verifyCertificate()) { ++ // server validation is not required ++ tm = new TrustManager[]{new NonValidatingTM()}; ++ } else { ++ // Load the server certificate ++ ++ TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); ++ KeyStore ks; ++ try { ++ ks = KeyStore.getInstance("jks"); ++ } catch (KeyStoreException e) { ++ // this should never happen ++ throw new NoSuchAlgorithmException("jks KeyStore not available"); ++ } ++ String sslrootcertfile = PGProperty.SSL_ROOT_CERT.get(info); ++ if (sslrootcertfile == null) { // Fall back to default ++ sslrootcertfile = defaultdir + "root.crt"; ++ } ++ FileInputStream fis; ++ try { ++ fis = new FileInputStream(sslrootcertfile); // NOSONAR ++ } catch (FileNotFoundException ex) { ++ throw new PSQLException( ++ GT.tr("Could not open SSL root certificate file {0}.", sslrootcertfile), ++ PSQLState.CONNECTION_FAILURE, ex); ++ } ++ try { ++ CertificateFactory cf = CertificateFactory.getInstance("X.509"); ++ // Certificate[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); //Does ++ // not work in java 1.4 ++ Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); ++ ks.load(null, null); ++ for (int i = 0; i < certs.length; i++) { ++ ks.setCertificateEntry("cert" + i, (Certificate) certs[i]); ++ } ++ tmf.init(ks); ++ } catch (IOException ioex) { ++ throw new PSQLException( ++ GT.tr("Could not read SSL root certificate file {0}.", sslrootcertfile), ++ PSQLState.CONNECTION_FAILURE, ioex); ++ } catch (GeneralSecurityException gsex) { ++ throw new PSQLException( ++ GT.tr("Loading the SSL root certificate {0} into a TrustManager failed.", ++ sslrootcertfile), ++ PSQLState.CONNECTION_FAILURE, gsex); ++ } finally { ++ try { ++ fis.close(); ++ } catch (IOException e) { ++ /* ignore */ ++ } ++ } ++ tm = tmf.getTrustManagers(); ++ } ++ ++ // finally we can initialize the context ++ try { ++ ctx.init(new KeyManager[]{km}, tm, null); ++ } catch (KeyManagementException ex) { ++ throw new PSQLException(GT.tr("Could not initialize SSL context."), ++ PSQLState.CONNECTION_FAILURE, ex); ++ } ++ ++ _factory = ctx.getSocketFactory(); ++ } catch (NoSuchAlgorithmException ex) { ++ throw new PSQLException(GT.tr("Could not find a java cryptographic algorithm: {0}.", ++ ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); ++ } ++ } ++ ++ /** ++ * Propagates any exception from {@link LazyKeyManager}. ++ * ++ * @throws PSQLException if there is an exception to propagate ++ */ ++ public void throwKeyManagerException() throws PSQLException { ++ if (km != null) { ++ km.throwKeyManagerException(); ++ } ++ } ++ ++ /** ++ * A CallbackHandler that reads the password from the console or returns the password given to its ++ * constructor. ++ */ ++ static class ConsoleCallbackHandler implements CallbackHandler { ++ ++ private char[] password = null; ++ ++ ConsoleCallbackHandler(String password) { ++ if (password != null) { ++ this.password = password.toCharArray(); ++ } ++ } ++ ++ /** ++ * Handles the callbacks. ++ * ++ * @param callbacks The callbacks to handle ++ * @throws UnsupportedCallbackException If the console is not available or other than ++ * PasswordCallback is supplied ++ */ ++ @Override ++ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { ++ Console cons = System.console(); ++ if (cons == null && password == null) { ++ throw new UnsupportedCallbackException(callbacks[0], "Console is not available"); ++ } ++ for (Callback callback : callbacks) { ++ if (!(callback instanceof PasswordCallback)) { ++ throw new UnsupportedCallbackException(callback); ++ } ++ PasswordCallback pwdCallback = (PasswordCallback) callback; ++ if (password != null) { ++ pwdCallback.setPassword(password); ++ continue; ++ } ++ // It is used instead of cons.readPassword(prompt), because the prompt may contain '%' ++ // characters ++ pwdCallback.setPassword(cons.readPassword("%s", pwdCallback.getPrompt())); ++ } ++ } ++ } ++} +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/MakeSSL.java b/pgjdbc/src/main/java/org/postgresql/ssl/MakeSSL.java +index d2e453552c..e09d88edbe 100644 +--- a/pgjdbc/src/main/java/org/postgresql/ssl/MakeSSL.java ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/MakeSSL.java +@@ -7,7 +7,8 @@ + + import org.postgresql.PGProperty; + import org.postgresql.core.PGStream; +-import org.postgresql.ssl.jdbc4.LibPQFactory; ++import org.postgresql.core.SocketFactoryFactory; ++import org.postgresql.jdbc.SslMode; + import org.postgresql.util.GT; + import org.postgresql.util.ObjectFactory; + import org.postgresql.util.PSQLException; +@@ -17,7 +18,6 @@ + import java.util.Properties; + import java.util.logging.Level; + import java.util.logging.Logger; +- + import javax.net.ssl.HostnameVerifier; + import javax.net.ssl.SSLSocket; + import javax.net.ssl.SSLSocketFactory; +@@ -30,47 +30,38 @@ public static void convert(PGStream stream, Properties info) + throws PSQLException, IOException { + LOGGER.log(Level.FINE, "converting regular socket connection to ssl"); + +- SSLSocketFactory factory; +- +- String sslmode = PGProperty.SSL_MODE.get(info); +- // Use the default factory if no specific factory is requested +- // unless sslmode is set +- String classname = PGProperty.SSL_FACTORY.get(info); +- if (classname == null) { +- // If sslmode is set, use the libpq compatible factory +- if (sslmode != null) { +- factory = new LibPQFactory(info); +- } else { +- factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); +- } +- } else { +- try { +- factory = (SSLSocketFactory) instantiate(classname, info, true, +- PGProperty.SSL_FACTORY_ARG.get(info)); +- } catch (Exception e) { +- throw new PSQLException( +- GT.tr("The SSLSocketFactory class provided {0} could not be instantiated.", classname), +- PSQLState.CONNECTION_FAILURE, e); +- } +- } +- ++ SSLSocketFactory factory = SocketFactoryFactory.getSslSocketFactory(info); + SSLSocket newConnection; + try { + newConnection = (SSLSocket) factory.createSocket(stream.getSocket(), + stream.getHostSpec().getHost(), stream.getHostSpec().getPort(), true); + // We must invoke manually, otherwise the exceptions are hidden ++ newConnection.setUseClientMode(true); + newConnection.startHandshake(); + } catch (IOException ex) { +- if (factory instanceof LibPQFactory) { // throw any KeyManager exception +- ((LibPQFactory) factory).throwKeyManagerException(); +- } + throw new PSQLException(GT.tr("SSL error: {0}", ex.getMessage()), + PSQLState.CONNECTION_FAILURE, ex); + } ++ if (factory instanceof LibPQFactory) { // throw any KeyManager exception ++ ((LibPQFactory) factory).throwKeyManagerException(); ++ } + ++ SslMode sslMode = SslMode.of(info); ++ if (sslMode.verifyPeerName()) { ++ verifyPeerName(stream, info, newConnection); ++ } ++ ++ stream.changeSocket(newConnection); ++ } ++ ++ private static void verifyPeerName(PGStream stream, Properties info, SSLSocket newConnection) ++ throws PSQLException { ++ HostnameVerifier hvn; + String sslhostnameverifier = PGProperty.SSL_HOSTNAME_VERIFIER.get(info); +- if (sslhostnameverifier != null) { +- HostnameVerifier hvn; ++ if (sslhostnameverifier == null) { ++ hvn = PGjdbcHostnameVerifier.INSTANCE; ++ sslhostnameverifier = "PgjdbcHostnameVerifier"; ++ } else { + try { + hvn = (HostnameVerifier) instantiate(sslhostnameverifier, info, false, null); + } catch (Exception e) { +@@ -79,24 +70,16 @@ public static void convert(PGStream stream, Properties info) + sslhostnameverifier), + PSQLState.CONNECTION_FAILURE, e); + } +- if (!hvn.verify(stream.getHostSpec().getHost(), newConnection.getSession())) { +- throw new PSQLException( +- GT.tr("The hostname {0} could not be verified by hostnameverifier {1}.", +- stream.getHostSpec().getHost(), sslhostnameverifier), +- PSQLState.CONNECTION_FAILURE); +- } +- } else { +- if ("verify-full".equals(sslmode) && factory instanceof LibPQFactory) { +- if (!(((LibPQFactory) factory).verify(stream.getHostSpec().getHost(), +- newConnection.getSession()))) { +- throw new PSQLException( +- GT.tr("The hostname {0} could not be verified.", stream.getHostSpec().getHost()), +- PSQLState.CONNECTION_FAILURE); +- } +- } ++ } + ++ if (hvn.verify(stream.getHostSpec().getHost(), newConnection.getSession())) { ++ return; + } +- stream.changeSocket(newConnection); ++ ++ throw new PSQLException( ++ GT.tr("The hostname {0} could not be verified by hostnameverifier {1}.", ++ stream.getHostSpec().getHost(), sslhostnameverifier), ++ PSQLState.CONNECTION_FAILURE); + } + + } +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java b/pgjdbc/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java +new file mode 100644 +index 0000000000..851eb3aeb6 +--- /dev/null ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java +@@ -0,0 +1,264 @@ ++/* ++ * Copyright (c) 2018, PostgreSQL Global Development Group ++ * See the LICENSE file in the project root for more information. ++ */ ++ ++package org.postgresql.ssl; ++ ++import org.postgresql.util.GT; ++ ++import java.net.IDN; ++import java.security.cert.CertificateParsingException; ++import java.security.cert.X509Certificate; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.Comparator; ++import java.util.List; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import javax.naming.InvalidNameException; ++import javax.naming.ldap.LdapName; ++import javax.naming.ldap.Rdn; ++import javax.net.ssl.HostnameVerifier; ++import javax.net.ssl.SSLPeerUnverifiedException; ++import javax.net.ssl.SSLSession; ++import javax.security.auth.x500.X500Principal; ++ ++public class PGjdbcHostnameVerifier implements HostnameVerifier { ++ private static final Logger LOGGER = Logger.getLogger(PGjdbcHostnameVerifier.class.getName()); ++ ++ public static final PGjdbcHostnameVerifier INSTANCE = new PGjdbcHostnameVerifier(); ++ ++ private static final int TYPE_DNS_NAME = 2; ++ private static final int TYPE_IP_ADDRESS = 7; ++ ++ public static Comparator HOSTNAME_PATTERN_COMPARATOR = new Comparator() { ++ private int countChars(String value, char ch) { ++ int count = 0; ++ int pos = -1; ++ while (true) { ++ pos = value.indexOf(ch, pos + 1); ++ if (pos == -1) { ++ break; ++ } ++ count++; ++ } ++ return count; ++ } ++ ++ @Override ++ public int compare(String o1, String o2) { ++ // The more the dots the better: a.b.c.postgresql.org is more specific than postgresql.org ++ int d1 = countChars(o1, '.'); ++ int d2 = countChars(o2, '.'); ++ if (d1 != d2) { ++ return d1 > d2 ? 1 : -1; ++ } ++ ++ // The less the stars the better: postgresql.org is more specific than *.*.postgresql.org ++ int s1 = countChars(o1, '*'); ++ int s2 = countChars(o2, '*'); ++ if (s1 != s2) { ++ return s1 < s2 ? 1 : -1; ++ } ++ ++ // The longer the better: postgresql.org is more specific than sql.org ++ int l1 = o1.length(); ++ int l2 = o2.length(); ++ if (l1 != l2) { ++ return l1 > l2 ? 1 : -1; ++ } ++ ++ return 0; ++ } ++ }; ++ ++ @Override ++ public boolean verify(String hostname, SSLSession session) { ++ X509Certificate[] peerCerts; ++ try { ++ peerCerts = (X509Certificate[]) session.getPeerCertificates(); ++ } catch (SSLPeerUnverifiedException e) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Unable to parse X509Certificate for hostname {0}", hostname), e); ++ return false; ++ } ++ if (peerCerts == null || peerCerts.length == 0) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("No certificates found for hostname {0}", hostname)); ++ return false; ++ } ++ ++ String canonicalHostname; ++ if (hostname.startsWith("[") && hostname.endsWith("]")) { ++ // IPv6 address like [2001:db8:0:1:1:1:1:1] ++ canonicalHostname = hostname.substring(1, hostname.length() - 1); ++ } else { ++ // This converts unicode domain name to ASCII ++ try { ++ canonicalHostname = IDN.toASCII(hostname); ++ if (LOGGER.isLoggable(Level.FINEST)) { ++ LOGGER.log(Level.FINEST, "Canonical host name for {0} is {1}", ++ new Object[]{hostname, canonicalHostname}); ++ } ++ } catch (IllegalArgumentException e) { ++ // e.g. hostname is invalid ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Hostname {0} is invalid", hostname), e); ++ return false; ++ } ++ } ++ ++ X509Certificate serverCert = peerCerts[0]; ++ ++ // Check for Subject Alternative Names (see RFC 6125) ++ ++ Collection> subjectAltNames; ++ try { ++ subjectAltNames = serverCert.getSubjectAlternativeNames(); ++ if (subjectAltNames == null) { ++ subjectAltNames = Collections.emptyList(); ++ } ++ } catch (CertificateParsingException e) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Unable to parse certificates for hostname {0}", hostname), e); ++ return false; ++ } ++ ++ boolean anyDnsSan = false; ++ /* ++ * Each item in the SAN collection is a 2-element list. ++ * See {@link X509Certificate#getSubjectAlternativeNames} ++ * The first element in each list is a number indicating the type of entry. ++ */ ++ for (List sanItem : subjectAltNames) { ++ if (sanItem.size() != 2) { ++ continue; ++ } ++ Integer sanType = (Integer) sanItem.get(0); ++ if (sanType == null) { ++ // just in case ++ continue; ++ } ++ if (sanType != TYPE_IP_ADDRESS && sanType != TYPE_DNS_NAME) { ++ continue; ++ } ++ String san = (String) sanItem.get(1); ++ if (sanType == TYPE_IP_ADDRESS && san.startsWith("*")) { ++ // Wildcards should not be present in the IP Address field ++ continue; ++ } ++ anyDnsSan |= sanType == TYPE_DNS_NAME; ++ if (verifyHostName(canonicalHostname, san)) { ++ if (LOGGER.isLoggable(Level.FINEST)) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Server name validation pass for {0}, subjectAltName {1}", hostname, san)); ++ } ++ return true; ++ } ++ } ++ ++ if (anyDnsSan) { ++ /* ++ * RFC2818, section 3.1 (I bet you won't recheck :) ++ * If a subjectAltName extension of type dNSName is present, that MUST ++ * be used as the identity. Otherwise, the (most specific) Common Name ++ * field in the Subject field of the certificate MUST be used. Although ++ * the use of the Common Name is existing practice, it is deprecated and ++ * Certification Authorities are encouraged to use the dNSName instead. ++ */ ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Server name validation failed: certificate for host {0} dNSName entries subjectAltName," ++ + " but none of them match. Assuming server name validation failed", hostname)); ++ return false; ++ } ++ ++ // Last attempt: no DNS Subject Alternative Name entries detected, try common name ++ LdapName DN; ++ try { ++ DN = new LdapName(serverCert.getSubjectX500Principal().getName(X500Principal.RFC2253)); ++ } catch (InvalidNameException e) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Server name validation failed: unable to extract common name" ++ + " from X509Certificate for hostname {0}", hostname), e); ++ return false; ++ } ++ ++ List commonNames = new ArrayList(1); ++ for (Rdn rdn : DN.getRdns()) { ++ if ("CN".equals(rdn.getType())) { ++ commonNames.add((String) rdn.getValue()); ++ } ++ } ++ if (commonNames.isEmpty()) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Server name validation failed: certificate for hostname {0} has no DNS subjectAltNames," ++ + " and it CommonName is missing as well", ++ hostname)); ++ return false; ++ } ++ if (commonNames.size() > 1) { ++ /* ++ * RFC2818, section 3.1 ++ * If a subjectAltName extension of type dNSName is present, that MUST ++ * be used as the identity. Otherwise, the (most specific) Common Name ++ * field in the Subject field of the certificate MUST be used ++ * ++ * The sort is from less specific to most specific. ++ */ ++ Collections.sort(commonNames, HOSTNAME_PATTERN_COMPARATOR); ++ } ++ String commonName = commonNames.get(commonNames.size() - 1); ++ boolean result = verifyHostName(canonicalHostname, commonName); ++ if (!result) { ++ LOGGER.log(Level.SEVERE, ++ GT.tr("Server name validation failed: hostname {0} does not match common name {1}", ++ hostname, commonName)); ++ } ++ return result; ++ } ++ ++ public boolean verifyHostName(String hostname, String pattern) { ++ if (hostname == null || pattern == null) { ++ return false; ++ } ++ int lastStar = pattern.lastIndexOf('*'); ++ if (lastStar == -1) { ++ // No wildcard => just compare hostnames ++ return hostname.equalsIgnoreCase(pattern); ++ } ++ if (lastStar > 0) { ++ // Wildcards like foo*.com are not supported yet ++ return false; ++ } ++ if (pattern.indexOf('.') == -1) { ++ // Wildcard certificates should contain at least one dot ++ return false; ++ } ++ // pattern starts with *, so hostname should be at least (pattern.length-1) long ++ if (hostname.length() < pattern.length() - 1) { ++ return false; ++ } ++ // Use case insensitive comparison ++ final boolean ignoreCase = true; ++ // Below code is "hostname.endsWithIgnoreCase(pattern.withoutFirstStar())" ++ ++ // E.g. hostname==sub.host.com; pattern==*.host.com ++ // We need to start the offset of ".host.com" in hostname ++ // For this we take hostname.length() - pattern.length() ++ // and +1 is required since pattern is known to start with * ++ int toffset = hostname.length() - pattern.length() + 1; ++ ++ // Wildcard covers just one domain level ++ // a.b.c.com should not be covered by *.c.com ++ if (hostname.lastIndexOf('.', toffset - 1) >= 0) { ++ // If there's a dot in between 0..toffset ++ return false; ++ } ++ ++ return hostname.regionMatches(ignoreCase, toffset, ++ pattern, 1, pattern.length() - 1); ++ } ++ ++} +diff --git a/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java b/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java +index 4aadabd4bb..7dbe9df63c 100644 +--- a/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java ++++ b/pgjdbc/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java +@@ -1,270 +1,63 @@ + /* +- * Copyright (c) 2004, PostgreSQL Global Development Group ++ * Copyright (c) 2017, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + + package org.postgresql.ssl.jdbc4; + +-import org.postgresql.PGProperty; +-import org.postgresql.ssl.MakeSSL; +-import org.postgresql.ssl.NonValidatingFactory.NonValidatingTM; +-import org.postgresql.ssl.WrappedFactory; +-import org.postgresql.util.GT; ++import org.postgresql.jdbc.SslMode; ++import org.postgresql.ssl.PGjdbcHostnameVerifier; + import org.postgresql.util.PSQLException; +-import org.postgresql.util.PSQLState; + +-import java.io.Console; +-import java.io.FileInputStream; +-import java.io.FileNotFoundException; +-import java.io.IOException; +-import java.security.GeneralSecurityException; +-import java.security.KeyManagementException; +-import java.security.KeyStore; +-import java.security.KeyStoreException; +-import java.security.NoSuchAlgorithmException; +-import java.security.cert.Certificate; +-import java.security.cert.CertificateFactory; +-import java.security.cert.CertificateParsingException; +-import java.security.cert.X509Certificate; +-import java.util.Collection; +-import java.util.List; ++import java.net.IDN; + import java.util.Properties; +- +-import javax.naming.InvalidNameException; +-import javax.naming.ldap.LdapName; +-import javax.naming.ldap.Rdn; + import javax.net.ssl.HostnameVerifier; +-import javax.net.ssl.KeyManager; +-import javax.net.ssl.SSLContext; +-import javax.net.ssl.SSLPeerUnverifiedException; + import javax.net.ssl.SSLSession; +-import javax.net.ssl.TrustManager; +-import javax.net.ssl.TrustManagerFactory; +-import javax.security.auth.callback.Callback; +-import javax.security.auth.callback.CallbackHandler; +-import javax.security.auth.callback.PasswordCallback; +-import javax.security.auth.callback.UnsupportedCallbackException; +-import javax.security.auth.x500.X500Principal; + + /** +- * Provide an SSLSocketFactory that is compatible with the libpq behaviour. ++ * @deprecated prefer {@link org.postgresql.ssl.LibPQFactory} + */ +-public class LibPQFactory extends WrappedFactory implements HostnameVerifier { +- +- private static final int ALT_DNS_NAME = 2; +- +- LazyKeyManager km = null; +- String sslmode; ++@Deprecated ++public class LibPQFactory extends org.postgresql.ssl.LibPQFactory implements HostnameVerifier { ++ private final SslMode sslMode; + + /** + * @param info the connection parameters The following parameters are used: +- * sslmode,sslcert,sslkey,sslrootcert,sslhostnameverifier,sslpasswordcallback,sslpassword ++ * sslmode,sslcert,sslkey,sslrootcert,sslhostnameverifier,sslpasswordcallback,sslpassword + * @throws PSQLException if security error appears when initializing factory ++ * @deprecated prefer {@link org.postgresql.ssl.LibPQFactory} + */ ++ @Deprecated + public LibPQFactory(Properties info) throws PSQLException { +- try { +- sslmode = PGProperty.SSL_MODE.get(info); +- SSLContext ctx = SSLContext.getInstance("TLS"); // or "SSL" ? +- +- // Determining the default file location +- String pathsep = System.getProperty("file.separator"); +- String defaultdir; +- boolean defaultfile = false; +- if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows +- defaultdir = System.getenv("APPDATA") + pathsep + "postgresql" + pathsep; +- } else { +- defaultdir = System.getProperty("user.home") + pathsep + ".postgresql" + pathsep; +- } +- +- // Load the client's certificate and key +- String sslcertfile = PGProperty.SSL_CERT.get(info); +- if (sslcertfile == null) { // Fall back to default +- defaultfile = true; +- sslcertfile = defaultdir + "postgresql.crt"; +- } +- String sslkeyfile = PGProperty.SSL_KEY.get(info); +- if (sslkeyfile == null) { // Fall back to default +- defaultfile = true; +- sslkeyfile = defaultdir + "postgresql.pk8"; +- } +- +- // Determine the callback handler +- CallbackHandler cbh; +- String sslpasswordcallback = PGProperty.SSL_PASSWORD_CALLBACK.get(info); +- if (sslpasswordcallback != null) { +- try { +- cbh = (CallbackHandler) MakeSSL.instantiate(sslpasswordcallback, info, false, null); +- } catch (Exception e) { +- throw new PSQLException( +- GT.tr("The password callback class provided {0} could not be instantiated.", +- sslpasswordcallback), +- PSQLState.CONNECTION_FAILURE, e); +- } +- } else { +- cbh = new ConsoleCallbackHandler(PGProperty.SSL_PASSWORD.get(info)); +- } +- +- // If the properties are empty, give null to prevent client key selection +- km = new LazyKeyManager(("".equals(sslcertfile) ? null : sslcertfile), +- ("".equals(sslkeyfile) ? null : sslkeyfile), cbh, defaultfile); +- +- TrustManager[] tm; +- if ("verify-ca".equals(sslmode) || "verify-full".equals(sslmode)) { +- // Load the server certificate +- +- TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); +- KeyStore ks; +- try { +- ks = KeyStore.getInstance("jks"); +- } catch (KeyStoreException e) { +- // this should never happen +- throw new NoSuchAlgorithmException("jks KeyStore not available"); +- } +- String sslrootcertfile = PGProperty.SSL_ROOT_CERT.get(info); +- if (sslrootcertfile == null) { // Fall back to default +- sslrootcertfile = defaultdir + "root.crt"; +- } +- FileInputStream fis; +- try { +- fis = new FileInputStream(sslrootcertfile); // NOSONAR +- } catch (FileNotFoundException ex) { +- throw new PSQLException( +- GT.tr("Could not open SSL root certificate file {0}.", sslrootcertfile), +- PSQLState.CONNECTION_FAILURE, ex); +- } +- try { +- CertificateFactory cf = CertificateFactory.getInstance("X.509"); +- // Certificate[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); //Does +- // not work in java 1.4 +- Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); +- ks.load(null, null); +- for (int i = 0; i < certs.length; i++) { +- ks.setCertificateEntry("cert" + i, (Certificate) certs[i]); +- } +- tmf.init(ks); +- } catch (IOException ioex) { +- throw new PSQLException( +- GT.tr("Could not read SSL root certificate file {0}.", sslrootcertfile), +- PSQLState.CONNECTION_FAILURE, ioex); +- } catch (GeneralSecurityException gsex) { +- throw new PSQLException( +- GT.tr("Loading the SSL root certificate {0} into a TrustManager failed.", +- sslrootcertfile), +- PSQLState.CONNECTION_FAILURE, gsex); +- } finally { +- try { +- fis.close(); +- } catch (IOException e) { +- /* ignore */ +- } +- } +- tm = tmf.getTrustManagers(); +- } else { // server validation is not required +- tm = new TrustManager[]{new NonValidatingTM()}; +- } ++ super(info); + +- // finally we can initialize the context +- try { +- ctx.init(new KeyManager[]{km}, tm, null); +- } catch (KeyManagementException ex) { +- throw new PSQLException(GT.tr("Could not initialize SSL context."), +- PSQLState.CONNECTION_FAILURE, ex); +- } +- +- _factory = ctx.getSocketFactory(); +- } catch (NoSuchAlgorithmException ex) { +- throw new PSQLException(GT.tr("Could not find a java cryptographic algorithm: {0}.", +- ex.getMessage()), PSQLState.CONNECTION_FAILURE, ex); +- } ++ sslMode = SslMode.of(info); + } + + /** +- * Propagates any exception from {@link LazyKeyManager}. ++ * Verifies if given hostname matches pattern. + * +- * @throws PSQLException if there is an exception to propagate ++ * @deprecated use {@link PGjdbcHostnameVerifier} ++ * @param hostname input hostname ++ * @param pattern domain name pattern ++ * @return true when domain matches pattern + */ +- public void throwKeyManagerException() throws PSQLException { +- if (km != null) { +- km.throwKeyManagerException(); +- } +- } +- +- /** +- * A CallbackHandler that reads the password from the console or returns the password given to its +- * constructor. +- */ +- static class ConsoleCallbackHandler implements CallbackHandler { +- +- private char[] password = null; +- +- ConsoleCallbackHandler(String password) { +- if (password != null) { +- this.password = password.toCharArray(); +- } +- } +- +- /** +- * Handles the callbacks. +- * +- * @param callbacks The callbacks to handle +- * @throws UnsupportedCallbackException If the console is not available or other than +- * PasswordCallback is supplied +- */ +- @Override +- public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { +- Console cons = System.console(); +- if (cons == null && password == null) { +- throw new UnsupportedCallbackException(callbacks[0], "Console is not available"); +- } +- for (Callback callback : callbacks) { +- if (callback instanceof PasswordCallback) { +- if (password == null) { +- // It is used instead of cons.readPassword(prompt), because the prompt may contain '%' +- // characters +- ((PasswordCallback) callback).setPassword( +- cons.readPassword("%s", ((PasswordCallback) callback).getPrompt())); +- } else { +- ((PasswordCallback) callback).setPassword(password); +- } +- } else { +- throw new UnsupportedCallbackException(callback); +- } +- } +- +- } +- } +- ++ @Deprecated + public static boolean verifyHostName(String hostname, String pattern) { +- if (hostname == null || pattern == null) { +- return false; +- } +- if (!pattern.startsWith("*")) { +- // No wildcard => just compare hostnames +- return hostname.equalsIgnoreCase(pattern); +- } +- // pattern starts with *, so hostname should be at least (pattern.length-1) long +- if (hostname.length() < pattern.length() - 1) { +- return false; +- } +- // Compare ignore case +- final boolean ignoreCase = true; +- // Below code is "hostname.endsWithIgnoreCase(pattern.withoutFirstStar())" +- +- // E.g. hostname==sub.host.com; pattern==*.host.com +- // We need to start the offset of ".host.com" in hostname +- // For this we take hostname.length() - pattern.length() +- // and +1 is required since pattern is known to start with * +- int toffset = hostname.length() - pattern.length() + 1; +- +- // Wildcard covers just one domain level +- // a.b.c.com should not be covered by *.c.com +- if (hostname.lastIndexOf('.', toffset - 1) >= 0) { +- // If there's a dot in between 0..toffset +- return false; ++ String canonicalHostname; ++ if (hostname.startsWith("[") && hostname.endsWith("]")) { ++ // IPv6 address like [2001:db8:0:1:1:1:1:1] ++ canonicalHostname = hostname.substring(1, hostname.length() - 1); ++ } else { ++ // This converts unicode domain name to ASCII ++ try { ++ canonicalHostname = IDN.toASCII(hostname); ++ } catch (IllegalArgumentException e) { ++ // e.g. hostname is invalid ++ return false; ++ } + } +- +- return hostname.regionMatches(ignoreCase, toffset, +- pattern, 1, pattern.length() - 1); ++ return PGjdbcHostnameVerifier.INSTANCE.verifyHostName(canonicalHostname, pattern); + } + + /** +@@ -274,56 +67,18 @@ public static boolean verifyHostName(String hostname, String pattern) { + * the certificate will not match subdomains. If the connection is made using an IP address + * instead of a hostname, the IP address will be matched (without doing any DNS lookups). + * ++ * @deprecated use PgjdbcHostnameVerifier + * @param hostname Hostname or IP address of the server. + * @param session The SSL session. + * @return true if the certificate belongs to the server, false otherwise. ++ * @see PGjdbcHostnameVerifier + */ ++ @Deprecated + public boolean verify(String hostname, SSLSession session) { +- X509Certificate[] peerCerts; +- try { +- peerCerts = (X509Certificate[]) session.getPeerCertificates(); +- } catch (SSLPeerUnverifiedException e) { +- return false; ++ if (!sslMode.verifyPeerName()) { ++ return true; + } +- if (peerCerts == null || peerCerts.length == 0) { +- return false; +- } +- // Extract the common name +- X509Certificate serverCert = peerCerts[0]; +- +- try { +- // Check for Subject Alternative Names (see RFC 6125) +- Collection> subjectAltNames = serverCert.getSubjectAlternativeNames(); +- +- if (subjectAltNames != null) { +- for (List sanit : subjectAltNames) { +- Integer type = (Integer) sanit.get(0); +- String san = (String) sanit.get(1); +- +- // this mimics libpq check for ALT_DNS_NAME +- if (type != null && type == ALT_DNS_NAME && verifyHostName(hostname, san)) { +- return true; +- } +- } +- } +- } catch (CertificateParsingException e) { +- return false; +- } +- +- LdapName DN; +- try { +- DN = new LdapName(serverCert.getSubjectX500Principal().getName(X500Principal.RFC2253)); +- } catch (InvalidNameException e) { +- return false; +- } +- String CN = null; +- for (Rdn rdn : DN.getRdns()) { +- if ("CN".equals(rdn.getType())) { +- // Multiple AVAs are not treated +- CN = (String) rdn.getValue(); +- break; +- } +- } +- return verifyHostName(hostname, CN); ++ return PGjdbcHostnameVerifier.INSTANCE.verify(hostname, session); + } ++ + } +diff --git a/pgjdbc/src/main/java/org/postgresql/util/ObjectFactory.java b/pgjdbc/src/main/java/org/postgresql/util/ObjectFactory.java +index 4145bc54c0..273ac6d611 100644 +--- a/pgjdbc/src/main/java/org/postgresql/util/ObjectFactory.java ++++ b/pgjdbc/src/main/java/org/postgresql/util/ObjectFactory.java +@@ -21,7 +21,7 @@ + * single String argument is searched if it fails, or tryString is true a no argument constructor + * is tried. + * +- * @param classname Nam of the class to instantiate ++ * @param classname name of the class to instantiate + * @param info parameter to pass as Properties + * @param tryString weather to look for a single String argument constructor + * @param stringarg parameter to pass as String +diff --git a/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java b/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java +index 8a3fb7283a..789736a6ed 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java +@@ -32,14 +32,28 @@ + * Utility class for JDBC tests. + */ + public class TestUtil { ++ /* ++ * The case is as follows: ++ * 1. Typically the database and hostname are taken from System.properties or build.properties or build.local.properties ++ * That enables to override test DB via system property ++ * 2. There are tests where different DBs should be used (e.g. SSL tests), so we can't just use DB name from system property ++ * That is why _test_ properties exist: they overpower System.properties and build.properties ++ */ ++ public static final String SERVER_HOST_PORT_PROP = "_test_hostport"; ++ public static final String DATABASE_PROP = "_test_database"; ++ + /* + * Returns the Test database JDBC URL + */ + public static String getURL() { +- return getURL(getServer(), getPort()); ++ return getURL(getServer(), + getPort()); + } + + public static String getURL(String server, int port) { ++ return getURL(server + ":" + port, getDatabase()); ++ } ++ ++ public static String getURL(String hostport, String database) { + String logLevel = ""; + if (getLogLevel() != null && !getLogLevel().equals("")) { + logLevel = "&loggerLevel=" + getLogLevel(); +@@ -76,9 +90,8 @@ public static String getURL(String server, int port) { + } + + return "jdbc:postgresql://" +- + server + ":" +- + port + "/" +- + getDatabase() ++ + hostport + "/" ++ + database + + "?ApplicationName=Driver Tests" + + logLevel + + logFile +@@ -135,6 +148,13 @@ public static String getPassword() { + return System.getProperty("password"); + } + ++ /* ++ * Returns password for default callbackhandler ++ */ ++ public static String getSslPassword() { ++ return System.getProperty(PGProperty.SSL_PASSWORD.getName()); ++ } ++ + /* + * Returns the user for SSPI authentication tests + */ +@@ -301,6 +321,11 @@ public static Connection openDB(Properties props) throws Exception { + password = ""; + } + props.setProperty("password", password); ++ String sslPassword = getSslPassword(); ++ if (sslPassword != null) { ++ PGProperty.SSL_PASSWORD.set(props, sslPassword); ++ } ++ + if (!props.containsKey(PGProperty.PREPARE_THRESHOLD.getName())) { + PGProperty.PREPARE_THRESHOLD.set(props, getPrepareThreshold()); + } +@@ -310,8 +335,11 @@ public static Connection openDB(Properties props) throws Exception { + props.put(PGProperty.PREFER_QUERY_MODE.getName(), value); + } + } ++ // Enable Base4 tests to override host,port,database ++ String hostport = props.getProperty(SERVER_HOST_PORT_PROP, getServer() + ":" + getPort()); ++ String database = props.getProperty(DATABASE_PROP, getDatabase()); + +- return DriverManager.getConnection(getURL(), props); ++ return DriverManager.getConnection(getURL(hostport, database), props); + } + + /* +diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java +index b9615b0967..faaa82bd45 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java +@@ -9,6 +9,7 @@ + import static org.junit.Assert.assertNotNull; + import static org.junit.Assert.assertNull; + ++import org.postgresql.PGConnection; + import org.postgresql.PGNotification; + import org.postgresql.core.ServerVersion; + import org.postgresql.test.TestUtil; +@@ -41,7 +42,7 @@ public void testNotify() throws SQLException { + stmt.executeUpdate("LISTEN mynotification"); + stmt.executeUpdate("NOTIFY mynotification"); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -60,7 +61,7 @@ public void testNotifyArgument() throws Exception { + stmt.executeUpdate("LISTEN mynotification"); + stmt.executeUpdate("NOTIFY mynotification, 'message'"); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -83,13 +84,14 @@ public void testAsyncNotify() throws Exception { + try { + int retries = 20; + while (retries-- > 0 +- && (notifications = ((org.postgresql.PGConnection) conn).getNotifications()) == null ) { ++ && (notifications = conn.unwrap(PGConnection.class).getNotifications()) == null ) { + Thread.sleep(100); + } + } catch (InterruptedException ie) { + } + +- assertNotNull(notifications); ++ assertNotNull("Notification is expected to be delivered when subscription was created" ++ + " before sending notification", notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); + assertEquals("", notifications[0].getParameter()); +@@ -108,7 +110,7 @@ public void testAsyncNotifyWithTimeout() throws Exception { + + // Here we let the getNotifications() timeout. + long startMillis = System.currentTimeMillis(); +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(500); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(500); + long endMillis = System.currentTimeMillis(); + long runtime = endMillis - startMillis; + assertNull("There have been notifications, although none have been expected.",notifications); +@@ -126,7 +128,7 @@ public void testAsyncNotifyWithTimeoutAndMessagesAvailableWhenStartingListening( + // listen for notifications + connectAndNotify("mynotification"); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(10000); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(10000); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -143,7 +145,7 @@ public void testAsyncNotifyWithEndlessTimeoutAndMessagesAvailableWhenStartingLis + // Now we check the case where notifications are already available while we are waiting forever + connectAndNotify("mynotification"); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(0); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(0); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -169,7 +171,7 @@ public void run() { + } + }).start(); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(10000); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(10000); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -195,7 +197,7 @@ public void run() { + } + }).start(); + +- PGNotification[] notifications = ((org.postgresql.PGConnection) conn).getNotifications(0); ++ PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(0); + assertNotNull(notifications); + assertEquals(1, notifications.length); + assertEquals("mynotification", notifications[0].getName()); +@@ -226,7 +228,7 @@ public void run() { + }).start(); + + try { +- ((org.postgresql.PGConnection) conn).getNotifications(40000); ++ conn.unwrap(PGConnection.class).getNotifications(40000); + Assert.fail("The getNotifications(...) call didn't return when the socket closed."); + } catch (SQLException e) { + // We expected that +diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java +index 46fcc4bb47..405d977fb8 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java +@@ -24,7 +24,6 @@ + BinaryStreamTest.class, + CharacterStreamTest.class, + UUIDTest.class, +- LibPQFactoryHostNameTest.class, + XmlTest.class + }) + public class Jdbc4TestSuite { +diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java +new file mode 100644 +index 0000000000..a162c90e05 +--- /dev/null ++++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java +@@ -0,0 +1,51 @@ ++/* ++ * Copyright (c) 2018, PostgreSQL Global Development Group ++ * See the LICENSE file in the project root for more information. ++ */ ++ ++package org.postgresql.test.ssl; ++ ++import org.postgresql.ssl.PGjdbcHostnameVerifier; ++ ++import org.junit.Assert; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.junit.runners.Parameterized; ++ ++import java.util.Arrays; ++ ++@RunWith(Parameterized.class) ++public class CommonNameVerifierTest { ++ ++ private final String a; ++ private final String b; ++ private final int expected; ++ ++ public CommonNameVerifierTest(String a, String b, int expected) { ++ this.a = a; ++ this.b = b; ++ this.expected = expected; ++ } ++ ++ @Parameterized.Parameters(name = "a={0}, b={1}") ++ public static Iterable data() { ++ return Arrays.asList(new Object[][]{ ++ {"com", "host.com", -1}, ++ {"*.com", "host.com", -1}, ++ {"*.com", "*.*.com", -1}, ++ {"**.com", "*.com", -1}, ++ {"a.com", "*.host.com", -1}, ++ {"host.com", "subhost.host.com", -1}, ++ {"host.com", "host.com", 0} ++ }); ++ } ++ ++ @Test ++ public void comparePatterns() throws Exception { ++ Assert.assertEquals(a + " vs " + b, ++ expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(a, b)); ++ ++ Assert.assertEquals(b + " vs " + a, ++ -expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(b, a)); ++ } ++} +diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/LibPQFactoryHostNameTest.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java +similarity index 79% +rename from pgjdbc/src/test/java/org/postgresql/test/jdbc4/LibPQFactoryHostNameTest.java +rename to pgjdbc/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java +index 1ee140c4ee..0d998b777d 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc4/LibPQFactoryHostNameTest.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java +@@ -3,8 +3,9 @@ + * See the LICENSE file in the project root for more information. + */ + +-package org.postgresql.test.jdbc4; ++package org.postgresql.test.ssl; + ++import org.postgresql.ssl.PGjdbcHostnameVerifier; + import org.postgresql.ssl.jdbc4.LibPQFactory; + + import org.junit.Assert; +@@ -47,11 +48,16 @@ public LibPQFactoryHostNameTest(String hostname, String pattern, boolean expecte + {"sub.host.com", "*.hoSt.com", true}, + {"*.host.com", "host.com", false}, + {"sub.sub.host.com", "*.host.com", false}, // Wildcard should cover just one level ++ {"com", "*", false}, // Wildcard should have al least one dot + }); + } + + @Test + public void checkPattern() throws Exception { +- Assert.assertEquals(expected, LibPQFactory.verifyHostName(hostname, pattern)); ++ Assert.assertEquals(hostname + ", pattern: " + pattern, ++ expected, LibPQFactory.verifyHostName(hostname, pattern)); ++ ++ Assert.assertEquals(hostname + ", pattern: " + pattern, ++ expected, PGjdbcHostnameVerifier.INSTANCE.verifyHostName(hostname, pattern)); + } + } +diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTest.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTest.java +index fa209a33fc..adfc1dda90 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTest.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTest.java +@@ -1,336 +1,481 @@ + /* +- * Copyright (c) 2004, PostgreSQL Global Development Group ++ * Copyright (c) 2018, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + + package org.postgresql.test.ssl; + ++import org.postgresql.PGProperty; ++import org.postgresql.jdbc.SslMode; + import org.postgresql.test.TestUtil; ++import org.postgresql.test.jdbc2.BaseTest4; ++import org.postgresql.util.PSQLState; + +-import junit.framework.TestCase; +-import junit.framework.TestSuite; ++import org.junit.Assert; ++import org.junit.Test; ++import org.junit.runner.RunWith; ++import org.junit.runners.Parameterized; + + import java.io.File; +-import java.sql.Connection; +-import java.sql.DriverManager; ++import java.io.FileNotFoundException; ++import java.net.SocketException; ++import java.security.cert.CertPathValidatorException; + import java.sql.ResultSet; + import java.sql.SQLException; +-import java.util.Map; ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; + import java.util.Properties; +-import java.util.TreeMap; ++import javax.net.ssl.SSLHandshakeException; + +-public class SslTest extends TestCase { ++@RunWith(Parameterized.class) ++public class SslTest extends BaseTest4 { ++ enum Hostname { ++ GOOD("localhost"), ++ BAD("127.0.0.1"), ++ ; + +- /** +- * Tries to connect to the database. +- * +- * @param connstr Connection string for the database +- * @param expected Expected values. the first element is a String holding the expected message of +- * PSQLException or null, if no exception is expected, the second indicates weather ssl is +- * to be used (Boolean) +- */ +- protected void driver(String connstr, Object[] expected) throws SQLException { +- Connection conn = null; +- String exmsg = (String) expected[0]; +- try { +- conn = DriverManager.getConnection(connstr, TestUtil.getUser(), TestUtil.getPassword()); +- if (exmsg != null) { +- fail("Exception did not occur: " + exmsg); ++ final String value; ++ ++ Hostname(String value) { ++ this.value = value; ++ } ++ } ++ ++ enum TestDatabase { ++ hostdb, ++ hostnossldb, ++ hostssldb, ++ hostsslcertdb, ++ certdb, ++ ; ++ ++ public static final TestDatabase[] VALUES = values(); ++ ++ public boolean requiresClientCert() { ++ return this == certdb || this == hostsslcertdb; ++ } ++ ++ public boolean requiresSsl() { ++ return this == certdb || this == hostssldb || this == hostsslcertdb; ++ } ++ ++ public boolean rejectsSsl() { ++ return this == hostnossldb; ++ } ++ } ++ ++ enum ClientCertificate { ++ EMPTY(""), ++ GOOD("goodclient"), ++ BAD("badclient"), ++ ; ++ ++ public static final ClientCertificate[] VALUES = values(); ++ public final String fileName; ++ ++ ClientCertificate(String fileName) { ++ this.fileName = fileName; ++ } ++ } ++ ++ enum ClientRootCertificate { ++ EMPTY(""), ++ GOOD("goodroot"), ++ BAD("badroot"), ++ ; ++ ++ public static final ClientRootCertificate[] VALUES = values(); ++ public final String fileName; ++ ++ ClientRootCertificate(String fileName) { ++ this.fileName = fileName; ++ } ++ } ++ ++ @Parameterized.Parameter(0) ++ public Hostname host; ++ ++ @Parameterized.Parameter(1) ++ public TestDatabase db; ++ ++ @Parameterized.Parameter(2) ++ public SslMode sslmode; ++ ++ @Parameterized.Parameter(3) ++ public ClientCertificate clientCertificate; ++ ++ @Parameterized.Parameter(4) ++ public ClientRootCertificate clientRootCertificate; ++ ++ @Parameterized.Parameter(5) ++ public String certdir; ++ ++ @Parameterized.Parameters(name = "host={0}, db={1} sslMode={2}, cCert={3}, cRootCert={4}") ++ public static Iterable data() { ++ Properties prop = TestUtil.loadPropertyFiles("ssltest.properties"); ++ String enableSslTests = prop.getProperty("enable_ssl_tests"); ++ if (!Boolean.valueOf(enableSslTests)) { ++ System.out.println("enableSslTests is " + enableSslTests + ", skipping SSL tests"); ++ return Collections.emptyList(); ++ } ++ ++ Collection tests = new ArrayList(); ++ ++ ++ File certDirFile = TestUtil.getFile(prop.getProperty("certdir")); ++ String certdir = certDirFile.getAbsolutePath(); ++ ++ for (SslMode sslMode : SslMode.VALUES) { ++ for (Hostname hostname : Hostname.values()) { ++ for (TestDatabase database : TestDatabase.VALUES) { ++ for (ClientCertificate clientCertificate : ClientCertificate.VALUES) { ++ for (ClientRootCertificate rootCertificate : ClientRootCertificate.VALUES) { ++ if ((sslMode == SslMode.DISABLE ++ || database.rejectsSsl()) ++ && (clientCertificate != ClientCertificate.GOOD ++ || rootCertificate != ClientRootCertificate.GOOD)) { ++ // When SSL is disabled, it does not make sense to verify "bad certificates" ++ // since certificates are NOT used in plaintext connections ++ continue; ++ } ++ if (database.rejectsSsl() ++ && (sslMode.verifyCertificate() ++ || hostname == Hostname.BAD) ++ ) { ++ // DB would reject SSL connection, so it makes no sense to test cases like verify-full ++ continue; ++ } ++ tests.add( ++ new Object[]{hostname, database, sslMode, clientCertificate, rootCertificate, ++ certdir}); ++ } ++ } ++ } + } +- // +- ResultSet rs = conn.createStatement().executeQuery("select ssl_is_used()"); +- assertTrue(rs.next()); +- assertEquals("ssl_is_used: ", ((Boolean) expected[1]).booleanValue(), rs.getBoolean(1)); +- conn.close(); ++ } ++ ++ return tests; ++ } ++ ++ @Override ++ protected void updateProperties(Properties props) { ++ super.updateProperties(props); ++ props.put(TestUtil.SERVER_HOST_PORT_PROP, host.value + ":" + TestUtil.getPort()); ++ props.put(TestUtil.DATABASE_PROP, db.toString()); ++ PGProperty.SSL_MODE.set(props, sslmode.value); ++ if (clientCertificate == ClientCertificate.EMPTY) { ++ PGProperty.SSL_CERT.set(props, ""); ++ PGProperty.SSL_KEY.set(props, ""); ++ } else { ++ PGProperty.SSL_CERT.set(props, ++ certdir + "/" + clientCertificate.fileName + ".crt"); ++ PGProperty.SSL_KEY.set(props, ++ certdir + "/" + clientCertificate.fileName + ".pk8"); ++ } ++ if (clientRootCertificate == ClientRootCertificate.EMPTY) { ++ PGProperty.SSL_ROOT_CERT.set(props, ""); ++ } else { ++ PGProperty.SSL_ROOT_CERT.set(props, ++ certdir + "/" + clientRootCertificate.fileName + ".crt"); ++ } ++ } ++ ++ @Override ++ public void setUp() throws Exception { ++ SQLException e = null; ++ try { ++ super.setUp(); + } catch (SQLException ex) { +- if (conn != null) { +- conn.close(); ++ e = ex; ++ } ++ ++ try { ++ // Note that checkErrorCodes throws AssertionError for unexpected cases ++ checkErrorCodes(e); ++ } catch (AssertionError ae) { ++ // Make sure original SQLException is printed as well even in case of AssertionError ++ if (e != null) { ++ ae.initCause(e); ++ } ++ throw ae; ++ } ++ } ++ ++ private void assertClientCertRequired(SQLException e, String caseName) { ++ if (e == null) { ++ Assert.fail(caseName + " should result in failure of client validation"); ++ } ++ Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", ++ PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); ++ } ++ ++ private void checkErrorCodes(SQLException e) { ++ if (e == null && sslmode == SslMode.ALLOW && !db.requiresSsl()) { ++ // allowed to connect with plain connection ++ return; ++ } ++ ++ if (clientRootCertificate == ClientRootCertificate.EMPTY ++ && (sslmode == SslMode.VERIFY_CA || sslmode == SslMode.VERIFY_FULL)) { ++ String caseName = "rootCertificate is missing and sslmode=" + sslmode; ++ if (e == null) { ++ Assert.fail(caseName + " should result in FileNotFound exception for root certificate"); ++ } ++ Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", ++ PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); ++ FileNotFoundException fnf = findCause(e, FileNotFoundException.class); ++ if (fnf == null) { ++ Assert.fail(caseName + " ==> FileNotFoundException should be present in getCause chain"); ++ } ++ return; ++ } ++ ++ if (db.requiresSsl() && sslmode == SslMode.DISABLE) { ++ String caseName = "sslmode=DISABLE and database " + db + " requires SSL"; ++ if (e == null) { ++ Assert.fail(caseName + " should result in connection failure"); ++ } ++ Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", ++ PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); ++ return; ++ } ++ ++ if (db.rejectsSsl() && sslmode.requireEncryption()) { ++ String caseName = ++ "database " + db + " rejects SSL, and sslmode " + sslmode + " requires encryption"; ++ if (e == null) { ++ Assert.fail(caseName + " should result in connection failure"); + } +- if (exmsg == null) { // no exception is excepted +- fail("Exception thrown: " + ex.getMessage()); ++ Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", ++ PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); ++ return; ++ } ++ ++ // Server certificate, server hostname, and client certificate can be validated in any order ++ // So we have three validators and expect at least one of them to match ++ List errors = null; ++ try { ++ if (assertServerCertificate(e)) { ++ return; ++ } ++ } catch (AssertionError ae) { ++ errors = addError(errors, ae); ++ } ++ ++ try { ++ if (assertServerHostname(e)) { ++ return; ++ } ++ } catch (AssertionError ae) { ++ errors = addError(errors, ae); ++ } ++ ++ ++ try { ++ if (assertClientCertificate(e)) { ++ return; ++ } ++ } catch (AssertionError ae) { ++ errors = addError(errors, ae); ++ } ++ ++ if (sslmode == SslMode.ALLOW && db.requiresSsl()) { ++ // Allow tries to connect with non-ssl first, and it always throws the first error even after try SSL. ++ // "If SSL was expected to fail" (e.g. invalid certificate), and db requiresSsl, then ALLOW ++ // should fail as well ++ String caseName = ++ "sslmode=ALLOW and db " + db + " requires SSL, and there are expected SSL failures"; ++ if (errors == null) { ++ if (e != null) { ++ Assert.fail(caseName + " ==> connection should be upgraded to SSL with no failures"); ++ } + } else { +- assertTrue("expected: " + exmsg + " actual: " + ex.getMessage(), +- ex.getMessage().matches(exmsg)); ++ if (e == null) { ++ Assert.fail(caseName + " ==> connection should fail"); ++ } ++ Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", ++ PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); ++ } ++ // ALLOW is ok ++ return; ++ } ++ ++ if (errors == null) { ++ if (e == null) { ++ // Assume "no exception" was expected. ++ // The cases like "successfully connected in sslmode=DISABLE to SSLONLY db" ++ // should be handled with assertions above + return; + } ++ Assert.fail("SQLException present when it was not expected"); ++ } ++ ++ AssertionError firstError = errors.get(0); ++ if (errors.size() == 1) { ++ throw firstError; ++ } ++ ++ for (int i = 1; i < errors.size(); i++) { ++ AssertionError error = errors.get(i); ++ // addSuppressed is Java 1.7+ ++ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1" ++ firstError.addSuppressed(error); ++ //#endif ++ error.printStackTrace(); + } +- } + +- protected String certdir; +- protected String connstr; +- protected String sslmode; +- protected boolean goodclient; +- protected boolean goodserver; +- protected String prefix; +- protected Object[] expected; +- +- private String makeConnStr(String sslmode, boolean goodclient, boolean goodserver) { +- return connstr +- + "&sslmode=" + sslmode +- + "&sslcert=" + certdir + "/" + prefix + (goodclient ? "goodclient.crt" : "badclient.crt") +- + "&sslkey=" + certdir + "/" + prefix + (goodclient ? "goodclient.pk8" : "badclient.pk8") +- + "&sslrootcert=" + certdir + "/" + prefix + (goodserver ? "goodroot.crt" : "badroot.crt") +- // + "&sslfactory=org.postgresql.ssl.NonValidatingFactory" +- + "&loglevel=" + TestUtil.getLogLevel(); ++ throw firstError; + } + +- public SslTest(String name, String certdir, String connstr, String sslmode, +- boolean goodclient, boolean goodserver, String prefix, Object[] expected) { +- super(name); +- this.certdir = certdir; +- this.connstr = connstr; +- this.sslmode = sslmode; +- this.goodclient = goodclient; +- this.goodserver = goodserver; +- this.prefix = prefix; +- this.expected = expected; ++ private List addError(List errors, AssertionError ae) { ++ if (errors == null) { ++ errors = new ArrayList(); ++ } ++ errors.add(ae); ++ return errors; + } + +- static TestSuite getSuite(Properties prop, String param) { +- File certDirFile = TestUtil.getFile(prop.getProperty("certdir")); +- String certdir = certDirFile.getAbsolutePath(); +- String sconnstr = prop.getProperty(param); +- String sprefix = prop.getProperty(param + "prefix"); +- String[] sslModes = {"disable", "allow", "prefer", "require", "verify-ca", "verify-full"}; +- +- TestSuite suite = new TestSuite(); +- Map expected = expectedmap.get(param); +- if (expected == null) { +- expected = defaultexpected; +- } +- for (String sslMode : sslModes) { +- suite.addTest(new SslTest(param + "-" + sslMode + "GG3", certdir, sconnstr, sslMode, +- true, true, sprefix, expected.get(sslMode + "GG"))); +- suite.addTest(new SslTest(param + "-" + sslMode + "GB3", certdir, sconnstr, sslMode, +- true, false, sprefix, expected.get(sslMode + "GB"))); +- suite.addTest(new SslTest(param + "-" + sslMode + "BG3", certdir, sconnstr, sslMode, +- false, true, sprefix, expected.get(sslMode + "BG"))); +- } +- return suite; ++ /** ++ * Checks server certificate validation error. ++ * ++ * @param e connection exception or null if no exception ++ * @return true when validation pass, false when the case is not applicable ++ * @throws AssertionError when exception does not match expectations ++ */ ++ private boolean assertServerCertificate(SQLException e) { ++ if (clientRootCertificate == ClientRootCertificate.GOOD ++ || (sslmode != SslMode.VERIFY_CA && sslmode != SslMode.VERIFY_FULL)) { ++ return false; ++ } ++ ++ String caseName = "Server certificate is " + clientRootCertificate + " + sslmode=" + sslmode; ++ if (e == null) { ++ Assert.fail(caseName + " should result in failure of server validation"); ++ } ++ ++ Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", ++ PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); ++ CertPathValidatorException validatorEx = findCause(e, CertPathValidatorException.class); ++ if (validatorEx == null) { ++ Assert.fail(caseName + " ==> exception should be caused by CertPathValidatorException," ++ + " but no CertPathValidatorException is present in the getCause chain"); ++ } ++ // getReason is Java 1.7+ ++ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1" ++ Assert.assertEquals(caseName + " ==> CertPathValidatorException.getReason", ++ "NO_TRUST_ANCHOR", validatorEx.getReason().toString()); ++ //#endif ++ return true; + } + +- protected void runTest() throws Throwable { +- driver(makeConnStr(sslmode, goodclient, goodserver), expected); ++ /** ++ * Checks hostname validation error. ++ * ++ * @param e connection exception or null if no exception ++ * @return true when validation pass, false when the case is not applicable ++ * @throws AssertionError when exception does not match expectations ++ */ ++ private boolean assertServerHostname(SQLException e) { ++ if (sslmode != SslMode.VERIFY_FULL || host != Hostname.BAD) { ++ return false; ++ } ++ ++ String caseName = "VERIFY_FULL + hostname that does not match server certificate"; ++ if (e == null) { ++ Assert.fail(caseName + " ==> CONNECTION_FAILURE expected"); ++ } ++ Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", ++ PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); ++ if (!e.getMessage().contains("PgjdbcHostnameVerifier")) { ++ Assert.fail(caseName + " ==> message should contain" ++ + " 'PgjdbcHostnameVerifier'. Actual message is " + e.getMessage()); ++ } ++ return true; + } + +- static Map> expectedmap; +- static TreeMap defaultexpected; +- +- // For some strange reason, the v2 driver begins these error messages by "Connection rejected: " +- // but the v3 does not. +- // Also, for v2 there are two spaces after FATAL:, and the message ends with "\n.". +- static String PG_HBA_ON = +- "(Connection rejected: )?FATAL: ?no pg_hba.conf entry for host .*, user .*, database .*, SSL on(?s-d:.*)"; +- static String PG_HBA_OFF = +- "(Connection rejected: )?FATAL: ?no pg_hba.conf entry for host .*, user .*, database .*, SSL off(?s-d:.*)"; +- static String FAILED = "The connection attempt failed."; +- static String BROKEN = +- "SSL error: (Broken pipe( \\(Write failed\\))?|Received fatal alert: unknown_ca|Connection reset|Protocol wrong type for socket)"; +- static String SSLMODEALLOW = "Invalid sslmode value: allow"; +- static String SSLMODEPREFER = "Invalid sslmode value: prefer"; +- // static String UNKNOWN = "SSL error: Broken pipe"; +- //static String UNKNOWN = "SSL error: Received fatal alert: unknown_ca"; +- static String ANY = ".*"; +- static String VALIDATOR = +- "SSL error: sun.security.validator.ValidatorException: PKIX path (building|validation) failed:.*"; +- static String HOSTNAME = "The hostname .* could not be verified."; +- +- static { +- defaultexpected = new TreeMap(); +- defaultexpected.put("disableGG", new Object[]{null, Boolean.FALSE}); +- defaultexpected.put("disableGB", new Object[]{null, Boolean.FALSE}); +- defaultexpected.put("disableBG", new Object[]{null, Boolean.FALSE}); +- defaultexpected.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- defaultexpected.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- defaultexpected.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- defaultexpected.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- defaultexpected.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- defaultexpected.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- defaultexpected.put("requireGG", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("requireGB", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("requireBG", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("verify-caGB", new Object[]{ANY, Boolean.TRUE}); +- defaultexpected.put("verify-caBG", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("verify-fullGG", new Object[]{null, Boolean.TRUE}); +- defaultexpected.put("verify-fullGB", new Object[]{ANY, Boolean.TRUE}); +- defaultexpected.put("verify-fullBG", new Object[]{null, Boolean.TRUE}); +- +- expectedmap = new TreeMap>(); +- TreeMap work; +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{null, Boolean.FALSE}); +- work.put("disableGB", new Object[]{null, Boolean.FALSE}); +- work.put("disableBG", new Object[]{null, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("requireGG", new Object[]{ANY, Boolean.TRUE}); +- work.put("requireGB", new Object[]{ANY, Boolean.TRUE}); +- work.put("requireBG", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{ANY, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{ANY, Boolean.TRUE}); +- expectedmap.put("ssloff9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{null, Boolean.FALSE}); +- work.put("disableGB", new Object[]{null, Boolean.FALSE}); +- work.put("disableBG", new Object[]{null, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("requireGG", new Object[]{PG_HBA_ON, Boolean.TRUE}); +- work.put("requireGB", new Object[]{PG_HBA_ON, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{PG_HBA_ON, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{PG_HBA_ON, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslhostnossl9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{null, Boolean.FALSE}); +- work.put("disableGB", new Object[]{null, Boolean.FALSE}); +- work.put("disableBG", new Object[]{null, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("requireGG", new Object[]{null, Boolean.TRUE}); +- work.put("requireGB", new Object[]{null, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslhostgh9", work); +- +- work = (TreeMap) work.clone(); +- work.put("disableGG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableGB", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableBG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- expectedmap.put("sslhostsslgh9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{null, Boolean.FALSE}); +- work.put("disableGB", new Object[]{null, Boolean.FALSE}); +- work.put("disableBG", new Object[]{null, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.FALSE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- work.put("requireGG", new Object[]{null, Boolean.TRUE}); +- work.put("requireGB", new Object[]{null, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{HOSTNAME, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslhostbh9", work); +- +- work = (TreeMap) work.clone(); +- work.put("disableGG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableGB", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableBG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.FALSE}); +- expectedmap.put("sslhostsslbh9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableGB", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableBG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("requireGG", new Object[]{null, Boolean.TRUE}); +- work.put("requireGB", new Object[]{null, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslhostsslcertgh9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableGB", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableBG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("requireGG", new Object[]{null, Boolean.TRUE}); +- work.put("requireGB", new Object[]{null, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{HOSTNAME, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslhostsslcertbh9", work); +- +- work = (TreeMap) defaultexpected.clone(); +- work.put("disableGG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableGB", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("disableBG", new Object[]{PG_HBA_OFF, Boolean.FALSE}); +- work.put("allowGG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowGB", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("allowBG", new Object[]{SSLMODEALLOW, Boolean.TRUE}); +- work.put("preferGG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferGB", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("preferBG", new Object[]{SSLMODEPREFER, Boolean.TRUE}); +- work.put("requireGG", new Object[]{null, Boolean.TRUE}); +- work.put("requireGB", new Object[]{null, Boolean.TRUE}); +- work.put("requireBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-caGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-caGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-caBG", new Object[]{BROKEN, Boolean.TRUE}); +- work.put("verify-fullGG", new Object[]{null, Boolean.TRUE}); +- work.put("verify-fullGB", new Object[]{VALIDATOR, Boolean.TRUE}); +- work.put("verify-fullBG", new Object[]{BROKEN, Boolean.TRUE}); +- expectedmap.put("sslcertgh9", work); +- +- work = (TreeMap) work.clone(); +- work.put("verify-fullGG", new Object[]{HOSTNAME, Boolean.TRUE}); +- expectedmap.put("sslcertbh9", work); ++ /** ++ * Checks client certificate validation error. ++ * ++ * @param e connection exception or null if no exception ++ * @return true when validation pass, false when the case is not applicable ++ * @throws AssertionError when exception does not match expectations ++ */ ++ private boolean assertClientCertificate(SQLException e) { ++ if (db.requiresClientCert() && clientCertificate == ClientCertificate.EMPTY) { ++ String caseName = ++ "client certificate was not sent and database " + db + " requires client certificate"; ++ assertClientCertRequired(e, caseName); ++ return true; ++ } + ++ if (clientCertificate != ClientCertificate.BAD) { ++ return false; ++ } ++ // Server verifies certificate no matter how it is configured, so sending BAD one ++ // is doomed to fail ++ String caseName = "BAD client certificate, and database " + db + " requires one"; ++ if (e == null) { ++ Assert.fail(caseName + " should result in failure of client validation"); ++ } ++ Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", ++ PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); ++ ++ // Two exceptions are possible ++ // SSLHandshakeException: Received fatal alert: unknown_ca ++ // SocketException: broken pipe (write failed) ++ ++ SocketException brokenPipe = findCause(e, SocketException.class); ++ SSLHandshakeException handshakeException = findCause(e, SSLHandshakeException.class); ++ ++ if (brokenPipe == null && handshakeException == null) { ++ Assert.fail(caseName + " ==> exception should be caused by SocketException(broken pipe)" ++ + " or SSLHandshakeException. No exceptions of such kind are present in the getCause chain"); ++ } ++ if (brokenPipe != null && !brokenPipe.getMessage().contains("Broken pipe")) { ++ Assert.fail( ++ caseName + " ==> server should have terminated the connection (broken pipe expected)" ++ + ", actual exception was " + brokenPipe.getMessage()); ++ } ++ if (handshakeException != null && !handshakeException.getMessage().contains("unknown_ca")) { ++ Assert.fail( ++ caseName + " ==> server should have terminated the connection (expected 'unknown_ca')" ++ + ", actual exception was " + handshakeException.getMessage()); ++ } ++ return true; + } + ++ private static T findCause(Throwable t, Class cause) { ++ while (t != null) { ++ if (cause.isInstance(t)) { ++ return (T) t; ++ } ++ t = t.getCause(); ++ } ++ return null; ++ } ++ ++ ++ @Test ++ public void run() throws SQLException { ++ if (con == null) { ++ // e.g. expected failure to connect ++ return; ++ } ++ ResultSet rs = con.createStatement().executeQuery("select ssl_is_used()"); ++ Assert.assertTrue("select ssl_is_used() should return a row", rs.next()); ++ boolean sslUsed = rs.getBoolean(1); ++ if (sslmode == SslMode.ALLOW) { ++ Assert.assertEquals("ssl_is_used: ", ++ db.requiresSsl(), ++ sslUsed); ++ } else { ++ Assert.assertEquals("ssl_is_used: ", ++ sslmode != SslMode.DISABLE && !db.rejectsSsl(), ++ sslUsed); ++ } ++ TestUtil.closeQuietly(rs); ++ } + + } +diff --git a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java +index 19c3a7c532..cf48916f42 100644 +--- a/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java ++++ b/pgjdbc/src/test/java/org/postgresql/test/ssl/SslTestSuite.java +@@ -5,43 +5,14 @@ + + package org.postgresql.test.ssl; + +-import org.postgresql.test.TestUtil; +- +-import junit.framework.TestSuite; +- +-import java.util.Properties; +- +-public class SslTestSuite extends TestSuite { +- private static Properties prop; +- +- private static void add(TestSuite suite, String param) { +- if (prop.getProperty(param, "").equals("")) { +- System.out.println("Skipping " + param + "."); +- } else { +- suite.addTest(SslTest.getSuite(prop, param)); +- } +- } +- +- /* +- * The main entry point for JUnit +- */ +- public static TestSuite suite() throws Exception { +- TestSuite suite = new TestSuite(); +- prop = TestUtil.loadPropertyFiles("ssltest.properties"); +- add(suite, "ssloff9"); +- add(suite, "sslhostnossl9"); +- +- String[] hostModes = {"sslhost", "sslhostssl", "sslhostsslcert", "sslcert"}; +- String[] certModes = {"gh", "bh"}; +- +- for (String hostMode : hostModes) { +- for (String certMode : certModes) { +- add(suite, hostMode + certMode + "9"); +- } +- } +- +- TestUtil.initDriver(); +- +- return suite; +- } ++import org.junit.runner.RunWith; ++import org.junit.runners.Suite; ++ ++@RunWith(Suite.class) ++@Suite.SuiteClasses({ ++ LibPQFactoryHostNameTest.class, ++ CommonNameVerifierTest.class, ++ SslTest.class ++}) ++public class SslTestSuite { + } +diff --git a/ssltest.properties b/ssltest.properties +index 21d1d45a40..492399a428 100644 +--- a/ssltest.properties ++++ b/ssltest.properties +@@ -1,32 +1,2 @@ +- +- + certdir=certdir +- +-# Uncomment to enable testing of SingleCertValidatingFactory +-#testsinglecertfactory=true +- +-ssloff9= +-ssloff9prefix= +- +-#sslhostnossl9=jdbc:postgresql://localhost:5432/hostnossldb?sslpassword=sslpwd +-sslhostnossl9prefix= +- +-#sslhostgh9=jdbc:postgresql://localhost:5432/hostdb?sslpassword=sslpwd +-sslhostgh9prefix= +-#sslhostbh9=jdbc:postgresql://127.0.0.1:5432/hostdb?sslpassword=sslpwd +-sslhostbh9prefix= +- +-#sslhostsslgh9=jdbc:postgresql://localhost:5432/hostssldb?sslpassword=sslpwd +-sslhostsslgh9prefix= +-#sslhostsslbh9=jdbc:postgresql://127.0.0.1:5432/hostssldb?sslpassword=sslpwd +-sslhostsslbh9prefix= +- +-#sslhostsslcertgh9=jdbc:postgresql://localhost:5432/hostsslcertdb?sslpassword=sslpwd +-sslhostsslcertgh9prefix= +-#sslhostsslcertbh9=jdbc:postgresql://127.0.0.1:5432/hostsslcertdb?sslpassword=sslpwd +-sslhostsslcertbh9prefix= +- +-#sslcertgh9=jdbc:postgresql://localhost:5432/certdb?sslpassword=sslpwd +-sslcertgh9prefix= +-#sslcertbh9=jdbc:postgresql://127.0.0.1:5432/certdb?sslpassword=sslpwd +-sslcertbh9prefix= +\ No newline at end of file ++#enable_ssl_tests=true \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_5.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_5.diff new file mode 100644 index 00000000..f75a22af --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue107_5.diff @@ -0,0 +1,835 @@ +diff --git a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java +index 1f2a7c4baec..9d636200d61 100644 +--- a/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java ++++ b/distribution/src/main/release/samples/throttling/src/main/java/demo/throttling/client/Client.java +@@ -112,7 +112,6 @@ public static void main(String[] args) throws Exception { + Map properties = new HashMap<>(); + properties.put("bus.jmx.usePlatformMBeanServer", Boolean.TRUE); + properties.put("bus.jmx.enabled", Boolean.TRUE); +- properties.put("bus.jmx.createMBServerConnectorFactory", Boolean.FALSE); + Bus b = new CXFBusFactory().createBus(null, properties); + MetricRegistry registry = new MetricRegistry(); + CodahaleMetricsProvider.setupJMXReporter(b, registry); +diff --git a/distribution/src/main/release/samples/wsdl_first/src/main/resources/server-applicationContext.xml b/distribution/src/main/release/samples/wsdl_first/src/main/resources/server-applicationContext.xml +index 8bf5109ec95..391f97c624e 100644 +--- a/distribution/src/main/release/samples/wsdl_first/src/main/resources/server-applicationContext.xml ++++ b/distribution/src/main/release/samples/wsdl_first/src/main/resources/server-applicationContext.xml +@@ -45,4 +45,4 @@ + + + +- +\ No newline at end of file ++ +diff --git a/rt/management/pom.xml b/rt/management/pom.xml +index f63f3bfa6d7..19ccf6ada0c 100644 +--- a/rt/management/pom.xml ++++ b/rt/management/pom.xml +@@ -34,8 +34,7 @@ + org.apache.cxf.management + + javax.xml.bind*;version="${cxf.osgi.javax.bind.version}", +- javax.annotation*;version="${cxf.osgi.javax.annotation.version}", +- sun.rmi*;resolution:=optional ++ javax.annotation*;version="${cxf.osgi.javax.annotation.version}" + + + +diff --git a/rt/management/src/main/java/org/apache/cxf/management/jmx/InstrumentationManagerImpl.java b/rt/management/src/main/java/org/apache/cxf/management/jmx/InstrumentationManagerImpl.java +index ee7f0a71fee..d898a4b6f5f 100644 +--- a/rt/management/src/main/java/org/apache/cxf/management/jmx/InstrumentationManagerImpl.java ++++ b/rt/management/src/main/java/org/apache/cxf/management/jmx/InstrumentationManagerImpl.java +@@ -19,7 +19,6 @@ + + package org.apache.cxf.management.jmx; + +-import java.io.IOException; + import java.lang.management.ManagementFactory; + import java.util.HashMap; + import java.util.HashSet; +@@ -53,31 +52,25 @@ + import org.apache.cxf.management.ManagedComponent; + import org.apache.cxf.management.ManagementConstants; + import org.apache.cxf.management.jmx.export.runtime.ModelMBeanAssembler; +-import org.apache.cxf.management.jmx.type.JMXConnectorPolicyType; + + /** + * The manager class for the JMXManagedComponent which hosts the JMXManagedComponents. + */ +-public class InstrumentationManagerImpl extends JMXConnectorPolicyType ++public class InstrumentationManagerImpl + implements InstrumentationManager, BusLifeCycleListener { + private static final Logger LOG = LogUtils.getL7dLogger(InstrumentationManagerImpl.class); + + private static Map mbeanServerIDMap = new HashMap<>(); + + private Bus bus; +- private MBServerConnectorFactory mcf; + private MBeanServer mbs; + private Set busMBeans = new HashSet<>(); + private boolean connectFailed; + private String persistentBusId; +- private Map environment; + +- /** +- * For backward compatibility, {@link #createMBServerConnectorFactory} is true by default. +- */ +- private boolean createMBServerConnectorFactory = true; + private String mbeanServerName = ManagementConstants.DEFAULT_DOMAIN_NAME; + private boolean usePlatformMBeanServer; ++ private boolean enabled; + + public InstrumentationManagerImpl() { + super(); +@@ -119,16 +112,16 @@ public void setServerName(String s) { + mbeanServerName = s; + } + +- public void setCreateMBServerConnectorFactory(boolean createMBServerConnectorFactory) { +- this.createMBServerConnectorFactory = createMBServerConnectorFactory; +- } +- + public void setUsePlatformMBeanServer(Boolean flag) { + usePlatformMBeanServer = flag; + } + +- public void setEnvironment(Map env) { +- environment = env; ++ public void setEnabled(boolean enabled) { ++ this.enabled = enabled; ++ } ++ ++ public boolean isEnabled() { ++ return enabled; + } + + @Deprecated +@@ -139,7 +132,6 @@ public void register() { + public void init() { + if (bus != null && bus.getExtension(MBeanServer.class) != null) { + enabled = true; +- createMBServerConnectorFactory = false; + mbs = bus.getExtension(MBeanServer.class); + } + if (isEnabled()) { +@@ -168,21 +160,6 @@ public void init() { + } + } + +- if (createMBServerConnectorFactory) { +- mcf = MBServerConnectorFactory.getInstance(); +- mcf.setMBeanServer(mbs); +- mcf.setThreaded(isThreaded()); +- mcf.setDaemon(isDaemon()); +- mcf.setServiceUrl(getJMXServiceURL()); +- mcf.setEnvironment(environment); +- try { +- mcf.createConnector(); +- } catch (IOException ex) { +- connectFailed = true; +- LOG.log(Level.SEVERE, "START_CONNECTOR_FAILURE_MSG", new Object[] {ex}); +- } +- } +- + if (!connectFailed && null != bus) { + try { + //Register Bus here since we can guarantee that Instrumentation +@@ -282,14 +259,6 @@ public void shutdown() { + return; + } + +- if (mcf != null) { +- try { +- mcf.destroy(); +- } catch (IOException ex) { +- LOG.log(Level.SEVERE, "STOP_CONNECTOR_FAILURE_MSG", new Object[] {ex}); +- } +- } +- + //Using the array to hold the busMBeans to avoid the CurrentModificationException + Object[] mBeans = busMBeans.toArray(); + for (Object name : mBeans) { +@@ -400,12 +369,7 @@ private void readJMXProperties(Bus b) { + getBusProperty(b, "bus.jmx.serverName", mbeanServerName); + usePlatformMBeanServer = + getBusProperty(b, "bus.jmx.usePlatformMBeanServer", usePlatformMBeanServer); +- createMBServerConnectorFactory = +- getBusProperty(b, "bus.jmx.createMBServerConnectorFactory", createMBServerConnectorFactory); +- daemon = getBusProperty(b, "bus.jmx.daemon", daemon); +- threaded = getBusProperty(b, "bus.jmx.threaded", threaded); + enabled = getBusProperty(b, "bus.jmx.enabled", enabled); +- jmxServiceURL = getBusProperty(b, "bus.jmx.JMXServiceURL", jmxServiceURL); + } + } + +diff --git a/rt/management/src/main/java/org/apache/cxf/management/jmx/MBServerConnectorFactory.java b/rt/management/src/main/java/org/apache/cxf/management/jmx/MBServerConnectorFactory.java +deleted file mode 100644 +index 5eb7059b8fb..00000000000 +--- a/rt/management/src/main/java/org/apache/cxf/management/jmx/MBServerConnectorFactory.java ++++ /dev/null +@@ -1,260 +0,0 @@ +-/** +- * Licensed to the Apache Software Foundation (ASF) under one +- * or more contributor license agreements. See the NOTICE file +- * distributed with this work for additional information +- * regarding copyright ownership. The ASF licenses this file +- * to you under the Apache License, Version 2.0 (the +- * "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, +- * software distributed under the License is distributed on an +- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +- * KIND, either express or implied. See the License for the +- * specific language governing permissions and limitations +- * under the License. +- */ +- +-package org.apache.cxf.management.jmx; +- +-import java.io.IOException; +-import java.net.URI; +-import java.net.URISyntaxException; +-import java.rmi.AccessException; +-import java.rmi.AlreadyBoundException; +-import java.rmi.NotBoundException; +-import java.rmi.Remote; +-import java.rmi.RemoteException; +-import java.util.Map; +-import java.util.logging.Level; +-import java.util.logging.Logger; +- +-import javax.management.MBeanServer; +-import javax.management.MBeanServerFactory; +-import javax.management.remote.JMXConnectorServer; +-import javax.management.remote.JMXServiceURL; +-import javax.management.remote.rmi.RMIConnectorServer; +-import javax.management.remote.rmi.RMIJRMPServerImpl; +- +-import org.apache.cxf.common.logging.LogUtils; +- +- +- +-/** +- * Deal with the MBeanServer Connections +- * +- */ +-public final class MBServerConnectorFactory { +- +- public static final String DEFAULT_SERVICE_URL = "service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi"; +- +- private static final Logger LOG = LogUtils.getL7dLogger(MBServerConnectorFactory.class); +- +- private static MBeanServer server; +- +- private static String serviceUrl = DEFAULT_SERVICE_URL; +- +- private static Map environment; +- +- private static boolean threaded; +- +- private static boolean daemon; +- +- private static JMXConnectorServer connectorServer; +- +- private static Remote remoteServerStub; +- +- private static RMIJRMPServerImpl rmiServer; +- +- private static class MBServerConnectorFactoryHolder { +- private static final MBServerConnectorFactory INSTANCE = +- new MBServerConnectorFactory(); +- } +- +- private static class MBeanServerHolder { +- private static final MBeanServer INSTANCE = +- MBeanServerFactory.createMBeanServer(); +- } +- +- private MBServerConnectorFactory() { +- +- } +- +- static int getServerPort(final String url) { +- int portStart = url.indexOf("localhost") + 10; +- int portEnd; +- int port = 0; +- if (portStart > 0) { +- portEnd = indexNotOfNumber(url, portStart); +- if (portEnd > portStart) { +- final String portString = url.substring(portStart, portEnd); +- port = Integer.parseInt(portString); +- } +- } +- return port; +- } +- +- private static int indexNotOfNumber(String str, int index) { +- int i = 0; +- for (i = index; i < str.length(); i++) { +- if (str.charAt(i) < '0' || str.charAt(i) > '9') { +- return i; +- } +- } +- return -1; +- } +- +- public static MBServerConnectorFactory getInstance() { +- return MBServerConnectorFactoryHolder.INSTANCE; +- } +- +- public void setMBeanServer(MBeanServer ms) { +- server = ms; +- } +- +- public void setServiceUrl(String url) { +- serviceUrl = url; +- } +- +- public void setEnvironment(Map env) { +- environment = env; +- } +- +- public void setThreaded(boolean fthread) { +- threaded = fthread; +- } +- +- public void setDaemon(boolean fdaemon) { +- daemon = fdaemon; +- } +- +- +- public void createConnector() throws IOException { +- +- if (server == null) { +- server = MBeanServerHolder.INSTANCE; +- } +- +- // Create the JMX service URL. +- final JMXServiceURL url = new JMXServiceURL(serviceUrl); +- +- // if the URL is localhost, start up an Registry +- if (serviceUrl.indexOf("localhost") > -1 +- && url.getProtocol().compareToIgnoreCase("rmi") == 0) { +- try { +- int port = getRegistryPort(serviceUrl); +- new JmxRegistry(port, getBindingName(url)); +- +- } catch (Exception ex) { +- LOG.log(Level.SEVERE, "CREATE_REGISTRY_FAULT_MSG", new Object[]{ex}); +- } +- } +- +- rmiServer = new RMIJRMPServerImpl(getServerPort(serviceUrl), null, null, environment); +- +- // Create the connector server now. +- connectorServer = new RMIConnectorServer(url, environment, rmiServer, server); +- +- if (threaded) { +- // Start the connector server asynchronously (in a separate thread). +- Thread connectorThread = new Thread() { +- public void run() { +- try { +- connectorServer.start(); +- remoteServerStub = rmiServer.toStub(); +- } catch (IOException ex) { +- LOG.log(Level.SEVERE, "START_CONNECTOR_FAILURE_MSG", new Object[]{ex}); +- } +- } +- }; +- +- connectorThread.setName("JMX Connector Thread [" + serviceUrl + "]"); +- connectorThread.setDaemon(daemon); +- connectorThread.start(); +- } else { +- // Start the connector server in the same thread. +- connectorServer.start(); +- remoteServerStub = rmiServer.toStub(); +- } +- +- if (LOG.isLoggable(Level.INFO)) { +- LOG.info("JMX connector server started: " + connectorServer); +- } +- } +- +- static int getRegistryPort(final String url) { +- int serverStart = url.indexOf("/jndi/rmi://"); +- final String serverPart = url.substring(serverStart + 12); +- int portStart = serverPart.indexOf(':') + 1; +- +- int portEnd; +- int port = 0; +- if (portStart > 0) { +- portEnd = indexNotOfNumber(serverPart, portStart); +- if (portEnd > portStart) { +- final String portString = serverPart.substring(portStart, portEnd); +- port = Integer.parseInt(portString); +- } +- } +- return port; +- } +- +- protected static String getBindingName(final JMXServiceURL jmxServiceURL) { +- final String urlPath = jmxServiceURL.getURLPath(); +- +- try { +- if (urlPath.startsWith("/jndi/")) { +- return new URI(urlPath.substring(6)).getPath() +- .replaceAll("^/+", "").replaceAll("/+$", ""); +- } +- } catch (URISyntaxException e) { +- // ignore +- } +- +- return "jmxrmi"; // use the default +- } +- +- public void destroy() throws IOException { +- connectorServer.stop(); +- if (LOG.isLoggable(Level.INFO)) { +- LOG.info("JMX connector server stopped: " + connectorServer); +- } +- } +- +- /* +- * Better to use the internal API than re-invent the wheel. +- */ +- @SuppressWarnings("restriction") +- private class JmxRegistry extends sun.rmi.registry.RegistryImpl { +- private final String lookupName; +- +- JmxRegistry(final int port, final String lookupName) throws RemoteException { +- super(port); +- this.lookupName = lookupName; +- } +- +- @Override +- public Remote lookup(String s) throws RemoteException, NotBoundException { +- return lookupName.equals(s) ? remoteServerStub : null; +- } +- +- @Override +- public void bind(String s, Remote remote) throws RemoteException, AlreadyBoundException, AccessException { +- } +- +- @Override +- public void unbind(String s) throws RemoteException, NotBoundException, AccessException { +- } +- +- @Override +- public void rebind(String s, Remote remote) throws RemoteException, AccessException { +- } +- +- @Override +- public String[] list() throws RemoteException { +- return new String[] {lookupName}; +- } +- } +-} +diff --git a/rt/management/src/main/java/org/apache/cxf/management/utils/ManagementConsole.java b/rt/management/src/main/java/org/apache/cxf/management/utils/ManagementConsole.java +index 3fa2fe2891c..3739ffaa915 100644 +--- a/rt/management/src/main/java/org/apache/cxf/management/utils/ManagementConsole.java ++++ b/rt/management/src/main/java/org/apache/cxf/management/utils/ManagementConsole.java +@@ -42,7 +42,7 @@ + public final class ManagementConsole { + private static MBeanServerConnection mbsc; + private static final String DEFAULT_JMXSERVICE_URL = +- "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"; ++ "service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi"; + private static final Logger LOG = LogUtils.getL7dLogger(ManagementConsole.class); + + String jmxServerURL; +diff --git a/rt/management/src/test/java/org/apache/cxf/management/InstrumentationManagerTest.java b/rt/management/src/test/java/org/apache/cxf/management/InstrumentationManagerTest.java +index 9ebc7ca4fe2..c505835d10a 100644 +--- a/rt/management/src/test/java/org/apache/cxf/management/InstrumentationManagerTest.java ++++ b/rt/management/src/test/java/org/apache/cxf/management/InstrumentationManagerTest.java +@@ -158,7 +158,6 @@ public void testInstrumentBusWithBusProperties() { + assertNotNull("Instrumentation Manager of cxf1 should not be null", im1); + + assertTrue(im1.isEnabled()); +- assertEquals("service:jmx:rmi:///jndi/rmi://localhost:9914/jmxrmi", im1.getJMXServiceURL()); + + cxf2 = (Bus)context.getBean("cxf2"); + InstrumentationManagerImpl im2 = +@@ -166,7 +165,6 @@ public void testInstrumentBusWithBusProperties() { + assertNotNull("Instrumentation Manager of cxf2 should not be null", im2); + + assertFalse(im2.isEnabled()); +- assertEquals("service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi", im2.getJMXServiceURL()); + + } finally { + if (cxf1 != null) { +diff --git a/rt/management/src/test/java/org/apache/cxf/management/jmx/BusRegistrationTest.java b/rt/management/src/test/java/org/apache/cxf/management/jmx/BusRegistrationTest.java +index e9f9308289a..6c458f3085b 100644 +--- a/rt/management/src/test/java/org/apache/cxf/management/jmx/BusRegistrationTest.java ++++ b/rt/management/src/test/java/org/apache/cxf/management/jmx/BusRegistrationTest.java +@@ -64,12 +64,6 @@ public void tearDown() throws Exception { + } + } + +- @Test +- public void testRegisterMultipleBuses() throws Exception { +- // classic external IM-bean +- testRegisterMultipleBuses("managed-spring.xml"); +- } +- + @Test + public void testRegisterMultipleBuses2() throws Exception { + // integrated IM configuration in bus +diff --git a/rt/management/src/test/java/org/apache/cxf/management/jmx/JMXManagedComponentManagerTest.java b/rt/management/src/test/java/org/apache/cxf/management/jmx/JMXManagedComponentManagerTest.java +index d516da39c06..20a7b091435 100644 +--- a/rt/management/src/test/java/org/apache/cxf/management/jmx/JMXManagedComponentManagerTest.java ++++ b/rt/management/src/test/java/org/apache/cxf/management/jmx/JMXManagedComponentManagerTest.java +@@ -28,7 +28,6 @@ + import javax.management.ObjectName; + + import org.apache.cxf.management.jmx.export.AnnotationTestInstrumentation; +-import org.apache.cxf.testutil.common.TestUtil; + + import org.junit.After; + import org.junit.Before; +@@ -38,17 +37,13 @@ + import static org.junit.Assert.fail; + + public class JMXManagedComponentManagerTest { +- private static final String PORT = TestUtil.getPortNumber(JMXManagedComponentManagerTest.class); + private static final String NAME_ATTRIBUTE = "Name"; + private InstrumentationManagerImpl manager; + + @Before + public void setUp() throws Exception { + manager = new InstrumentationManagerImpl(); +- manager.setDaemon(false); +- manager.setThreaded(true); + manager.setEnabled(true); +- manager.setJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + PORT + "/jmxrmi"); + manager.init(); + //Wait for MBeanServer connector to be initialized on separate thread. + Thread.sleep(2000); +@@ -61,10 +56,6 @@ public void tearDown() throws Exception { + + @Test + public void testRegisterInstrumentation() throws Exception { +- //manager.setDaemon(false); +- //manager.setThreaded(false); +- //manager.setJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi"); +- //manager.init(); + + AnnotationTestInstrumentation im = new AnnotationTestInstrumentation(); + ObjectName name = new ObjectName("org.apache.cxf:type=foo,name=bar"); +@@ -128,13 +119,7 @@ public void testBusLifecycleListener() throws Exception { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + + this.manager = new InstrumentationManagerImpl(); +- this.manager.setDaemon(false); +- // Turn threading off so that we get the exception in this thread +- // and the manager is set into a failed state if the connector +- // cannot be created. +- this.manager.setThreaded(false); + this.manager.setEnabled(true); +- this.manager.setJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + PORT + "/jmxrmi"); + this.manager.setServer(server); + this.manager.init(); + +@@ -161,13 +146,7 @@ public void testBusLifecycleListener() throws Exception { + } + + this.manager = new InstrumentationManagerImpl(); +- this.manager.setDaemon(false); +- // Turn threading off so that we get the exception in this thread +- // and the manager is set into a failed state if the connector +- // cannot be created. +- this.manager.setThreaded(false); + this.manager.setEnabled(true); +- this.manager.setJMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:" + PORT + "/jmxrmi"); + this.manager.setServer(server); + this.manager.init(); + +@@ -183,4 +162,4 @@ private ObjectName registerStandardMBean(String name) throws Exception { + this.manager.register(hw, oName); + return oName; + } +-} +\ No newline at end of file ++} +diff --git a/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java b/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java +deleted file mode 100644 +index 468ec0cbfaf..00000000000 +--- a/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java ++++ /dev/null +@@ -1,55 +0,0 @@ +-/** +- * Licensed to the Apache Software Foundation (ASF) under one +- * or more contributor license agreements. See the NOTICE file +- * distributed with this work for additional information +- * regarding copyright ownership. The ASF licenses this file +- * to you under the Apache License, Version 2.0 (the +- * "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, +- * software distributed under the License is distributed on an +- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +- * KIND, either express or implied. See the License for the +- * specific language governing permissions and limitations +- * under the License. +- */ +-package org.apache.cxf.management.jmx; +- +-import javax.management.remote.JMXServiceURL; +- +-import org.junit.Assert; +-import org.junit.Test; +- +- +-public class MBServerConnectorFactoryTest { +- +- @Test +- public void testGetServerPort() throws Exception { +- Assert.assertEquals(9914, MBServerConnectorFactory.getServerPort( +- "service:jmx:rmi:///jndi/rmi://localhost:9914/jmxrmi")); +- +- Assert.assertEquals(10002, MBServerConnectorFactory.getServerPort( +- "service:jmx:rmi://localhost:10002/jndi/rmi://localhost:10001/jmxrmi")); +- } +- +- @Test +- public void testGetRegistryPort() throws Exception { +- Assert.assertEquals(9914, MBServerConnectorFactory.getRegistryPort( +- "service:jmx:rmi:///jndi/rmi://localhost:9914/jmxrmi")); +- +- Assert.assertEquals(10001, MBServerConnectorFactory.getRegistryPort( +- "service:jmx:rmi://localhost:10002/jndi/rmi://localhost:10001/jmxrmi")); +- } +- +- @Test +- public void testGetBindingName() throws Exception { +- Assert.assertEquals("jmxrmi", MBServerConnectorFactory.getBindingName( +- new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi"))); +- +- Assert.assertEquals("cxf-jmxrmi", MBServerConnectorFactory.getBindingName( +- new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9913/cxf-jmxrmi"))); +- } +-} +\ No newline at end of file +diff --git a/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorTest.java b/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorTest.java +deleted file mode 100644 +index c01e10fc6f2..00000000000 +--- a/rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorTest.java ++++ /dev/null +@@ -1,57 +0,0 @@ +-/** +- * Licensed to the Apache Software Foundation (ASF) under one +- * or more contributor license agreements. See the NOTICE file +- * distributed with this work for additional information +- * regarding copyright ownership. The ASF licenses this file +- * to you under the Apache License, Version 2.0 (the +- * "License"); you may not use this file except in compliance +- * with the License. You may obtain a copy of the License at +- * +- * http://www.apache.org/licenses/LICENSE-2.0 +- * +- * Unless required by applicable law or agreed to in writing, +- * software distributed under the License is distributed on an +- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +- * KIND, either express or implied. See the License for the +- * specific language governing permissions and limitations +- * under the License. +- */ +- +-package org.apache.cxf.management.jmx; +- +- +- +-import javax.management.MBeanServer; +-import javax.management.MBeanServerFactory; +- +-import org.apache.cxf.testutil.common.TestUtil; +- +-import org.junit.Test; +- +-import static org.junit.Assert.fail; +- +- +-public class MBServerConnectorTest { +- private static final String PORT = TestUtil.getPortNumber(MBServerConnectorTest.class); +- +- @Test +- public void testMBServerConnector() { +- MBServerConnectorFactory mcf; +- MBeanServer mbs; +- mbs = MBeanServerFactory.createMBeanServer("test"); +- mcf = MBServerConnectorFactory.getInstance(); +- mcf.setMBeanServer(mbs); +- mcf.setThreaded(true); +- mcf.setDaemon(true); +- mcf.setServiceUrl("service:jmx:rmi:///jndi/rmi://localhost:" + PORT + "/jmxrmi"); +- try { +- mcf.createConnector(); +- Thread.sleep(1000); +- mcf.destroy(); +- } catch (Exception ex) { +- ex.printStackTrace(); +- fail("Some Exception happened to MBServerConnectorTest"); +- } +- } +- +-} +diff --git a/rt/management/src/test/resources/managed-spring.xml b/rt/management/src/test/resources/managed-spring.xml +index 81808a54a5a..683c1b3ab32 100644 +--- a/rt/management/src/test/resources/managed-spring.xml ++++ b/rt/management/src/test/resources/managed-spring.xml +@@ -24,11 +24,7 @@ + xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> + + +- + +- +- +- + + + +diff --git a/rt/management/src/test/resources/managed-spring3.xml b/rt/management/src/test/resources/managed-spring3.xml +index 83add997fe0..57009b7cf40 100644 +--- a/rt/management/src/test/resources/managed-spring3.xml ++++ b/rt/management/src/test/resources/managed-spring3.xml +@@ -26,9 +26,5 @@ + + + +- +- +- +- + + +\ No newline at end of file +diff --git a/rt/management/src/test/resources/no-connector-spring.xml b/rt/management/src/test/resources/no-connector-spring.xml +index f6c197d9a2c..565d92dffe0 100644 +--- a/rt/management/src/test/resources/no-connector-spring.xml ++++ b/rt/management/src/test/resources/no-connector-spring.xml +@@ -24,6 +24,5 @@ + + + +- + + +\ No newline at end of file +diff --git a/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/managed-manager-bean.xml b/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/managed-manager-bean.xml +index 8e01539b360..7571a48693d 100644 +--- a/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/managed-manager-bean.xml ++++ b/rt/ws/rm/src/test/java/org/apache/cxf/ws/rm/managed-manager-bean.xml +@@ -26,9 +26,6 @@ + + + +- +- +- + + + +diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/ManagedBusTest.java b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/ManagedBusTest.java +index dbde562570a..a40c61d394c 100644 +--- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/ManagedBusTest.java ++++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/ManagedBusTest.java +@@ -98,8 +98,6 @@ public void testManagedSpringBus() throws Exception { + assertNotNull(im); + + InstrumentationManagerImpl imi = (InstrumentationManagerImpl)im; +- assertEquals("service:jmx:rmi:///jndi/rmi://localhost:9913/jmxrmi", +- imi.getJMXServiceURL()); + assertFalse(imi.isEnabled()); + assertNull(imi.getMBeanServer()); + +@@ -127,8 +125,6 @@ private void doManagedBusTest(Bus bus, String expect, String reject, int port) t + InstrumentationManager im = bus.getExtension(InstrumentationManager.class); + assertNotNull(im); + InstrumentationManagerImpl imi = (InstrumentationManagerImpl)im; +- assertEquals("service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi", +- imi.getJMXServiceURL()); + assertTrue(imi.isEnabled()); + assertNotNull(imi.getMBeanServer()); + +diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/counter-spring.xml b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/counter-spring.xml +index e5cc90f9a17..b3d516022d9 100644 +--- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/counter-spring.xml ++++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/counter-spring.xml +@@ -24,7 +24,6 @@ + + + +- + + + +diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-bus.xml b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-bus.xml +index 7304a399d39..fed391b4a60 100644 +--- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-bus.xml ++++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-bus.xml +@@ -24,7 +24,6 @@ + + + +- + + + +diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-spring.xml b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-spring.xml +index 4beb120a457..fed391b4a60 100644 +--- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-spring.xml ++++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/managed-spring.xml +@@ -24,7 +24,6 @@ + + + +- + + + +diff --git a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/persistent-id.xml b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/persistent-id.xml +index 118abfe7978..9219b56bcf3 100644 +--- a/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/persistent-id.xml ++++ b/systests/uncategorized/src/test/java/org/apache/cxf/systest/management/persistent-id.xml +@@ -24,7 +24,6 @@ + + + +- + + + +diff --git a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-client.xml b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-client.xml +index c0cd5b2700a..514038f02b8 100644 +--- a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-client.xml ++++ b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-client.xml +@@ -43,7 +43,5 @@ + + + +- +- + +- +\ No newline at end of file ++ +diff --git a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-server.xml b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-server.xml +index 823d8f0622e..1ee90cfbae4 100644 +--- a/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-server.xml ++++ b/systests/ws-rm/src/test/java/org/apache/cxf/systest/ws/rm/managed-server.xml +@@ -43,6 +43,5 @@ + + + +- + +- +\ No newline at end of file ++ \ No newline at end of file From 89ce30112325125f812e3d295f9b2131c6689686 Mon Sep 17 00:00:00 2001 From: Ilari Suhonen <52505120+bluelhf@users.noreply.github.com> Date: Wed, 7 Jul 2021 08:15:55 +0300 Subject: [PATCH 023/107] Fix typo in UnifiedDiff#spplyPatchTo(Predicate, List) (#127) * Fix typo in UnifiedDiff.java Changes UnifiedDiff#spplyPatchTo to UnifiedDiff#applyPatchTo * Fix typo in test Fixes the method invocation in the UnifiedDiff roundtrip test to account for the typo fix in UnifiedDiff --- .../main/java/com/github/difflib/unifieddiff/UnifiedDiff.java | 2 +- .../github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java index bdabc589..f2bb231d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java @@ -55,7 +55,7 @@ public String getTail() { return tail; } - public List spplyPatchTo(Predicate findFile, List originalLines) throws PatchFailedException { + public List applyPatchTo(Predicate findFile, List originalLines) throws PatchFailedException { UnifiedDiffFile file = files.stream() .filter(diff -> findFile.test(diff.getFromFile())) .findFirst().orElse(null); diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java index acc84e9a..d1892771 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java @@ -150,7 +150,7 @@ private void verify(List origLines, List revLines, // Patch fromUnifiedPatch = unifiedDiff.getFiles().get(0).getPatch(); // patchedLines = fromUnifiedPatch.applyTo(origLines); // } - patchedLines = unifiedDiff.spplyPatchTo(file -> originalFile.equals(file), origLines); + patchedLines = unifiedDiff.applyPatchTo(file -> originalFile.equals(file), origLines); assertEquals(revLines.size(), patchedLines.size()); for (int i = 0; i < revLines.size(); i++) { String l1 = revLines.get(i); From 8bb4a9a8b1209f461b320a16150fc738107701ae Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 8 Jul 2021 21:01:10 +0200 Subject: [PATCH 024/107] first implementation of meyers diff with linear space --- .../myers/MeyersDiffWithLinearSpace.java | 195 ++++++++++++++++++ .../difflib/algorithm/myers/MyersDiff.java | 3 +- .../myers/MeyersDiffWithLinearSpaceTest.java | 73 +++++++ .../algorithm/myers/MyersDiffTest.java | 1 - ...ithMeyersDiffWithLinearSpacePatchTest.java | 104 ++++++++++ .../unifieddiff/UnifiedDiffReaderTest.java | 20 ++ .../unifieddiff/problem_diff_issue122.diff | 26 +++ .../unifieddiff/problem_diff_issue123.diff | 94 +++++++++ .../src/test/resources/logging.properties | 2 +- 9 files changed, 514 insertions(+), 4 deletions(-) create mode 100644 java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java new file mode 100644 index 00000000..1dcae646 --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -0,0 +1,195 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmI; +import com.github.difflib.algorithm.DiffAlgorithmListener; +import com.github.difflib.patch.DeltaType; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiPredicate; + +/** + * + * @author tw + */ +public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { + + private final BiPredicate equalizer; + + public MeyersDiffWithLinearSpace() { + equalizer = Object::equals; + } + + public MeyersDiffWithLinearSpace(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + @Override + public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + DiffData data = new DiffData(source, target); + //shouldn't it be source.size() - 1? + buildScript(data, 0, source.size(), 0, target.size()); + return data.script; + } + + private void buildScript(DiffData data, int start1, int end1, int start2, int end2) { + final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); + if (middle == null + || middle.start == end1 && middle.diag == end1 - end2 + || middle.end == start1 && middle.diag == start1 - start2) { + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { + //script.append(new KeepCommand<>(left.charAt(i))); + ++i; + ++j; + } else { + //TODO: compress these commands. + if (end1 - start1 > end2 - start2) { + //script.append(new DeleteCommand<>(left.charAt(i))); + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + ++i; + } else { + //script.append(new InsertCommand<>(right.charAt(j))); + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + ++j; + } + } + } + } else { + buildScript(data, start1, middle.start, start2, middle.start - middle.diag); +// for (int i = middle.getStart(); i < middle.getEnd(); ++i) { +// script.append(new KeepCommand<>(left.charAt(i))); +// } + buildScript(data, middle.end, end1, middle.end - middle.diag, end2); + } + } + + private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + data.vDown[1 + offset] = start1; + data.vUp[1 + offset] = end1 + 1; + + for (int d = 0; d <= offset; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { + data.vDown[i] = data.vDown[i + 1]; + } else { + data.vDown[i] = data.vDown[i - 1] + 1; + } + + int x = data.vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (data.vUp[i - delta] <= data.vDown[i]) { + return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); + } + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d + || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { + data.vUp[i] = data.vUp[i + 1] - 1; + } else { + data.vUp[i] = data.vUp[i - 1]; + } + + int x = data.vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d) { + if (data.vUp[i] <= data.vDown[i + delta]) { + return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + + // According to Myers, this cannot happen + throw new IllegalStateException("could not find a diff path"); + } + + private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + class DiffData { + + final int size; + final int[] vDown; + final int[] vUp; + final List script; + final List source; + final List target; + + public DiffData(List source, List target) { + this.source = source; + this.target = target; + size = source.size() + target.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + script = new ArrayList<>(); + } + } + + class Snake { + + final int start; + final int end; + final int diag; + + public Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + } +} diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 631bc38c..f1345577 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -30,11 +30,10 @@ */ public final class MyersDiff implements DiffAlgorithmI { - private final BiPredicate DEFAULT_EQUALIZER = Object::equals; private final BiPredicate equalizer; public MyersDiff() { - equalizer = DEFAULT_EQUALIZER; + equalizer = Object::equals; } public MyersDiff(final BiPredicate equalizer) { diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java new file mode 100644 index 00000000..5462eb40 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.algorithm.DiffAlgorithmListener; +import com.github.difflib.patch.Patch; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author tw + */ +public class MeyersDiffWithLinearSpaceTest { + + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = Patch.generate(original, revised, new MeyersDiffWithLinearSpace().computeDiff(original, revised, null)); + assertNotNull(patch); + System.out.println(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate(original, revised, + new MeyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } + + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } + + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + System.out.println(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + System.out.println(logdata); + assertEquals(8, logdata.size()); + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 5b8348e6..1e233a8c 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -69,5 +69,4 @@ public void diffEnd() { System.out.println(logdata); assertEquals(8, logdata.size()); } - } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java new file mode 100644 index 00000000..2b060011 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -0,0 +1,104 @@ +package com.github.difflib.algorithm.myers; + +import com.github.difflib.patch.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.github.difflib.DiffUtils; + +public class WithMeyersDiffWithLinearSpacePatchTest { + + @Test + public void testPatch_Insert() { + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Delete() { + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Serializable() throws IOException, ClassNotFoundException { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); + + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index fdc80fd8..02e7c4d8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -364,4 +364,24 @@ public void testParseIssue117() throws IOException { // }); // }); } + + @Test + public void testParseIssue122() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } + + @Test + public void testParseIssue123() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff new file mode 100644 index 00000000..a2a382a0 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue122.diff @@ -0,0 +1,26 @@ + +diff -r fcd3ed3394f6 -r 135bdcb88b8d coders/wpg.c +--- a/coders/wpg.c Sun Nov 05 01:11:09 2017 +0100 ++++ b/coders/wpg.c Sun Nov 05 01:35:28 2017 +0100 +@@ -340,12 +340,15 @@ + + + if(RetVal==MagickFail) ++ { + (void) LogMagickEvent(CoderEvent,GetMagickModule(),"ImportImagePixelArea failed for row: %ld, bpp: %d", y, bpp); ++ return MagickFail; ++ } + +- if (!SyncImagePixels(image)) ++ if(!SyncImagePixels(image)) + { + (void) LogMagickEvent(CoderEvent,GetMagickModule(),"SyncImagePixels failed for row: %ld, bpp: %d", y, bpp); +- RetVal = MagickFail; ++ return MagickFail; + } + + return RetVal; + + + + diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff new file mode 100644 index 00000000..2228fc8a --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue123.diff @@ -0,0 +1,94 @@ +Index: src/java/test/org/apache/zookeeper/test/ACLTest.java +=================================================================== +--- src/java/test/org/apache/zookeeper/test/ACLTest.java (revision 1510080) ++++ src/java/test/org/apache/zookeeper/test/ACLTest.java (working copy) +@@ -28,6 +28,7 @@ + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import org.apache.zookeeper.CreateMode; ++import org.apache.zookeeper.KeeperException; + import org.apache.zookeeper.PortAssignment; + import org.apache.zookeeper.WatchedEvent; + import org.apache.zookeeper.Watcher; +@@ -35,8 +36,10 @@ + import org.apache.zookeeper.ZooKeeper; + import org.apache.zookeeper.Watcher.Event.KeeperState; + import org.apache.zookeeper.ZooDefs.Ids; ++import org.apache.zookeeper.ZooDefs.Perms; + import org.apache.zookeeper.data.ACL; + import org.apache.zookeeper.data.Id; ++import org.apache.zookeeper.data.Stat; + import org.apache.zookeeper.server.ServerCnxnFactory; + import org.apache.zookeeper.server.SyncRequestProcessor; + import org.apache.zookeeper.server.ZooKeeperServer; +@@ -77,6 +80,48 @@ + ClientBase.CONNECTION_TIMEOUT)); + } + } ++ ++ /** ++ * Verify that getAcl should fail when there is not ++ * read permission to that node ++ */ ++ @Test ++ public void testAclReadPermission() throws Exception { ++ File tmpDir = ClientBase.createTmpDir(); ++ ClientBase.setupTestEnv(); ++ ZooKeeperServer zks = new ZooKeeperServer(tmpDir, tmpDir, 3000); ++ SyncRequestProcessor.setSnapCount(1000); ++ final int PORT = Integer.parseInt(HOSTPORT.split(":")[1]); ++ ServerCnxnFactory f = ServerCnxnFactory.createFactory(PORT, -1); ++ f.startup(zks); ++ ZooKeeper zk; ++ String path = "/node1"; ++ boolean readPermLimitWorks = false; ++ try { ++ LOG.info("starting up the zookeeper server .. waiting"); ++ Assert.assertTrue("waiting for server being up", ++ ClientBase.waitForServerUp(HOSTPORT, CONNECTION_TIMEOUT)); ++ zk = new ZooKeeper(HOSTPORT, CONNECTION_TIMEOUT, this); ++ Id id = new Id("ip", "127.0.0.1"); ++ ArrayList acl = new ArrayList(); // Not set read permission ++ acl.add(new ACL(Perms.CREATE, id)); ++ acl.add(new ACL(Perms.DELETE, id)); ++ acl.add(new ACL(Perms.WRITE, id)); ++ acl.add(new ACL(Perms.ADMIN, id)); ++ zk.create(path, path.getBytes(), acl, CreateMode.PERSISTENT); ++ Stat stat = new Stat(); ++ zk.getACL(path, stat); // Should cause exception without read permission ++ } catch (KeeperException.NoAuthException e) { ++ readPermLimitWorks = true; ++ } finally { ++ f.shutdown(); ++ Assert.assertTrue("waiting for server down", ++ ClientBase.waitForServerDown(HOSTPORT, CONNECTION_TIMEOUT)); ++ } ++ if (!readPermLimitWorks) { ++ Assert.fail("Should not reach here as ACL has no read permission"); ++ } ++ } + + /** + * Verify that acl optimization of storing just +Index: src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java +=================================================================== +--- src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java (revision 1510080) ++++ src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java (working copy) +@@ -324,6 +324,17 @@ + GetACLRequest getACLRequest = new GetACLRequest(); + ByteBufferInputStream.byteBuffer2Record(request.request, + getACLRequest); ++ DataNode n = zks.getZKDatabase().getNode(getACLRequest.getPath()); ++ if (n == null) { ++ throw new KeeperException.NoNodeException(); ++ } ++ Long aclL; ++ synchronized(n) { ++ aclL = n.acl; ++ } ++ PrepRequestProcessor.checkACL(zks, zks.getZKDatabase().convertLong(aclL), ++ ZooDefs.Perms.READ, ++ request.authInfo); + Stat stat = new Stat(); + List acl = + zks.getZKDatabase().getACL(getACLRequest.getPath(), stat); \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/logging.properties b/java-diff-utils/src/test/resources/logging.properties index 4c1655b6..bc5042e0 100644 --- a/java-diff-utils/src/test/resources/logging.properties +++ b/java-diff-utils/src/test/resources/logging.properties @@ -1,7 +1,7 @@ handlers=java.util.logging.ConsoleHandler .level=INFO -com.github.difflib.unifieddiff.level=FINE +com.github.difflib.unifieddiff.level=INFO java.util.logging.ConsoleHandler.level=INFO #java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter From 976505222683234b11505191394f9e046c7cfbdd Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 8 Jul 2021 22:42:53 +0200 Subject: [PATCH 025/107] first implementation of meyers diff with linear space --- .../com/github/difflib/algorithm/Change.java | 8 ++++++++ .../myers/MeyersDiffWithLinearSpace.java | 17 ++++++++++++++--- .../WithMeyersDiffWithLinearSpacePatchTest.java | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java index 57fbb788..9b6f1dfe 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java @@ -36,4 +36,12 @@ public Change(DeltaType deltaType, int startOriginal, int endOriginal, int start this.startRevised = startRevised; this.endRevised = endRevised; } + + public Change withEndOriginal(int endOriginal) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } + + public Change withEndRevised(int endRevised) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 1dcae646..98a49dcd 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -65,11 +65,22 @@ private void buildScript(DiffData data, int start1, int end1, int start2, int en //TODO: compress these commands. if (end1 - start1 > end2 - start2) { //script.append(new DeleteCommand<>(left.charAt(i))); - data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endOriginal != i + || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + } else { + data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); + } ++i; } else { - //script.append(new InsertCommand<>(right.charAt(j))); - data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endRevised != j + || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + } else { + data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndRevised(j + 1)); + } ++j; } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java index 2b060011..d2a02ca4 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -93,7 +93,7 @@ public void testPatch_Change_withExceptionProcessor() { try { List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); + assertEquals(11, data.size()); assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); From 24c3df1f39d67e3de20b31b4de2214e2000f0e38 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Fri, 9 Jul 2021 18:27:52 +0200 Subject: [PATCH 026/107] --- .../myers/MeyersDiffWithLinearSpace.java | 3 --- .../myers/MeyersDiffWithLinearSpaceTest.java | 18 ++++++++++++++++++ ...WithMeyersDiffWithLinearSpacePatchTest.java | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 98a49dcd..e7678be4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -87,9 +87,6 @@ private void buildScript(DiffData data, int start1, int end1, int start2, int en } } else { buildScript(data, start1, middle.start, start2, middle.start - middle.diag); -// for (int i = middle.getStart(); i < middle.getEnd(); ++i) { -// script.append(new KeepCommand<>(left.charAt(i))); -// } buildScript(data, middle.end, end1, middle.end - middle.diag, end2); } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index 5462eb40..fc397575 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -15,11 +15,14 @@ */ package com.github.difflib.algorithm.myers; +import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import static java.util.stream.Collectors.toList; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -70,4 +73,19 @@ public void diffEnd() { System.out.println(logdata); assertEquals(8, logdata.size()); } + + + @Test + public void testPerformanceProblemsIssue124() { + List old = Arrays.asList("abcd"); + List newl = IntStream.range(0, 90000) + .boxed() + .map(i -> i.toString()) + .collect(toList()); + + long start = System.currentTimeMillis(); + Patch diff = DiffUtils.diff(old, newl, new MeyersDiffWithLinearSpace()); + long end = System.currentTimeMillis(); + System.out.println("Finished in " + (end - start) + "ms and resulted " + diff.getDeltas().size() + " deltas"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java index d2a02ca4..82aa04d4 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -95,7 +95,7 @@ public void testPatch_Change_withExceptionProcessor() { List data = DiffUtils.patch(changeTest_from, patch); assertEquals(11, data.size()); - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); } catch (PatchFailedException e) { fail(e.getMessage()); From c765616957fc690553e34d4007eca7f038937f10 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 18 Jul 2021 01:20:25 +0200 Subject: [PATCH 027/107] attached logging to new meyer diff algorithm --- .../java/com/github/difflib/DiffUtils.java | 71 +++++++++++-------- .../algorithm/DiffAlgorithmFactory.java | 29 ++++++++ .../myers/{MyersDiff.java => MeyersDiff.java} | 37 +++++++--- .../myers/MeyersDiffWithLinearSpace.java | 35 +++++++-- .../myers/MeyersDiffWithLinearSpaceTest.java | 8 +-- .../algorithm/myers/MyersDiffTest.java | 4 +- 6 files changed, 133 insertions(+), 51 deletions(-) create mode 100644 java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java rename java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/{MyersDiff.java => MeyersDiff.java} (85%) diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index f85b6178..44a8cff6 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -15,9 +15,10 @@ */ package com.github.difflib; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; -import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiff; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; @@ -34,26 +35,35 @@ public final class DiffUtils { /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * This factory generates the DEFAULT_DIFF algorithm for all these routines. + */ + static DiffAlgorithmFactory DEFAULT_DIFF = MeyersDiff.factory(); + + public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { + DEFAULT_DIFF = factory; + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param types to be diffed * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param progress progress listener - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), progress); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); } public static Patch diff(List original, List revised) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), null); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); } - + public static Patch diff(List original, List revised, boolean includeEqualParts) { - return DiffUtils.diff(original, revised, new MyersDiff<>(), null, includeEqualParts); + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); } /** @@ -67,45 +77,46 @@ public static Patch diff(String sourceText, String targetText, } /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param source The original text. Must not be {@code null}. * @param target The revised text. Must not be {@code null}. * - * @param equalizer the equalizer object to replace the default compare algorithm - * (Object.equals). If {@code null} the default equalizer of the default algorithm is used.. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @param equalizer the equalizer object to replace the default compare + * algorithm (Object.equals). If {@code null} the default equalizer of the + * default algorithm is used.. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List source, List target, BiPredicate equalizer) { if (equalizer != null) { return DiffUtils.diff(source, target, - new MyersDiff<>(equalizer)); + DEFAULT_DIFF.create(equalizer)); } - return DiffUtils.diff(source, target, new MyersDiff<>()); + return DiffUtils.diff(source, target, new MeyersDiff<>()); } public static Patch diff(List original, List revised, DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { return diff(original, revised, algorithm, progress, false); } - + /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param algorithm The diff algorithm. Must not be {@code null}. * @param progress The diff algorithm listener. * @param includeEqualParts Include equal data parts into the patch. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress, + DiffAlgorithmI algorithm, DiffAlgorithmListener progress, boolean includeEqualParts) { Objects.requireNonNull(original, "original must not be null"); Objects.requireNonNull(revised, "revised must not be null"); @@ -115,23 +126,23 @@ public static Patch diff(List original, List revised, } /** - * Computes the difference between the original and revised list of elements with default diff - * algorithm + * Computes the difference between the original and revised list of elements + * with default diff algorithm * * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param algorithm The diff algorithm. Must not be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never - * {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmI algorithm) { return diff(original, revised, algorithm, null); } /** - * Computes the difference between the given texts inline. This one uses the "trick" to make out - * of texts lists of characters, like DiffRowGenerator does and merges those changes at the end - * together again. + * Computes the difference between the given texts inline. This one uses the + * "trick" to make out of texts lists of characters, like DiffRowGenerator + * does and merges those changes at the end together again. * * @param original * @param revised diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java new file mode 100644 index 00000000..7e5205cd --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm; + +import java.util.function.BiPredicate; + +/** + * Tool to create new instances of a diff algorithm. This one is only needed at the moment to + * set DiffUtils default diff algorithm. + * @author tw + */ +public interface DiffAlgorithmFactory { + DiffAlgorithmI create(); + + DiffAlgorithmI create(BiPredicate equalizer); +} diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java similarity index 85% rename from java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java rename to java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java index f1345577..7daa91d2 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java @@ -16,6 +16,7 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.DeltaType; @@ -26,17 +27,17 @@ import java.util.function.BiPredicate; /** - * A clean-room implementation of Eugene Myers greedy differencing algorithm. + * A clean-room implementation of Eugene Meyers greedy differencing algorithm. */ -public final class MyersDiff implements DiffAlgorithmI { +public final class MeyersDiff implements DiffAlgorithmI { private final BiPredicate equalizer; - public MyersDiff() { + public MeyersDiff() { equalizer = Object::equals; } - public MyersDiff(final BiPredicate equalizer) { + public MeyersDiff(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @@ -63,8 +64,9 @@ public List computeDiff(final List source, final List target, Diff } /** - * Computes the minimum diffpath that expresses de differences between the original and revised - * sequences, according to Gene Myers differencing algorithm. + * Computes the minimum diffpath that expresses de differences between the + * original and revised sequences, according to Gene Myers differencing + * algorithm. * * @param orig The original sequence. * @param rev The revised sequence. @@ -138,8 +140,8 @@ private PathNode buildPath(final List orig, final List rev, DiffAlgorithmL * @param orig The original sequence. * @param rev The revised sequence. * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be built from the given - * path. + * @throws DifferentiationFailedException if a {@link Patch} could not be + * built from the given path. */ private List buildRevision(PathNode actualPath, List orig, List rev) { Objects.requireNonNull(actualPath, "path is null"); @@ -176,4 +178,23 @@ private List buildRevision(PathNode actualPath, List orig, List re } return changes; } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI + create() { + return new MeyersDiff(); + } + + @Override + public DiffAlgorithmI + create(BiPredicate < T, T > equalizer) { + return new MeyersDiff(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index e7678be4..ebcc02f1 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Objects; import java.util.function.BiPredicate; +import java.util.function.Consumer; /** * @@ -43,13 +44,33 @@ public MeyersDiffWithLinearSpace(final BiPredicate equalizer) { @Override public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + DiffData data = new DiffData(source, target); - //shouldn't it be source.size() - 1? - buildScript(data, 0, source.size(), 0, target.size()); + + int maxIdx = source.size() + target.size(); + + buildScript(data, 0, source.size(), 0, target.size(), idx -> { + if (progress != null) { + progress.diffStep(idx, maxIdx); + } + }); + + if (progress != null) { + progress.diffEnd(); + } return data.script; } - private void buildScript(DiffData data, int start1, int end1, int start2, int end2) { + private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { + if (progress != null) { + progress.accept(start1); + } final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); if (middle == null || middle.start == end1 && middle.diag == end1 - end2 @@ -86,8 +107,8 @@ private void buildScript(DiffData data, int start1, int end1, int start2, int en } } } else { - buildScript(data, start1, middle.start, start2, middle.start - middle.diag); - buildScript(data, middle.end, end1, middle.end - middle.diag, end2); + buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); + buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); } } @@ -169,7 +190,7 @@ private Snake buildSnake(DiffData data, final int start, final int diag, final i return new Snake(start, end, diag); } - class DiffData { + private class DiffData { final int size; final int[] vDown; @@ -188,7 +209,7 @@ public DiffData(List source, List target) { } } - class Snake { + private class Snake { final int start; final int end; diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index fc397575..3608140d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -39,8 +39,8 @@ public void testDiffMyersExample1Forward() { final Patch patch = Patch.generate(original, revised, new MeyersDiffWithLinearSpace().computeDiff(original, revised, null)); assertNotNull(patch); System.out.println(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + assertEquals(5, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); } @Test @@ -68,8 +68,8 @@ public void diffEnd() { })); assertNotNull(patch); System.out.println(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); + assertEquals(5, patch.getDeltas().size()); + assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); System.out.println(logdata); assertEquals(8, logdata.size()); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 1e233a8c..10db0e43 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -34,7 +34,7 @@ public class MyersDiffTest { public void testDiffMyersExample1Forward() { List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); + final Patch patch = Patch.generate(original, revised, new MeyersDiff().computeDiff(original, revised, null)); assertNotNull(patch); assertEquals(4, patch.getDeltas().size()); assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); @@ -47,7 +47,7 @@ public void testDiffMyersExample1ForwardWithListener() { List logdata = new ArrayList<>(); final Patch patch = Patch.generate(original, revised, - new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + new MeyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { @Override public void diffStart() { logdata.add("start"); From e413d79ecd0af293fdc0f2474b8b097dc360d78d Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 18 Jul 2021 01:30:44 +0200 Subject: [PATCH 028/107] attached logging to new meyer diff algorithm --- .../difflib/algorithm/myers/MeyersDiffWithLinearSpace.java | 2 +- .../difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index ebcc02f1..3fec0fd8 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -69,7 +69,7 @@ public List computeDiff(List source, List target, DiffAlgorithmLis private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { if (progress != null) { - progress.accept(start1); + progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); } final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); if (middle == null diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java index 3608140d..7a10f1f1 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java @@ -71,7 +71,7 @@ public void diffEnd() { assertEquals(5, patch.getDeltas().size()); assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); System.out.println(logdata); - assertEquals(8, logdata.size()); + assertEquals(11, logdata.size()); } From 7203d4890f088771161d14e7597682e872e0765a Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Mon, 19 Jul 2021 00:15:39 +0200 Subject: [PATCH 029/107] mmoved header to inner processing to allow a header for each diff block --- .../unifieddiff/UnifiedDiffReader.java | 72 +++++++++++++------ .../unifieddiff/UnifiedDiffReaderTest.java | 8 +-- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index da75e6ff..181b41ae 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -72,26 +72,45 @@ public final class UnifiedDiffReader { // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { - String headerTxt = ""; - LOG.log(Level.FINE, "header parsing"); - String line = null; - while (READER.ready()) { - line = READER.readLine(); - LOG.log(Level.FINE, "parsing line {0}", line); - if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) - || FROM_FILE.validLine(line) || TO_FILE.validLine(line) - || NEW_FILE_MODE.validLine(line)) { - break; - } else { - headerTxt += line + "\n"; - } - } - if (!"".equals(headerTxt)) { - data.setHeader(headerTxt); - } - +// String headerTxt = ""; +// LOG.log(Level.FINE, "header parsing"); +// String line = null; +// while (READER.ready()) { +// line = READER.readLine(); +// LOG.log(Level.FINE, "parsing line {0}", line); +// if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) +// || FROM_FILE.validLine(line) || TO_FILE.validLine(line) +// || NEW_FILE_MODE.validLine(line)) { +// break; +// } else { +// headerTxt += line + "\n"; +// } +// } +// if (!"".equals(headerTxt)) { +// data.setHeader(headerTxt); +// } + + String line = READER.readLine(); while (line != null) { - if (!CHUNK.validLine(line)) { + String headerTxt = ""; + LOG.log(Level.FINE, "header parsing"); + while (line != null) { + LOG.log(Level.FINE, "parsing line {0}", line); + if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, + FROM_FILE, TO_FILE, + RENAME_FROM, RENAME_TO, + NEW_FILE_MODE, DELETED_FILE_MODE, + CHUNK)) { + break; + } else { + headerTxt += line + "\n"; + } + line = READER.readLine(); + } + if (!"".equals(headerTxt)) { + data.setHeader(headerTxt); + } + if (line != null && !CHUNK.validLine(line)) { initFileIfNecessary(); while (line != null && !CHUNK.validLine(line)) { if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, @@ -107,7 +126,7 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { processLine(line, CHUNK); while ((line = READER.readLine()) != null) { line = checkForNoNewLineAtTheEndOfTheFile(line); - + if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { throw new UnifiedDiffParserException("expected data line not found"); } @@ -186,6 +205,19 @@ private boolean processLine(String line, UnifiedDiffLine... rules) throws Unifie return false; //throw new UnifiedDiffParserException("parsing error at line " + line); } + + private boolean validLine(String line, UnifiedDiffLine ... rules) { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.validLine(line)) { + LOG.fine(" >>> accepted rule " + rule.toString()); + return true; + } + } + return false; + } private void initFileIfNecessary() { if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 02e7c4d8..912b72fd 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -370,9 +370,9 @@ public void testParseIssue122() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); - assertThat(diff.getFiles().size()).isEqualTo(22); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); } @Test @@ -380,8 +380,8 @@ public void testParseIssue123() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); - assertThat(diff.getFiles().size()).isEqualTo(22); + assertThat(diff.getFiles().size()).isEqualTo(2); - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); } } From 2294c5be7b70b79df932e6a046825e4dff609dd6 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 15 Aug 2021 00:51:24 +0200 Subject: [PATCH 030/107] implemented multi algorithm patch test --- .../myers/MeyersDiffWithLinearSpace.java | 20 ++++++ ...va => PatchWithAllDiffAlgorithmsTest.java} | 63 +++++++++++++----- .../difflib/patch/PatchWithMeyerDiffTest.java | 51 ++++++++++++++ ...PatchWithMeyerDiffWithLinearSpaceTest.java | 66 +++++++++++++++++++ 4 files changed, 185 insertions(+), 15 deletions(-) rename java-diff-utils/src/test/java/com/github/difflib/patch/{PatchTest.java => PatchWithAllDiffAlgorithmsTest.java} (64%) create mode 100644 java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java create mode 100644 java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java index 3fec0fd8..bb3577f8 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java @@ -16,6 +16,7 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.DeltaType; @@ -221,4 +222,23 @@ public Snake(final int start, final int end, final int diag) { this.diag = diag; } } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI + create() { + return new MeyersDiffWithLinearSpace(); + } + + @Override + public DiffAlgorithmI + create(BiPredicate < T, T > equalizer) { + return new MeyersDiffWithLinearSpace(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java similarity index 64% rename from java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java rename to java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index 91d514a3..117055a1 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -11,14 +11,35 @@ import java.util.Arrays; import java.util.List; -import org.junit.jupiter.api.Test; import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.DiffAlgorithmFactory; +import com.github.difflib.algorithm.myers.MeyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class PatchWithAllDiffAlgorithmsTest { + + private static Stream provideAlgorithms() { + return Stream.of( + Arguments.of(MeyersDiff.factory()), + Arguments.of(MeyersDiffWithLinearSpace.factory())); + } + + @AfterAll + public static void afterAll() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + } -public class PatchTest { - - @Test - public void testPatch_Insert() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Insert(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List insertTest_from = Arrays.asList("hhh"); final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); @@ -30,8 +51,11 @@ public void testPatch_Insert() { } } - @Test - public void testPatch_Delete() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Delete(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); final List deleteTest_to = Arrays.asList("ggg"); @@ -43,8 +67,11 @@ public void testPatch_Delete() { } } - @Test - public void testPatch_Change() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -56,8 +83,11 @@ public void testPatch_Change() { } } - @Test - public void testPatch_Serializable() throws IOException, ClassNotFoundException { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -79,8 +109,11 @@ public void testPatch_Serializable() throws IOException, ClassNotFoundException } - @Test - public void testPatch_Change_withExceptionProcessor() { + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change_withExceptionProcessor(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); @@ -93,9 +126,9 @@ public void testPatch_Change_withExceptionProcessor() { try { List data = DiffUtils.patch(changeTest_from, patch); assertEquals(9, data.size()); - + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - + } catch (PatchFailedException e) { fail(e.getMessage()); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java new file mode 100644 index 00000000..d7bf2798 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.patch; + +import com.github.difflib.DiffUtils; +import java.util.Arrays; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class PatchWithMeyerDiffTest { + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); + + assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java new file mode 100644 index 00000000..8a62992e --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2021 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.patch; + +import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.myers.MeyersDiff; +import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class PatchWithMeyerDiffWithLinearSpaceTest { + + @BeforeAll + public static void setupClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiffWithLinearSpace.factory()); + } + + @AfterAll + public static void resetClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); + + assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } +} From 894b8ba300e85d4a502f4f48a7029bc48bed032c Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 15 Aug 2021 00:52:08 +0200 Subject: [PATCH 031/107] implemented multi algorithm patch test --- .../patch/PatchWithAllDiffAlgorithmsTest.java | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index 117055a1..82e72294 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -107,30 +107,5 @@ public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOExcept fail(e.getMessage()); } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Change_withExceptionProcessor(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - - changeTest_from.set(2, "CDC"); - - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); - - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } } From 7dea4e229843c5ffbcf7c5a93028f584ca5e7a59 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 8 Sep 2021 12:16:50 +0200 Subject: [PATCH 032/107] --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 93899e85..b4d066d6 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,13 @@ This is a test ~senctence~**for diffutils**. * producing human-readable differences * inline difference construction * Algorithms: - * Myer + * Meyers Standard Algorithm + * Meyers with linear space improvement * HistogramDiff using JGit Library ### Algorithms -* Myer's diff +* Meyer's diff * HistogramDiff But it can easily replaced by any other which is better for handing your texts. I have plan to add implementation of some in future. From 4c457b39dc011a47b32ab64fee671c747759f517 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 8 Sep 2021 12:19:53 +0200 Subject: [PATCH 033/107] --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7662f7e7..8f5dab30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ This project uses a custom versioning scheme (and not [Semantic Versioning](http ### Changed +* bugfixing new UnifiedDiff reader + * header for each file + * skip empty lines +* introduction of Meyers Diff Algorithm with Linear Space improvment (until matured this will not be the default diff algorithm) +* introduction of DiffAlgorithmFactory to set the default diff algorithm DiffUtils use (`DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiffWithLinearSpace.factory());`) + ## [4.10] ### Changed From c85296b63de0b8c2cf5ced8b20bc9eb1c19b4d96 Mon Sep 17 00:00:00 2001 From: Jerry James Date: Wed, 8 Sep 2021 04:21:50 -0600 Subject: [PATCH 034/107] Javadoc warning fixes (#109) Co-authored-by: Tobias --- .../main/java/com/github/difflib/UnifiedDiffUtils.java | 9 +++------ .../java/com/github/difflib/text/DiffRowGenerator.java | 4 ++-- .../github/difflib/unifieddiff/UnifiedDiffWriter.java | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 2098aacf..06a61073 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -129,7 +129,7 @@ private static void processLinesInPrevChunk(List rawChunk, Patch rawChunk, Patch generateUnifiedDiff(String originalFileName, String revisedFileName, List originalLines, Patch patch, @@ -200,13 +199,12 @@ public static List generateUnifiedDiff(String originalFileName, /** * processDeltas takes a list of Deltas and outputs them together in a single block of - * Unified-Diff-format text. + * Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com). * * @param origLines - the lines of the original file * @param deltas - the Deltas to be output as a single block * @param contextSize - the number of lines of context to place around block * @return - * @author Bill James (tankerbay@gmail.com) */ private static List processDeltas(List origLines, List> deltas, int contextSize, boolean newFile) { @@ -297,11 +295,10 @@ private static List processDeltas(List origLines, } /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com). * * @param delta - the Delta to output * @return list of String lines of code. - * @author Bill James (tankerbay@gmail.com) */ private static List getDeltaText(AbstractDelta delta) { List buffer = new ArrayList<>(); diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 6cd13b38..5370b16d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -554,8 +554,8 @@ public Builder processDiffs(Function processDiffs) { * Set the column width of generated lines of original and revised * texts. * - * @param width the width to set. Making it < 0 doesn't make any sense. - * Default 80. @return builder with config of column width + * @param width the width to set. Making it < 0 doesn't make any sense. Default 80. + * @return builder with config of column width */ public Builder columnWidth(int width) { if (width >= 0) { diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java index 8d6c58bf..7cac8a2c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java @@ -189,10 +189,10 @@ private static void processDeltas(Consumer writer, } /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. * - * @param delta - the Delta to output - * @return list of String lines of code. + * @param writer consumer for the list of String lines of code + * @param delta the Delta to output */ private static void getDeltaText(Consumer writer, AbstractDelta delta) { for (String line : delta.getSource().getLines()) { From ab1e38c57b39ec6f9bd48bcbf7f696321616218a Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 8 Sep 2021 12:28:13 +0200 Subject: [PATCH 035/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.11 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index ae872975..0f2df3bb 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.11-SNAPSHOT + 4.11 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 734f3d5f..2bb6b30c 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.11-SNAPSHOT + 4.11 UTF-8 diff --git a/pom.xml b/pom.xml index 50dd9510..75ebd8b0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.11-SNAPSHOT + 4.11 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.11 GitHub Issues From 8ae1f6cbbf154fd50943d500f0f67b24984b2bc2 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 8 Sep 2021 12:28:14 +0200 Subject: [PATCH 036/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 0f2df3bb..eadc1cfb 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.11 + 4.12-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 2bb6b30c..e9e78f76 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.11 + 4.12-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 75ebd8b0..3703674c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.11 + 4.12-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.11 + HEAD GitHub Issues From 3663cb5d87ee5d79749308564170d9cf950e31d2 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 8 Sep 2021 12:37:20 +0200 Subject: [PATCH 037/107] changed gpg plugin version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3703674c..40cdd391 100644 --- a/pom.xml +++ b/pom.xml @@ -199,7 +199,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.4 + 1.6 sign-artifacts From 0fd38db8ae3b62854e96d8623840792166b5181d Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 21 Oct 2021 23:17:04 +0200 Subject: [PATCH 038/107] fixes #129 - added the possibility to skip delta decompression --- .../github/difflib/text/DiffRowGenerator.java | 29 ++++++- .../difflib/text/DiffRowGeneratorTest.java | 82 ++++++++++++++++++- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 5370b16d..9dbf052b 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -173,6 +173,7 @@ static void wrapInTag(List sequence, int startPosition, private final boolean showInlineDiffs; private final boolean replaceOriginalLinefeedInChangesWithSpaces; + private final boolean decompressDeltas; private DiffRowGenerator(Builder builder) { showInlineDiffs = builder.showInlineDiffs; @@ -182,6 +183,7 @@ private DiffRowGenerator(Builder builder) { columnWidth = builder.columnWidth; mergeOriginalRevised = builder.mergeOriginalRevised; inlineDiffSplitter = builder.inlineDiffSplitter; + decompressDeltas = builder.decompressDeltas; if (builder.equalizer != null) { equalizer = builder.equalizer; @@ -225,8 +227,14 @@ public List generateDiffRows(final List original, Patch int endPos = 0; final List> deltaList = patch.getDeltas(); - for (AbstractDelta originalDelta : deltaList) { - for (AbstractDelta delta : decompressDeltas(originalDelta)) { + if (decompressDeltas) { + for (AbstractDelta originalDelta : deltaList) { + for (AbstractDelta delta : decompressDeltas(originalDelta)) { + endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); + } + } + } else { + for (AbstractDelta delta : deltaList) { endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); } } @@ -442,6 +450,7 @@ public static class Builder { private boolean showInlineDiffs = false; private boolean ignoreWhiteSpaces = false; + private boolean decompressDeltas = true; private BiFunction oldTag = (tag, f) -> f ? "" : ""; @@ -554,7 +563,8 @@ public Builder processDiffs(Function processDiffs) { * Set the column width of generated lines of original and revised * texts. * - * @param width the width to set. Making it < 0 doesn't make any sense. Default 80. + * @param width the width to set. Making it < 0 doesn't make any + * sense. Default 80. * @return builder with config of column width */ public Builder columnWidth(int width) { @@ -586,6 +596,19 @@ public Builder mergeOriginalRevised(boolean mergeOriginalRevised) { return this; } + /** + * Deltas could be in a state, that would produce some unreasonable + * results within an inline diff. So the deltas are decompressed into + * smaller parts and rebuild. But this could result in more differences. + * + * @param decompressDeltas + * @return + */ + public Builder decompressDeltas(boolean decompressDeltas) { + this.decompressDeltas = decompressDeltas; + return this; + } + /** * Per default each character is separatly processed. This variant * introduces processing by word, which does not deliver in word diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index d9a3c4ff..08172d81 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -641,7 +641,7 @@ public void testCorrectChangeIssue114() throws IOException { assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); } - + @Test public void testCorrectChangeIssue114_2() throws IOException { List original = Arrays.asList("A", "B", "C", "D", "E"); @@ -662,7 +662,7 @@ public void testCorrectChangeIssue114_2() throws IOException { assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]"); } - + @Test public void testIssue119WrongContextLength() throws IOException { String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_original.txt")).collect(joining("\n")); @@ -683,4 +683,82 @@ public void testIssue119WrongContextLength() throws IOException { .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) .forEach(System.out::println); } + + @Test + public void testIssue129WithDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = DiffRowGenerator.create() + .showInlineDiffs(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); +// .forEachOrdered(row -> { +// System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, +// row.getTag(), row.getOldLine(), row.getNewLine()); +// }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL"); + } + + @Test + public void testIssue129SkipDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = + DiffRowGenerator.create() + .showInlineDiffs(true) + .decompressDeltas(false) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); +// .forEachOrdered(row -> { +// System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, +// row.getTag(), row.getOldLine(), row.getNewLine()); +// }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); + } } From 37f9eafaa90070d42e343cf9076a982dd693a6ae Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 29 Nov 2021 07:10:52 +0900 Subject: [PATCH 039/107] Fizzy apply (#125) * add applyFuzzyTo and restoreFuzzy * implement applyFuzzyTo and restoreFuzzy for EqualDelta * add verifyChunk with fuzzy and delta * implement fuzzy apply and add tests * fix style * extract method * fix: assign lastPatchDelta and lastPatchEnd * fix use explict import * document EqualDelta#applyFuzzyToAt * set access modifiers * change test method name * document empty method * apply from first * add more tests * add more documentation about fuzz parameter * fix documentation about fizzy patch Co-authored-by: cowwoc Co-authored-by: cowwoc --- .../github/difflib/patch/AbstractDelta.java | 13 + .../com/github/difflib/patch/ChangeDelta.java | 13 + .../java/com/github/difflib/patch/Chunk.java | 22 +- .../com/github/difflib/patch/EqualDelta.java | 8 + .../java/com/github/difflib/patch/Patch.java | 111 +++++++ ...ithMeyersDiffWithLinearSpacePatchTest.java | 290 ++++++++++++++++++ .../com/github/difflib/patch/ChunkTest.java | 43 +++ 7 files changed, 498 insertions(+), 2 deletions(-) create mode 100644 java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java index 7db3ac50..a315e010 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java @@ -70,6 +70,19 @@ protected VerifyChunk verifyAntApplyTo(List target) throws PatchFailedExcepti protected abstract void restore(List target); + /** + * Apply patch fuzzy. + * + * @param target the list this patch will be applied to + * @param fuzz the number of elements to ignore before/after the patched elements + * @param position the position this patch will be applied to. ignores {@code source.getPosition()} + * @see
Description of Fuzzy Patch for more information. + */ + @SuppressWarnings("RedundantThrows") + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + throw new UnsupportedOperationException(this.getClass().getSimpleName() + " does not supports applying patch fuzzy"); + } + /** * Create a new delta of the actual instance with customized chunk data. */ diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java index ddb37665..376fd625 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java @@ -66,6 +66,19 @@ protected void restore(List target) { } } + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + int size = getSource().size(); + for (int i = fuzz; i < size - fuzz; i++) { + target.remove(position + fuzz); + } + + int i = fuzz; + for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) { + target.add(position + i, line); + i++; + } + } + @Override public String toString() { return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java index 8ff19875..7e55ac0d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java @@ -95,10 +95,28 @@ public Chunk(int position, T[] lines) { * @throws com.github.difflib.patch.PatchFailedException */ public VerifyChunk verifyChunk(List target) throws PatchFailedException { - if (position > target.size() || last() > target.size()) { + return verifyChunk(target, 0, getPosition()); + } + + /** + * Verifies that this chunk's saved text matches the corresponding text in + * the given sequence. + * + * @param target the sequence to verify against. + * @param fuzz the count of ignored prefix/suffix + * @param position the position of target + * @throws com.github.difflib.patch.PatchFailedException + */ + public VerifyChunk verifyChunk(List target, int fuzz, int position) throws PatchFailedException { + //noinspection UnnecessaryLocalVariable + int startIndex = fuzz; + int lastIndex = size() - fuzz; + int last = position + size() - 1; + + if (position + fuzz > target.size() || last - fuzz > target.size()) { return VerifyChunk.POSITION_OUT_OF_TARGET; } - for (int i = 0; i < size(); i++) { + for (int i = startIndex; i < lastIndex; i++) { if (!target.get(position + i).equals(lines.get(i))) { return VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java index e13cf52e..17fdadc6 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java @@ -35,6 +35,14 @@ protected void applyTo(List target) throws PatchFailedException { protected void restore(List target) { } + /** + * {@inheritDoc} + */ + @Override + protected void applyFuzzyToAt(List target, int fuzz, int delta) { + // equals so no operations + } + @Override public String toString() { return "[EqualDelta, position: " + getSource().getPosition() + ", lines: " diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java index 5a952f6c..5e3e51f8 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -66,6 +66,117 @@ public List applyTo(List target) throws PatchFailedException { return result; } + private static class PatchApplyingContext { + public final List result; + public final int maxFuzz; + + // the position last patch applied to. + public int lastPatchEnd = -1; + + ///// passing values from find to apply + public int currentFuzz = 0; + + public int defaultPosition; + public boolean beforeOutRange = false; + public boolean afterOutRange = false; + + private PatchApplyingContext(List result, int maxFuzz) { + this.result = result; + this.maxFuzz = maxFuzz; + } + } + + public List applyFuzzy(List target, int maxFuzz) throws PatchFailedException { + PatchApplyingContext ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz); + + // the difference between patch's position and actually applied position + int lastPatchDelta = 0; + + for (AbstractDelta delta : getDeltas()) { + ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta; + int patchPosition = findPositionFuzzy(ctx, delta); + if (0 <= patchPosition) { + delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition); + lastPatchDelta = patchPosition - delta.getSource().getPosition(); + ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta; + } else { + conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result); + } + } + + return ctx.result; + } + + // negative for not found + private int findPositionFuzzy(PatchApplyingContext ctx, AbstractDelta delta) throws PatchFailedException { + for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) { + ctx.currentFuzz = fuzz; + int foundPosition = findPositionWithFuzz(ctx, delta, fuzz); + if (foundPosition >= 0) { + return foundPosition; + } + } + return -1; + } + + // negative for not found + private int findPositionWithFuzz(PatchApplyingContext ctx, AbstractDelta delta, int fuzz) throws PatchFailedException { + if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) { + return ctx.defaultPosition; + } + + ctx.beforeOutRange = false; + ctx.afterOutRange = false; + + // moreDelta >= 0: just for overflow guard, not a normal condition + //noinspection OverflowingLoopIndex + for (int moreDelta = 0; moreDelta >= 0; moreDelta++) { + int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta); + if (pos >= 0) { + return pos; + } + if (ctx.beforeOutRange && ctx.afterOutRange) { + break; + } + } + + return -1; + } + + // negative for not found + private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext ctx, AbstractDelta delta, int fuzz, int moreDelta) throws PatchFailedException { + // range check: can't apply before end of last patch + if (!ctx.beforeOutRange) { + int beginAt = ctx.defaultPosition - moreDelta + fuzz; + // We can't apply patch before end of last patch. + if (beginAt <= ctx.lastPatchEnd) { + ctx.beforeOutRange = true; + } + } + // range check: can't apply after end of result + if (!ctx.afterOutRange) { + int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz; + // We can't apply patch before end of last patch. + if (ctx.result.size() < beginAt) { + ctx.afterOutRange = true; + } + } + + if (!ctx.beforeOutRange) { + VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta); + if (before == VerifyChunk.OK) { + return ctx.defaultPosition - moreDelta; + } + } + if (!ctx.afterOutRange) { + VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta); + if (after == VerifyChunk.OK) { + return ctx.defaultPosition + moreDelta; + } + } + return -1; + } + /** * Standard Patch behaviour to throw an exception for pathching conflicts. */ diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java index 82aa04d4..65ea1839 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java @@ -2,6 +2,7 @@ import com.github.difflib.patch.*; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import java.io.ByteArrayInputStream; @@ -9,8 +10,13 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.jupiter.api.Test; @@ -57,6 +63,104 @@ public void testPatch_Change() { } } + // region testPatch_fuzzyApply utils + + private List intRange(int count) { + return IntStream.range(0, count) + .mapToObj(Integer::toString) + .collect(Collectors.toList()); + } + + @SafeVarargs + private final List join(List... lists) { + return Arrays.stream(lists).flatMap(Collection::stream).collect(Collectors.toList()); + } + + private static class FuzzyApplyTestPair { + public final List from; + public final List to; + public final int requiredFuzz; + + private FuzzyApplyTestPair(List from, List to, int requiredFuzz) { + this.from = from; + this.to = to; + this.requiredFuzz = requiredFuzz; + } + } + + // endregion + + @Test + public void fuzzyApply() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>( + new Chunk<>(6, deltaFrom), + new Chunk<>(6, deltaTo))); + + //noinspection unchecked + List[] moves = new List[] { + intRange(6), // no patch move + intRange(3), // forward patch move + intRange(9), // backward patch move + intRange(0), // apply to the first + }; + + for (FuzzyApplyTestPair pair : FUZZY_APPLY_TEST_PAIRS) { + for (List move : moves) { + List from = join(move, pair.from); + List to = join(move, pair.to); + + for (int i = 0; i < pair.requiredFuzz; i++) { + int maxFuzz = i; + assertThrows(PatchFailedException.class, () -> + patch.applyFuzzy(from, maxFuzz), + () -> "fail for " + from + " -> " + to + " for fuzz " + maxFuzz + " required " + pair.requiredFuzz); + } + for (int i = pair.requiredFuzz; i < 4; i++) { + int maxFuzz = i; + assertEquals(to, patch.applyFuzzy(from, maxFuzz), + () -> "with " + maxFuzz); + } + } + } + } + + @Test + public void fuzzyApplyTwoSideBySidePatches() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>( + new Chunk<>(0, deltaFrom), + new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>( + new Chunk<>(6, deltaFrom), + new Chunk<>(6, deltaTo))); + + + assertEquals(join(deltaTo, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom), 0)); + } + + @Test + public void fuzzyApplyToNearest() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>( + new Chunk<>(0, deltaFrom), + new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>( + new Chunk<>(10, deltaFrom), + new Chunk<>(10, deltaTo))); + + assertEquals(join(deltaTo, deltaFrom, deltaTo), + patch.applyFuzzy(join(deltaFrom, deltaFrom, deltaFrom), 0)); + assertEquals(join(intRange(1), deltaTo, deltaFrom, deltaTo), + patch.applyFuzzy(join(intRange(1), deltaFrom, deltaFrom, deltaFrom), 0)); + } + @Test public void testPatch_Serializable() throws IOException, ClassNotFoundException { final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); @@ -101,4 +205,190 @@ public void testPatch_Change_withExceptionProcessor() { fail(e.getMessage()); } } + + static class FuzzyApplyTestDataGenerator { + private static String createList(List values) { + return values.stream() + .map(x -> '"' + x + '"') + .collect(Collectors.joining(", ", "Arrays.asList(", ")")); + } + + public static void main(String[] args) { + String[] deltaFrom = new String[] { "aaa", "bbb", "ccc", "ddd", "eee", "fff" }; + String[] deltaTo = new String[] { "aaa", "bbb", "cxc", "dxd", "eee", "fff" }; + + List pairs = new ArrayList<>(); + + // create test data. + // Brute-force search + String[] changedValue = new String[]{"axa", "bxb", "czc", "dzd", "exe", "fxf"}; + for (int i = 0; i < 1 << 6; i++) { + if ((i & 0b001100) != 0 && (i & 0b001100) != 0b001100) { + continue; + } + + String[] from = deltaFrom.clone(); + String[] to = deltaTo.clone(); + for (int j = 0; j < 6; j++) { + if ((i & (1 << j)) != 0) { + from[j] = changedValue[j]; + to[j] = changedValue[j]; + } + } + + int requiredFuzz; + if ((i & 0b001100) != 0) { + requiredFuzz = 3; + } else if ((i & 0b010010) != 0) { + requiredFuzz = 2; + } else if ((i & 0b100001) != 0) { + requiredFuzz = 1; + } else { + requiredFuzz = 0; + } + + pairs.add(new FuzzyApplyTestPair(Arrays.asList(from), Arrays.asList(to), requiredFuzz)); + } + pairs.sort(Comparator.comparingInt(a -> a.requiredFuzz)); + System.out.println("FuzzyApplyTestPair[] pairs = new FuzzyApplyTestPair[] {"); + for (FuzzyApplyTestPair pair : pairs) { + System.out.println(" new FuzzyApplyTestPair("); + System.out.println(" " + createList(pair.from) + ","); + System.out.println(" " + createList(pair.to) + ","); + System.out.println(" " + pair.requiredFuzz + "),"); + } + System.out.println("};"); + } + } + + private static final FuzzyApplyTestPair[] FUZZY_APPLY_TEST_PAIRS = new FuzzyApplyTestPair[] { + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"), + 0), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fff"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + }; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java new file mode 100644 index 00000000..4816f221 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java @@ -0,0 +1,43 @@ +package com.github.difflib.patch; + +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ChunkTest { + @Test + void verifyChunk() throws PatchFailedException { + Chunk chunk = new Chunk<>(7, toCharList("test")); + + // normal check + assertEquals(VerifyChunk.OK, + chunk.verifyChunk(toCharList("prefix test suffix"))); + assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, + chunk.verifyChunk(toCharList("prefix es suffix"), 0, 7)); + + // position + assertEquals(VerifyChunk.OK, + chunk.verifyChunk(toCharList("short test suffix"), 0, 6)); + assertEquals(VerifyChunk.OK, + chunk.verifyChunk(toCharList("loonger test suffix"), 0, 8)); + assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, + chunk.verifyChunk(toCharList("prefix test suffix"), 0, 6)); + assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, + chunk.verifyChunk(toCharList("prefix test suffix"), 0, 8)); + + // fuzz + assertEquals(VerifyChunk.OK, + chunk.verifyChunk(toCharList("prefix test suffix"), 1, 7)); + assertEquals(VerifyChunk.OK, + chunk.verifyChunk(toCharList("prefix es suffix"), 1, 7)); + assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, + chunk.verifyChunk(toCharList("prefix suffix"), 1, 7)); + } + + private List toCharList(String str) { + return str.chars().mapToObj(x -> (char) x).collect(Collectors.toList()); + } +} From 8fdd2399616109402617e5b2efd3f5d2fcb8fcb6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 21 Feb 2022 09:00:27 +0100 Subject: [PATCH 040/107] Fix a typo in ComputeDifference.java (#140) --- .../java/com/github/difflib/examples/ComputeDifference.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java index f665e662..e8b25437 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java @@ -12,11 +12,11 @@ public class ComputeDifference { private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "original.txt"; - private static final String RIVISED = TestConstants.MOCK_FOLDER + "revised.txt"; + private static final String REVISED = TestConstants.MOCK_FOLDER + "revised.txt"; public static void main(String[] args) throws IOException { List original = Files.readAllLines(new File(ORIGINAL).toPath()); - List revised = Files.readAllLines(new File(RIVISED).toPath()); + List revised = Files.readAllLines(new File(REVISED).toPath()); // Compute diff. Get the Patch object. Patch is the container for computed deltas. Patch patch = DiffUtils.diff(original, revised); From ea9942da129dfc102d52172f59d21acae79bdafd Mon Sep 17 00:00:00 2001 From: Hussein Kasem Date: Sun, 10 Apr 2022 00:20:37 +0200 Subject: [PATCH 041/107] Fix for #141 (#144) * Add test to reproduce issue #141 * Fix assumption on test to match expected behaviour expressed on #141 * Make "/" after "virtual" directories mandatory Now the regex only matches with "a/", "b/", "new/" and "old/", previously the slash was taken as optional. fixes #141 --- .../difflib/unifieddiff/UnifiedDiffReader.java | 2 +- .../difflib/unifieddiff/UnifiedDiffReaderTest.java | 12 +++++++++++- .../difflib/unifieddiff/problem_diff_issue141.diff | 11 +++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue141.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index 181b41ae..0ac22ce8 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -353,7 +353,7 @@ private String extractFileName(String _line) { line = line.substring(0, matcher.start()); } line = line.split("\t")[0]; - return line.substring(4).replaceFirst("^(a|b|old|new)(\\/)?", "") + return line.substring(4).replaceFirst("^(a|b|old|new)/", "") .trim(); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 912b72fd..dcf32182 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -122,7 +122,7 @@ public void testParseIssue46() throws IOException { assertThat(diff.getFiles().size()).isEqualTo(1); UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo(".vhd"); + assertThat(file1.getFromFile()).isEqualTo("a.vhd"); assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); assertThat(diff.getTail()).isNull(); @@ -384,4 +384,14 @@ public void testParseIssue123() throws IOException { assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); } + + @Test + public void testParseIssue141() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue141.diff")); + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getFromFile()).isEqualTo("a.txt"); + assertThat(file1.getToFile()).isEqualTo("a1.txt"); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue141.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue141.diff new file mode 100644 index 00000000..ab33c250 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue141.diff @@ -0,0 +1,11 @@ +--- a.txt ++++ a1.txt +@@ -8,7 +8,7 @@ + + + +- 23 ++ 24 + + + 1 From bc65f9703c137b3a7d2906eeaf064588dd36c125 Mon Sep 17 00:00:00 2001 From: Okue Date: Sun, 10 Apr 2022 07:25:45 +0900 Subject: [PATCH 042/107] Update version in gradle example of README (#145) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4d066d6..cfe763bf 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Just add the code below to your maven dependencies: io.github.java-diff-utils java-diff-utils - 4.9 + 4.11 ``` @@ -97,5 +97,5 @@ or using gradle: ```groovy // https://mvnrepository.com/artifact/io.github.java-diff-utils/java-diff-utils -implementation "io.github.java-diff-utils:java-diff-utils:4.5" +implementation "io.github.java-diff-utils:java-diff-utils:4.11" ``` From 0545519fc3e79b9a7c7f6407c87f8f53cde50141 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 15 May 2022 00:17:49 +0200 Subject: [PATCH 043/107] included some more tests --- .../difflib/patch/PatchWithMeyerDiffTest.java | 17 +++++++++++++ .../difflib/text/DiffRowGeneratorTest.java | 24 +++++++++++++++++++ .../com/github/difflib/text/issue129_1.txt | 12 ++++++++++ .../com/github/difflib/text/issue129_2.txt | 13 ++++++++++ 4 files changed, 66 insertions(+) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue129_1.txt create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue129_2.txt diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java index d7bf2798..98f4f6eb 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java @@ -16,8 +16,10 @@ package com.github.difflib.patch; import com.github.difflib.DiffUtils; +import static com.github.difflib.patch.Patch.CONFLICT_PRODUCES_MERGE_CONFLICT; import java.util.Arrays; import java.util.List; +import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; @@ -27,6 +29,7 @@ * @author tw */ public class PatchWithMeyerDiffTest { + @Test public void testPatch_Change_withExceptionProcessor() { final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); @@ -48,4 +51,18 @@ public void testPatch_Change_withExceptionProcessor() { fail(e.getMessage()); } } + + @Test + public void testPatchThreeWayIssue138() throws PatchFailedException { + List base = Arrays.asList("Imagine there's no heaven".split("\\s+")); + List left = Arrays.asList("Imagine there's no HEAVEN".split("\\s+")); + List right = Arrays.asList("IMAGINE there's no heaven".split("\\s+")); + + Patch rightPatch = DiffUtils.diff(base, right) + .withConflictOutput(CONFLICT_PRODUCES_MERGE_CONFLICT); + + List applied = rightPatch.applyTo(left); + + assertEquals("IMAGINE there's no HEAVEN", applied.stream().collect(joining(" "))); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 08172d81..897a37f9 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -761,4 +761,28 @@ public void testIssue129SkipDeltaDecompression() { assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); } + + @Test + public void testIssue129SkipWhitespaceChanges() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")).collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_2.txt")).collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList(original.split("\n")), + Arrays.asList(revised.split("\n"))); + + assertThat(rows).hasSize(13); + + rows.stream() + .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) + .forEach(System.out::println); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_1.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_1.txt new file mode 100644 index 00000000..42887fa5 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_1.txt @@ -0,0 +1,12 @@ +Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated +to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation, or +any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to +dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. +It is altogether fitting and proper that we should do this. But, in a larger sense, we can not dedicate -- we can not +consecrate -- we can not hallow -- this ground. The brave men, living and dead, who struggled here, have consecrated it, +far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never +forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought +here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us -- that +from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that +we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of +freedom -- and that government of the people, by the people, for the people, shall not perish from the earth. diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_2.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_2.txt new file mode 100644 index 00000000..50a051e3 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue129_2.txt @@ -0,0 +1,13 @@ +Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and +dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether +that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battle-field of that +war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives +that that nation might live. It is altogether fitting and proper that we should do this. But, in a larger sense, we +can not dedicate -- we can not consecrate -- we can not hallow -- this ground. The brave men, living and dead, who +struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long +remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated +here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here +dedicated to the great task remaining before us -- that from these honored dead we take increased devotion to that cause +for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died +in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, by the +people, for the people, shall not perish from the earth. From e73742c4fcb46308a69e64f91dd871132b39d22e Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Jul 2022 01:29:37 +0200 Subject: [PATCH 044/107] --- .github/release.yml | 8 ++++ .../UnifiedDiffRoundTripNewLineTest.java | 46 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .github/release.yml create mode 100644 java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..b9134ea9 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,8 @@ +changelog: + categories: + - title: Bugs solved + labels: + - "bug" + - title: Changes and new Features + labels: + - "*" \ No newline at end of file diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java new file mode 100644 index 00000000..725e0e2a --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.unifieddiff; + +import com.github.difflib.patch.PatchFailedException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled("for next release") +public class UnifiedDiffRoundTripNewLineTest { + @Test + public void testIssue135MissingNoNewLineInPatched() throws IOException, PatchFailedException { + String beforeContent = "rootProject.name = \"sample-repo\""; + String afterContent = "rootProject.name = \"sample-repo\"\n"; + String patch = "diff --git a/settings.gradle b/settings.gradle\n" + + "index ef3b8e2..ab30124 100644\n" + + "--- a/settings.gradle\n" + + "+++ b/settings.gradle\n" + + "@@ -1 +1 @@\n" + + "-rootProject.name = \"sample-repo\"\n" + + "\\ No newline at end of file\n" + + "+rootProject.name = \"sample-repo\"\n"; + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(patch.getBytes())); + String unifiedAfterContent = unifiedDiff.getFiles().get(0).getPatch() + .applyTo(Arrays.asList(beforeContent.split("\n"))).stream().collect(joining("\n")); + assertEquals(afterContent, unifiedAfterContent); + } +} From 0fd3bd8e061eed09dbb937c8ab9ba0969ba12264 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Jul 2022 01:33:40 +0200 Subject: [PATCH 045/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.12 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index eadc1cfb..5a62171e 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.12-SNAPSHOT + 4.12 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index e9e78f76..921f62cb 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.12-SNAPSHOT + 4.12 UTF-8 diff --git a/pom.xml b/pom.xml index 40cdd391..45e903cc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.12-SNAPSHOT + 4.12 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.12 GitHub Issues From a1f170eaa0d957211cb5acffc2691c2cca14d253 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Jul 2022 01:33:41 +0200 Subject: [PATCH 046/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 5a62171e..78b35257 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.12 + 4.13-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 921f62cb..c18e88b0 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.12 + 4.13-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 45e903cc..52b653da 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.12 + 4.13-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.12 + HEAD GitHub Issues From 912be299daad21b03919e6847ff42d6b8b03ceb2 Mon Sep 17 00:00:00 2001 From: Michael Lesniak Date: Tue, 16 Aug 2022 18:52:41 +0200 Subject: [PATCH 047/107] Update README.md (#153) Update minor version in example. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfe763bf..c3ad16ee 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Just add the code below to your maven dependencies: io.github.java-diff-utils java-diff-utils - 4.11 + 4.12 ``` From 5aac378e9f42c7f764a78998d2719c3ad87390ca Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Thu, 1 Sep 2022 22:27:09 +0200 Subject: [PATCH 048/107] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cfe763bf..f13ad58f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Just add the code below to your maven dependencies: io.github.java-diff-utils java-diff-utils - 4.11 + 4.12 ``` @@ -97,5 +97,5 @@ or using gradle: ```groovy // https://mvnrepository.com/artifact/io.github.java-diff-utils/java-diff-utils -implementation "io.github.java-diff-utils:java-diff-utils:4.11" +implementation "io.github.java-diff-utils:java-diff-utils:4.12" ``` From 7d0fd54f665e6d5489f54174402be518ba2fd401 Mon Sep 17 00:00:00 2001 From: andre161292 Date: Thu, 1 Sep 2022 23:08:39 +0200 Subject: [PATCH 049/107] Added ability to apply patch to existing list inplace (#152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added ability to apply patch to existing list inplace * Corrected typo 'verifyAntApplyTo' * Added restoreToExisting method as opposite to applyToExisting Co-authored-by: André Dörscheln --- .../github/difflib/patch/AbstractDelta.java | 2 +- .../java/com/github/difflib/patch/Patch.java | 49 ++++++++++++++----- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java index a315e010..f74f62ca 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java @@ -58,7 +58,7 @@ protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedE return getSource().verifyChunk(target); } - protected VerifyChunk verifyAntApplyTo(List target) throws PatchFailedException { + protected VerifyChunk verifyAndApplyTo(List target) throws PatchFailedException { final VerifyChunk verify = verifyChunkToFitTarget(target); if (verify == VerifyChunk.OK) { applyTo(target); diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java index 5e3e51f8..aaff7d94 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -48,22 +48,35 @@ public Patch(int estimatedPatchSize) { } /** - * Apply this patch to the given target + * Creates a new list, the patch is being applied to. * - * @return the patched text - * @throws PatchFailedException if can't apply patch + * @param target The list to apply the changes to. + * @return A new list containing the applied patch. + * @throws PatchFailedException if the patch cannot be applied */ public List applyTo(List target) throws PatchFailedException { List result = new ArrayList<>(target); + applyToExisting(result); + return result; + } + + /** + * Applies the patch to the supplied list. + * + * @param target The list to apply the changes to. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws PatchFailedException if the patch cannot be applied + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void applyToExisting(List target) throws PatchFailedException { ListIterator> it = getDeltas().listIterator(deltas.size()); while (it.hasPrevious()) { AbstractDelta delta = it.previous(); - VerifyChunk valid = delta.verifyAntApplyTo(result); + VerifyChunk valid = delta.verifyAndApplyTo(target); if (valid != VerifyChunk.OK) { - conflictOutput.processConflict(valid, delta, result); + conflictOutput.processConflict(valid, delta, target); } } - return result; } private static class PatchApplyingContext { @@ -220,19 +233,33 @@ public Patch withConflictOutput(ConflictOutput conflictOutput) { } /** - * Restore the text to original. Opposite to applyTo() method. + * Creates a new list, containing the restored state of the given list. + * Opposite to {@link #applyTo(List)} method. * - * @param target the given target - * @return the restored text + * @param target The list to copy and apply changes to. + * @return A new list, containing the restored state. */ public List restore(List target) { List result = new ArrayList<>(target); + restoreToExisting(result); + return result; + } + + + /** + * Restores all changes within the given list. + * Opposite to {@link #applyToExisting(List)} method. + * + * @param target The list to restore changes in. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void restoreToExisting(List target) { ListIterator> it = getDeltas().listIterator(deltas.size()); while (it.hasPrevious()) { AbstractDelta delta = it.previous(); - delta.restore(result); + delta.restore(target); } - return result; } /** From 2224a15ecfc3bbfd15b9f2d90a23964b27813944 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 10 Sep 2022 22:01:24 +0200 Subject: [PATCH 050/107] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f13ad58f..ca25196b 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Main reason to build this library was the lack of easy-to-use libraries with all ## API -Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.7/docs/api/) +Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/api/) ## Examples From 5aed9f2d9a298347cbd2735e6f0e32bf5555528a Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 10 Sep 2022 22:03:10 +0200 Subject: [PATCH 051/107] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ca25196b..5999e79d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Main reason to build this library was the lack of easy-to-use libraries with all ## API -Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/api/) +Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) ## Examples From 4cad0ea2b9c67e3e44847fb3d929141330a71634 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sun, 11 Sep 2022 04:29:30 +0800 Subject: [PATCH 052/107] Optimize CI a bit (#155) --- .github/workflows/maven.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index f8d96999..e57857f3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -14,13 +14,14 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11] + java: [8, 11, 17] name: Java ${{ matrix.java }} building ... steps: - - uses: actions/checkout@v2 - - name: Set up Java ${{ matrix.java }} - uses: actions/setup-java@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: - java-version: ${{ matrix.java }} + distribution: 'zulu' + java-version: ${{ matrix.java }} + cache: 'maven' - name: Build with Maven run: mvn -B package --file pom.xml \ No newline at end of file From dc5ecb8dedbcaa8d72789b46a53e155c9ae87284 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 20 Nov 2022 12:04:13 +0100 Subject: [PATCH 053/107] fixes #159 --- CHANGELOG.md | 4 +++- .../src/main/java/com/github/difflib/DiffUtils.java | 6 +++--- .../myers/{MeyersDiff.java => MyersDiff.java} | 12 ++++++------ ...inearSpace.java => MyersDiffWithLinearSpace.java} | 10 +++++----- .../test/java/com/github/difflib/DiffUtilsTest.java | 2 +- .../difflib/algorithm/myers/MyersDiffTest.java | 4 ++-- ...ceTest.java => MyersDiffWithLinearSpaceTest.java} | 8 ++++---- ...va => WithMyersDiffWithLinearSpacePatchTest.java} | 12 ++++++------ .../patch/PatchWithAllDiffAlgorithmsTest.java | 11 +++++------ ...MeyerDiffTest.java => PatchWithMyerDiffTest.java} | 2 +- ...ava => PatchWithMyerDiffWithLinearSpaceTest.java} | 10 +++++----- 11 files changed, 41 insertions(+), 40 deletions(-) rename java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/{MeyersDiff.java => MyersDiff.java} (95%) rename java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/{MeyersDiffWithLinearSpace.java => MyersDiffWithLinearSpace.java} (96%) rename java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/{MeyersDiffWithLinearSpaceTest.java => MyersDiffWithLinearSpaceTest.java} (90%) rename java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/{WithMeyersDiffWithLinearSpacePatchTest.java => WithMyersDiffWithLinearSpacePatchTest.java} (97%) rename java-diff-utils/src/test/java/com/github/difflib/patch/{PatchWithMeyerDiffTest.java => PatchWithMyerDiffTest.java} (98%) rename java-diff-utils/src/test/java/com/github/difflib/patch/{PatchWithMeyerDiffWithLinearSpaceTest.java => PatchWithMyerDiffWithLinearSpaceTest.java} (85%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5dab30..1c817822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project uses a custom versioning scheme (and not [Semantic Versioning](https://semver.org/spec/v2.0.0.html)). -## [unreleased] +## [4.13] + +* API change: due to Issue #159: the author of the algorithm is Eugene Myers, therefore classes and methods were renamed accordingly ### Changed diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index 44a8cff6..493d905b 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -18,7 +18,7 @@ import com.github.difflib.algorithm.DiffAlgorithmFactory; import com.github.difflib.algorithm.DiffAlgorithmI; import com.github.difflib.algorithm.DiffAlgorithmListener; -import com.github.difflib.algorithm.myers.MeyersDiff; +import com.github.difflib.algorithm.myers.MyersDiff; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; @@ -37,7 +37,7 @@ public final class DiffUtils { /** * This factory generates the DEFAULT_DIFF algorithm for all these routines. */ - static DiffAlgorithmFactory DEFAULT_DIFF = MeyersDiff.factory(); + static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { DEFAULT_DIFF = factory; @@ -95,7 +95,7 @@ public static Patch diff(List source, List target, return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer)); } - return DiffUtils.diff(source, target, new MeyersDiff<>()); + return DiffUtils.diff(source, target, new MyersDiff<>()); } public static Patch diff(List original, List revised, diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java similarity index 95% rename from java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java rename to java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 7daa91d2..54ffed91 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -27,17 +27,17 @@ import java.util.function.BiPredicate; /** - * A clean-room implementation of Eugene Meyers greedy differencing algorithm. + * A clean-room implementation of Eugene Myers greedy differencing algorithm. */ -public final class MeyersDiff implements DiffAlgorithmI { +public final class MyersDiff implements DiffAlgorithmI { private final BiPredicate equalizer; - public MeyersDiff() { + public MyersDiff() { equalizer = Object::equals; } - public MeyersDiff(final BiPredicate equalizer) { + public MyersDiff(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @@ -187,13 +187,13 @@ public static DiffAlgorithmFactory factory() { @Override public DiffAlgorithmI create() { - return new MeyersDiff(); + return new MyersDiff(); } @Override public DiffAlgorithmI create(BiPredicate < T, T > equalizer) { - return new MeyersDiff(equalizer); + return new MyersDiff(equalizer); } }; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java similarity index 96% rename from java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java rename to java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java index bb3577f8..6d4451b0 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -30,15 +30,15 @@ * * @author tw */ -public class MeyersDiffWithLinearSpace implements DiffAlgorithmI { +public class MyersDiffWithLinearSpace implements DiffAlgorithmI { private final BiPredicate equalizer; - public MeyersDiffWithLinearSpace() { + public MyersDiffWithLinearSpace() { equalizer = Object::equals; } - public MeyersDiffWithLinearSpace(final BiPredicate equalizer) { + public MyersDiffWithLinearSpace(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @@ -231,13 +231,13 @@ public static DiffAlgorithmFactory factory() { @Override public DiffAlgorithmI create() { - return new MeyersDiffWithLinearSpace(); + return new MyersDiffWithLinearSpace(); } @Override public DiffAlgorithmI create(BiPredicate < T, T > equalizer) { - return new MeyersDiffWithLinearSpace(equalizer); + return new MyersDiffWithLinearSpace(equalizer); } }; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index 000b095a..a753a513 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -132,7 +132,7 @@ public void testDiffMissesChangeForkDnaumenkoIssue31() { } /** - * To test this, the greedy meyer algorithm is not suitable. + * To test this, the greedy Myer algorithm is not suitable. */ @Test @Disabled diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 10db0e43..1e233a8c 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -34,7 +34,7 @@ public class MyersDiffTest { public void testDiffMyersExample1Forward() { List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MeyersDiff().computeDiff(original, revised, null)); + final Patch patch = Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); assertNotNull(patch); assertEquals(4, patch.getDeltas().size()); assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); @@ -47,7 +47,7 @@ public void testDiffMyersExample1ForwardWithListener() { List logdata = new ArrayList<>(); final Patch patch = Patch.generate(original, revised, - new MeyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { @Override public void diffStart() { logdata.add("start"); diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java similarity index 90% rename from java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java rename to java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java index 7a10f1f1..b63876ac 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java @@ -30,13 +30,13 @@ * * @author tw */ -public class MeyersDiffWithLinearSpaceTest { +public class MyersDiffWithLinearSpaceTest { @Test public void testDiffMyersExample1Forward() { List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MeyersDiffWithLinearSpace().computeDiff(original, revised, null)); + final Patch patch = Patch.generate(original, revised, new MyersDiffWithLinearSpace().computeDiff(original, revised, null)); assertNotNull(patch); System.out.println(patch); assertEquals(5, patch.getDeltas().size()); @@ -50,7 +50,7 @@ public void testDiffMyersExample1ForwardWithListener() { List logdata = new ArrayList<>(); final Patch patch = Patch.generate(original, revised, - new MeyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { + new MyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { @Override public void diffStart() { logdata.add("start"); @@ -84,7 +84,7 @@ public void testPerformanceProblemsIssue124() { .collect(toList()); long start = System.currentTimeMillis(); - Patch diff = DiffUtils.diff(old, newl, new MeyersDiffWithLinearSpace()); + Patch diff = DiffUtils.diff(old, newl, new MyersDiffWithLinearSpace()); long end = System.currentTimeMillis(); System.out.println("Finished in " + (end - start) + "ms and resulted " + diff.getDeltas().size() + " deltas"); } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java similarity index 97% rename from java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java rename to java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java index 65ea1839..522eff58 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java @@ -22,14 +22,14 @@ import com.github.difflib.DiffUtils; -public class WithMeyersDiffWithLinearSpacePatchTest { +public class WithMyersDiffWithLinearSpacePatchTest { @Test public void testPatch_Insert() { final List insertTest_from = Arrays.asList("hhh"); final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); - final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MeyersDiffWithLinearSpace()); + final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MyersDiffWithLinearSpace()); try { assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); } catch (PatchFailedException e) { @@ -42,7 +42,7 @@ public void testPatch_Delete() { final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); final List deleteTest_to = Arrays.asList("ggg"); - final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MeyersDiffWithLinearSpace()); + final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MyersDiffWithLinearSpace()); try { assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); } catch (PatchFailedException e) { @@ -55,7 +55,7 @@ public void testPatch_Change() { final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); try { assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); } catch (PatchFailedException e) { @@ -166,7 +166,7 @@ public void testPatch_Serializable() throws IOException, ClassNotFoundException final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(patch); @@ -189,7 +189,7 @@ public void testPatch_Change_withExceptionProcessor() { final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MeyersDiffWithLinearSpace()); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); changeTest_from.set(2, "CDC"); diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index 82e72294..a9731514 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -14,8 +14,8 @@ import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffAlgorithmFactory; -import com.github.difflib.algorithm.myers.MeyersDiff; -import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.params.ParameterizedTest; @@ -25,14 +25,13 @@ public class PatchWithAllDiffAlgorithmsTest { private static Stream provideAlgorithms() { - return Stream.of( - Arguments.of(MeyersDiff.factory()), - Arguments.of(MeyersDiffWithLinearSpace.factory())); + return Stream.of(Arguments.of(MyersDiff.factory()), + Arguments.of(MyersDiffWithLinearSpace.factory())); } @AfterAll public static void afterAll() { - DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); } @ParameterizedTest diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java similarity index 98% rename from java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java rename to java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java index 98f4f6eb..97dc20fb 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java @@ -28,7 +28,7 @@ * * @author tw */ -public class PatchWithMeyerDiffTest { +public class PatchWithMyerDiffTest { @Test public void testPatch_Change_withExceptionProcessor() { diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java similarity index 85% rename from java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java rename to java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java index 8a62992e..2cc334ad 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java @@ -16,8 +16,8 @@ package com.github.difflib.patch; import com.github.difflib.DiffUtils; -import com.github.difflib.algorithm.myers.MeyersDiff; -import com.github.difflib.algorithm.myers.MeyersDiffWithLinearSpace; +import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterAll; @@ -30,16 +30,16 @@ * * @author tw */ -public class PatchWithMeyerDiffWithLinearSpaceTest { +public class PatchWithMyerDiffWithLinearSpaceTest { @BeforeAll public static void setupClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiffWithLinearSpace.factory()); + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.factory()); } @AfterAll public static void resetClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MeyersDiff.factory()); + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); } @Test From 9f88cf91fba746da96f66ed110745e3deec10121 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 4 Jan 2023 21:53:00 +0100 Subject: [PATCH 054/107] added some example code into the readme examples --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 5999e79d..5dd7d5d4 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,46 @@ These two outputs are generated using this java-diff-utils. The source code can **Producing a one liner including all difference information.** +```Java +//create a configured DiffRowGenerator +DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") //introduce markdown style for strikethrough + .newTag(f -> "**") //introduce markdown style for bold + .build(); + +//compute the differences for two test texts. +List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence."), + Arrays.asList("This is a test for diffutils.")); + +System.out.println(rows.get(0).getOldLine()); +``` + This is a test ~senctence~**for diffutils**. **Producing a side by side view of computed differences.** +```Java +DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); +List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), + Arrays.asList("This is a test for diffutils.", "This is the second line.")); + +System.out.println("|original|new|"); +System.out.println("|--------|---|"); +for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); +} +``` + |original|new| |--------|---| |This is a test ~senctence~.|This is a test **for diffutils**.| From 02532a9720a9560e8f3ae039837cf960a6338e3e Mon Sep 17 00:00:00 2001 From: Mohammad Ali Al Salti <48101123+mhamadli@users.noreply.github.com> Date: Tue, 21 Mar 2023 23:54:11 +0300 Subject: [PATCH 055/107] Update JavaDoc for DiffUtils class (#163) --- .../java/com/github/difflib/DiffUtils.java | 119 +++++++++++------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index 493d905b..8917772b 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -30,7 +30,7 @@ import java.util.function.BiPredicate; /** - * Implements the difference and patching engine + * Utility class to implement the difference and patching engine. */ public final class DiffUtils { @@ -39,38 +39,63 @@ public final class DiffUtils { */ static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); + /** + * Sets the default diff algorithm factory to be used by all diff routines. + * + * @param factory a {@link DiffAlgorithmFactory} represnting the new default diff algorithm factory. + */ public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { DEFAULT_DIFF = factory; } /** - * Computes the difference between the original and revised list of elements - * with default diff algorithm + * Computes the difference between two sequences of elements using the default diff algorithm. * - * @param types to be diffed - * @param original The original text. Must not be {@code null}. - * @param revised The revised text. Must not be {@code null}. - * @param progress progress listener - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); } + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ public static Patch diff(List original, List revised) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); } + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. + * @param includeEqualParts a {@link boolean} represnting whether to include equal parts in the resulting patch. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ public static Patch diff(List original, List revised, boolean includeEqualParts) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); } /** - * Computes the difference between the original and revised text. + * Computes the difference between two strings using the default diff algorithm. + * + * @param sourceText a {@link String} represnting the original string. Must not be {@code null}. + * @param targetText a {@link String} represnting the revised string. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised strings. Never {@code null}. */ public static Patch diff(String sourceText, String targetText, - DiffAlgorithmListener progress) { + DiffAlgorithmListener progress) { return DiffUtils.diff( Arrays.asList(sourceText.split("\n")), Arrays.asList(targetText.split("\n")), progress); @@ -80,17 +105,16 @@ public static Patch diff(String sourceText, String targetText, * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param source The original text. Must not be {@code null}. - * @param target The revised text. Must not be {@code null}. - * - * @param equalizer the equalizer object to replace the default compare + * @param source a {@link List} represnting the original text. Must not be {@code null}. + * @param target a {@link List} represnting the revised text. Must not be {@code null}. + * @param equalizer a {@link BiPredicate} represnting the equalizer object to replace the default compare * algorithm (Object.equals). If {@code null} the default equalizer of the - * default algorithm is used.. + * default algorithm is used. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ public static Patch diff(List source, List target, - BiPredicate equalizer) { + BiPredicate equalizer) { if (equalizer != null) { return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer)); @@ -99,7 +123,7 @@ public static Patch diff(List source, List target, } public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { + DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { return diff(original, revised, algorithm, progress, false); } @@ -107,17 +131,17 @@ public static Patch diff(List original, List revised, * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param original The original text. Must not be {@code null}. - * @param revised The revised text. Must not be {@code null}. - * @param algorithm The diff algorithm. Must not be {@code null}. - * @param progress The diff algorithm listener. + * @param original a {@link List} represnting the original text. Must not be {@code null}. + * @param revised a {@link List} represnting the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} represnting the diff algorithm listener. * @param includeEqualParts Include equal data parts into the patch. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress, - boolean includeEqualParts) { + DiffAlgorithmI algorithm, DiffAlgorithmListener progress, + boolean includeEqualParts) { Objects.requireNonNull(original, "original must not be null"); Objects.requireNonNull(revised, "revised must not be null"); Objects.requireNonNull(algorithm, "algorithm must not be null"); @@ -125,13 +149,14 @@ public static Patch diff(List original, List revised, return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts); } + /** * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param original The original text. Must not be {@code null}. - * @param revised The revised text. Must not be {@code null}. - * @param algorithm The diff algorithm. Must not be {@code null}. + * @param original a {@link List} represnting the original text. Must not be {@code null}. + * @param revised a {@link List} represnting the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ @@ -144,9 +169,10 @@ public static Patch diff(List original, List revised, DiffAlgorithm * "trick" to make out of texts lists of characters, like DiffRowGenerator * does and merges those changes at the end together again. * - * @param original - * @param revised - * @return + * @param original a {@link String} represnting the original text. Must not be {@code null}. + * @param revised a {@link String} represnting the revised text. Must not be {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. */ public static Patch diffInline(String original, String revised) { List origList = new ArrayList<>(); @@ -165,20 +191,13 @@ public static Patch diffInline(String original, String revised) { return patch; } - private static List compressLines(List lines, String delimiter) { - if (lines.isEmpty()) { - return Collections.emptyList(); - } - return Collections.singletonList(String.join(delimiter, lines)); - } - /** - * Patch the original text with given patch + * Applies the given patch to the original list and returns the revised list. * - * @param original the original text - * @param patch the given patch - * @return the revised text - * @throws PatchFailedException if can't apply patch + * @param original a {@link List} represnting the original list. + * @param patch a {@link List} represnting the patch to apply. + * @return the revised list. + * @throws PatchFailedException if the patch cannot be applied. */ public static List patch(List original, Patch patch) throws PatchFailedException { @@ -186,16 +205,24 @@ public static List patch(List original, Patch patch) } /** - * Unpatch the revised text for a given patch + * Applies the given patch to the revised list and returns the original list. * - * @param revised the revised text - * @param patch the given patch - * @return the original text + * @param revised a {@link List} represnting the revised list. + * @param patch a {@link Patch} represnting the patch to apply. + * @return the original list. + * @throws PatchFailedException if the patch cannot be applied. */ public static List unpatch(List revised, Patch patch) { return patch.restore(revised); } + private static List compressLines(List lines, String delimiter) { + if (lines.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(String.join(delimiter, lines)); + } + private DiffUtils() { } } From 1664490a9fb1f5d896bafb83e38d0795aecefbfd Mon Sep 17 00:00:00 2001 From: xiaohaitang <39946319+1506085843@users.noreply.github.com> Date: Fri, 28 Apr 2023 04:54:38 +0800 Subject: [PATCH 056/107] Add generateOriginalAndDiff method and test class (#164) * Add generateOriginalAndDiff method and test class * Add generateOriginalAndDiff method and test class.I split long code into short methods, changed ambiguous variable names to explicit variable names * fixes #164 * fix build issues --- .../com/github/difflib/UnifiedDiffUtils.java | 151 ++++++++++++++++++ .../difflib/examples/OriginalAndDiffTest.java | 44 +++++ 2 files changed, 195 insertions(+) create mode 100644 java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 06a61073..e926560d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -19,11 +19,15 @@ import com.github.difflib.patch.Chunk; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * @@ -313,4 +317,151 @@ private static List getDeltaText(AbstractDelta delta) { private UnifiedDiffUtils() { } + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * + */ + public static List generateOriginalAndDiff(List original, List revised) { + return generateOriginalAndDiff(original, revised, null, null); + } + + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * @param originalFileName Original file name + * @param revisedFileName revised file name + */ + public static List generateOriginalAndDiff(List original, List revised, String originalFileName, String revisedFileName) { + String originalFileNameTemp = originalFileName; + String revisedFileNameTemp = originalFileName; + if (originalFileNameTemp == null) { + originalFileNameTemp = "original"; + } + if (revisedFileNameTemp == null) { + revisedFileNameTemp = "revised"; + } + Patch patch = DiffUtils.diff(original, revised); + List unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0); + if (unifiedDiff.isEmpty()) { + unifiedDiff.add("--- " + originalFileNameTemp); + unifiedDiff.add("+++ " + revisedFileNameTemp); + unifiedDiff.add("@@ -0,0 +0,0 @@"); + } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { + unifiedDiff.set(1, unifiedDiff.get(1)); + unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); + } + List originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList()); + return insertOrig(originalWithPrefix, unifiedDiff); + } + + //Insert the diff format to the original file + private static List insertOrig(List original, List unifiedDiff) { + List result = new ArrayList<>(); + List> diffList = new ArrayList<>(); + List diff = new ArrayList<>(); + for (int i = 0; i < unifiedDiff.size(); i++) { + String u = unifiedDiff.get(i); + if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + diff.add(u); + continue; + } + if (i == unifiedDiff.size() - 1) { + diff.add(u); + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + break; + } + diff.add(u); + } + insertOrig(diffList, result, original); + return result; + } + + //Insert the diff format to the original file + private static void insertOrig(List> diffList, List result, List original) { + for (int i = 0; i < diffList.size(); i++) { + List diff = diffList.get(i); + List nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); + String simb = i == 0 ? diff.get(2) : diff.get(0); + String nexSimb = nexDiff == null ? null : nexDiff.get(0); + insert(result, diff); + Map map = getRowMap(simb); + if (null != nexSimb) { + Map nexMap = getRowMap(nexSimb); + int start = 0; + if (map.get("orgRow") != 0) { + start = map.get("orgRow") + map.get("orgDel") - 1; + } + int end = nexMap.get("revRow") - 2; + insert(result, getOrigList(original, start, end)); + } + if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { + insert(result, getOrigList(original, 0, original.size() - 1)); + } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { + int start = map.get("orgRow") + map.get("orgDel") - 1; + start = start == -1 ? 0 : start; + insert(result, getOrigList(original, start, original.size() - 1)); + } + } + } + + //Insert the unchanged content in the source file into result + private static void insert(List result, List noChangeContent) { + for (String ins : noChangeContent) { + result.add(ins); + } + } + + //Parse the line containing @@ to get the modified line number to delete or add a few lines + private static Map getRowMap(String str) { + Map map = new HashMap<>(); + if (str.startsWith("@@")) { + String[] sp = str.split(" "); + String org = sp[1]; + String[] orgSp = org.split(","); + map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); + map.put("orgDel", Integer.valueOf(orgSp[1])); + String[] revSp = org.split(","); + map.put("revRow", Integer.valueOf(revSp[0].substring(1))); + map.put("revAdd", Integer.valueOf(revSp[1])); + } + return map; + } + + //Get the specified part of the line from the original file + private static List getOrigList(List originalWithPrefix, int start, int end) { + List list = new ArrayList<>(); + if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) { + int startTemp = start; + for (; startTemp <= end; startTemp++) { + list.add(originalWithPrefix.get(startTemp)); + } + } + return list; + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java new file mode 100644 index 00000000..7f2d3b29 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java @@ -0,0 +1,44 @@ +package com.github.difflib.examples; + +import com.github.difflib.TestConstants; +import com.github.difflib.UnifiedDiffUtils; +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.fail; + +public class OriginalAndDiffTest { + + @Test + public void testGenerateOriginalAndDiff() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } +} From 281c073c56a0cd0052963562e007c51ce2994983 Mon Sep 17 00:00:00 2001 From: xiaohaitang <39946319+1506085843@users.noreply.github.com> Date: Mon, 14 Aug 2023 03:55:34 +0800 Subject: [PATCH 057/107] A problem was found and fixed about #164 (#170) * Add generateOriginalAndDiff method and test class * Add generateOriginalAndDiff method and test class.I split long code into short methods, changed ambiguous variable names to explicit variable names * fixes #164 * fix build issues * fix issues about (#164),detail: When deleting the first line of comparison text and adding several lines of text, the first diff of the result returned by the generateOriginalAndDiff method is inserted incorrectly * add a test for #170 --------- Co-authored-by: xutao --- .../com/github/difflib/UnifiedDiffUtils.java | 6 +- .../difflib/examples/OriginalAndDiffTest.java | 15 +++ .../resources/mocks/issue_170_original.txt | 95 +++++++++++++++ .../resources/mocks/issue_170_revised.txt | 114 ++++++++++++++++++ 4 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 java-diff-utils/src/test/resources/mocks/issue_170_original.txt create mode 100644 java-diff-utils/src/test/resources/mocks/issue_170_revised.txt diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index e926560d..727008db 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -420,11 +420,11 @@ private static void insertOrig(List> diffList, List result, int end = nexMap.get("revRow") - 2; insert(result, getOrigList(original, start, end)); } + int start = map.get("orgRow") + map.get("orgDel") - 1; + start = start == -1 ? 0 : start; if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { - insert(result, getOrigList(original, 0, original.size() - 1)); + insert(result, getOrigList(original, start, original.size() - 1)); } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { - int start = map.get("orgRow") + map.get("orgDel") - 1; - start = start == -1 ? 0 : start; insert(result, getOrigList(original, start, original.size() - 1)); } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java index 7f2d3b29..17283b4d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java @@ -31,6 +31,21 @@ public void testGenerateOriginalAndDiff() { System.out.println(originalAndDiff.stream().collect(joining("\n"))); } + @Test + public void testGenerateOriginalAndDiffFirstLineChange() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + public static List fileToLines(String filename) throws FileNotFoundException, IOException { List lines = new ArrayList<>(); String line = ""; diff --git a/java-diff-utils/src/test/resources/mocks/issue_170_original.txt b/java-diff-utils/src/test/resources/mocks/issue_170_original.txt new file mode 100644 index 00000000..8aa703f0 --- /dev/null +++ b/java-diff-utils/src/test/resources/mocks/issue_170_original.txt @@ -0,0 +1,95 @@ +//According to the original text, an html will be generated by comparing the text +public class generateDiffHtmlTest { + /** + * Here's a simple example of getting a nice html page based on the original text and the contrasted text,Read n1.txt and n2.txt of D disk, and finally generate an html file + * + */ + @Test + public static void generateOriginalAndDiffDemo(){ + List origLines = getFileContent("D:\\n1.txt"); + List revLines =getFileContent("D:\\n2.txt"); + List originalAndDiff =UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + //System.out.println(originalAndDiff.size()); + generateDiffHtml(originalAndDiff,"D:\\diff.html"); + } + + /** + * get file content + * @param filePath file path + */ + public static List getFileContent(String filePath) { + //origin + List fileContent =null; + File file = new File(filePath); + try { + fileContent = Files.readAllLines(file.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + return fileContent; + } + + /** + * The html file is generated by the difference diff between the two files, and the detailed content of the file comparison can be seen by opening the html file + * + */ + public static void generateDiffHtml(List diffString, String htmlPath) { + StringBuilder builder = new StringBuilder(); + for (String line : diffString) { + builder.append(line); + builder.append("\n"); + } + String githubCss = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/github.min.css"; + String diff2htmlCss = "https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"; + String diff2htmlJs = "https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"; + + String template = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + ""; + template = template.replace("temp", builder.toString()); + FileWriter fileWriter = null; + try { + fileWriter = new FileWriter(htmlPath); + BufferedWriter buf = new BufferedWriter(fileWriter); + buf.write(template); + buf.close(); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/java-diff-utils/src/test/resources/mocks/issue_170_revised.txt b/java-diff-utils/src/test/resources/mocks/issue_170_revised.txt new file mode 100644 index 00000000..7aa91dc2 --- /dev/null +++ b/java-diff-utils/src/test/resources/mocks/issue_170_revised.txt @@ -0,0 +1,114 @@ +package com.github.difflib.examples; + +import com.github.difflib.UnifiedDiffUtils; +import org.junit.jupiter.api.Test; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +// According to the original text, an html will be generated by comparing the text. +public class generateDiffHtmlTest { + + /** + * Here's a simple example of getting a nice html page based on the original text and the contrasted text, + * Read n1.txt and n2.txt of D disk, and finally generate an html file + * + */ + @Test + public static void generateOriginalAndDiffDemo(){ + List origLines = getFileContent("D:\\n1.txt"); + List revLines = getFileContent("D:\\n2.txt"); + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + + //generateDiffHtml + generateDiffHtml(originalAndDiff,"D:\\diff.html"); + } + + /** + * get file content + * + * @param filePath file path + */ + public static List getFileContent(String filePath) { + //原始文件 + List fileContent = null; + File file = new File(filePath); + try { + fileContent = Files.readAllLines(file.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + return fileContent; + } + + /** + * The html file is generated by the difference diff between the two files, + * and the detailed content of the file comparison can be seen by opening the html file + * + * @param diffString The comparison result obtained by calling the above diffString method + * @param htmlPath Generated html path,e.g:/user/var/mbos/ent/21231/diff.html + */ + public static void generateDiffHtml(List diffString, String htmlPath) { + StringBuilder builder = new StringBuilder(); + for (String line : diffString) { + builder.append(line); + builder.append("\n"); + } + String githubCss = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.1/styles/github.min.css"; + String diff2htmlCss = "https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"; + String diff2htmlJs = "https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html-ui.min.js"; + + String template = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + ""; + template = template.replace("temp", builder.toString()); + FileWriter fileWriter = null; + try { + fileWriter = new FileWriter(htmlPath); + BufferedWriter buf = new BufferedWriter(fileWriter); + buf.write(template); + buf.close(); + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + From 8df690fdd20d6c89e86ac36299bf678002ae14ad Mon Sep 17 00:00:00 2001 From: "Pierre Baillet (Oct)" <88319985+pierrebailletsonos@users.noreply.github.com> Date: Wed, 22 Nov 2023 08:44:30 +0100 Subject: [PATCH 058/107] Typo on diff author's name (#176) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5dd7d5d4..70dfa521 100644 --- a/README.md +++ b/README.md @@ -85,13 +85,13 @@ for (DiffRow row : rows) { * producing human-readable differences * inline difference construction * Algorithms: - * Meyers Standard Algorithm - * Meyers with linear space improvement + * Myers Standard Algorithm + * Myers with linear space improvement * HistogramDiff using JGit Library ### Algorithms -* Meyer's diff +* Myer's diff * HistogramDiff But it can easily replaced by any other which is better for handing your texts. I have plan to add implementation of some in future. From 54324fb792dd0163bf6d7a4f255cfc08b702dc41 Mon Sep 17 00:00:00 2001 From: Joao Machado <13315199+machadoit@users.noreply.github.com> Date: Wed, 28 Feb 2024 21:07:49 +0000 Subject: [PATCH 059/107] Fixes #182 by parsing diffs with 'Binary files' (#183) --- .../difflib/unifieddiff/UnifiedDiffFile.java | 45 +++++++++++++++++++ .../unifieddiff/UnifiedDiffReader.java | 37 +++++++++++++-- .../unifieddiff/UnifiedDiffReaderTest.java | 41 +++++++++++++++++ .../problem_diff_issue182_add.diff | 4 ++ .../problem_diff_issue182_delete.diff | 4 ++ .../problem_diff_issue182_edit.diff | 3 ++ .../problem_diff_issue182_mode.diff | 3 ++ 7 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_add.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_delete.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_edit.diff create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_mode.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java index 099f4142..3a9723c6 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java @@ -33,7 +33,12 @@ public final class UnifiedDiffFile { private String toTimestamp; private String index; private String newFileMode; + private String oldMode; + private String newMode; private String deletedFileMode; + private String binaryAdded; + private String binaryDeleted; + private String binaryEdited; private Patch patch = new Patch<>(); private boolean noNewLineAtTheEndOfTheFile = false; private Integer similarityIndex; @@ -138,6 +143,46 @@ public void setDeletedFileMode(String deletedFileMode) { this.deletedFileMode = deletedFileMode; } + public String getOldMode() { + return oldMode; + } + + public void setOldMode(String oldMode) { + this.oldMode = oldMode; + } + + public String getNewMode() { + return newMode; + } + + public void setNewMode(String newMode) { + this.newMode = newMode; + } + + public String getBinaryAdded() { + return binaryAdded; + } + + public void setBinaryAdded(String binaryAdded) { + this.binaryAdded = binaryAdded; + } + + public String getBinaryDeleted() { + return binaryDeleted; + } + + public void setBinaryDeleted(String binaryDeleted) { + this.binaryDeleted = binaryDeleted; + } + + public String getBinaryEdited() { + return binaryEdited; + } + + public void setBinaryEdited(String binaryEdited) { + this.binaryEdited = binaryEdited; + } + public boolean isNoNewLineAtTheEndOfTheFile() { return noNewLineAtTheEndOfTheFile; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index 0ac22ce8..7306904c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -55,7 +55,11 @@ public final class UnifiedDiffReader { private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); private final UnifiedDiffLine DELETED_FILE_MODE = new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); - + private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode); + private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode); + private final UnifiedDiffLine BINARY_ADDED = new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded); + private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted); + private final UnifiedDiffLine BINARY_EDITED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited); private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine); private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine); @@ -99,8 +103,10 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, FROM_FILE, TO_FILE, RENAME_FROM, RENAME_TO, - NEW_FILE_MODE, DELETED_FILE_MODE, - CHUNK)) { + NEW_FILE_MODE, DELETED_FILE_MODE, + OLD_MODE, NEW_MODE, + BINARY_ADDED, BINARY_DELETED, + BINARY_EDITED, CHUNK)) { break; } else { headerTxt += line + "\n"; @@ -116,7 +122,10 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, FROM_FILE, TO_FILE, RENAME_FROM, RENAME_TO, - NEW_FILE_MODE, DELETED_FILE_MODE)) { + NEW_FILE_MODE, DELETED_FILE_MODE, + OLD_MODE, NEW_MODE, + BINARY_ADDED , BINARY_DELETED, + BINARY_EDITED)) { throw new UnifiedDiffParserException("expected file start line not found"); } line = READER.readLine(); @@ -346,6 +355,26 @@ private void processDeletedFileMode(MatchResult match, String line) { actualFile.setDeletedFileMode(match.group(1)); } + private void processOldMode(MatchResult match, String line) { + actualFile.setOldMode(match.group(1)); + } + + private void processNewMode(MatchResult match, String line) { + actualFile.setNewMode(match.group(1)); + } + + private void processBinaryAdded(MatchResult match, String line) { + actualFile.setBinaryAdded(match.group(1)); + } + + private void processBinaryDeleted(MatchResult match, String line) { + actualFile.setBinaryDeleted(match.group(1)); + } + + private void processBinaryEdited(MatchResult match, String line) { + actualFile.setBinaryEdited(match.group(1)); + } + private String extractFileName(String _line) { Matcher matcher = TIMESTAMP_REGEXP.matcher(_line); String line = _line; diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index dcf32182..1c0552a2 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -394,4 +394,45 @@ public void testParseIssue141() throws IOException { assertThat(file1.getFromFile()).isEqualTo("a.txt"); assertThat(file1.getToFile()).isEqualTo("a1.txt"); } + + @Test + public void testParseIssue182_add() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryAdded()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182_delete() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryDeleted()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182_edit() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryEdited()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182_mode() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getOldMode()).isEqualTo("100644"); + assertThat(file1.getNewMode()).isEqualTo("100755"); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_add.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_add.diff new file mode 100644 index 00000000..0c165456 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_add.diff @@ -0,0 +1,4 @@ +diff --git a/some-image.png b/some-image.png +new file mode 100644 +index 0000000..bc3b5b4 +Binary files /dev/null and b/some-image.png differ \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_delete.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_delete.diff new file mode 100644 index 00000000..6543eed7 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_delete.diff @@ -0,0 +1,4 @@ +diff --git a/some-image.png b/some-image.png +deleted file mode 100644 +index 0e68078..0000000 +Binary files a/some-image.png and /dev/null differ \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_edit.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_edit.diff new file mode 100644 index 00000000..7df4fc60 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_edit.diff @@ -0,0 +1,3 @@ +diff --git a/some-image.png b/some-image.png +index bc3b5b4..0e68078 100644 +Binary files a/some-image.png and b/some-image.png differ \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_mode.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_mode.diff new file mode 100644 index 00000000..8f0e6e7d --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_issue182_mode.diff @@ -0,0 +1,3 @@ +diff --git a/some-image.png b/some-image.png +old mode 100644 +new mode 100755 \ No newline at end of file From 50da99fed0ff93bd7ef00bd3ed57c87833617127 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Wed, 28 Feb 2024 22:09:46 +0100 Subject: [PATCH 060/107] corrected some naming problems --- .../github/difflib/unifieddiff/UnifiedDiffReaderTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 1c0552a2..431f7b06 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -396,7 +396,7 @@ public void testParseIssue141() throws IOException { } @Test - public void testParseIssue182_add() throws IOException { + public void testParseIssue182add() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); @@ -406,7 +406,7 @@ public void testParseIssue182_add() throws IOException { } @Test - public void testParseIssue182_delete() throws IOException { + public void testParseIssue182delete() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); @@ -416,7 +416,7 @@ public void testParseIssue182_delete() throws IOException { } @Test - public void testParseIssue182_edit() throws IOException { + public void testParseIssue182edit() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); @@ -426,7 +426,7 @@ public void testParseIssue182_edit() throws IOException { } @Test - public void testParseIssue182_mode() throws IOException { + public void testParseIssue182mode() throws IOException { UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); From 6279142d0acdc679a2ccc4ca006ce44766ea85c4 Mon Sep 17 00:00:00 2001 From: Sophiah Ho Date: Wed, 3 Apr 2024 01:35:40 -0400 Subject: [PATCH 061/107] Clarify what this parameter does without reference to internal logic, which also fixes the typo (#186) --- .../main/java/com/github/difflib/text/DiffRowGenerator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 9dbf052b..3ff34774 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -492,11 +492,10 @@ public Builder ignoreWhiteSpaces(boolean val) { } /** - * Give the originial old and new text lines to Diffrow without any - * additional processing and without any tags to highlight the change. + * Report all lines without markup on the old or new text. * * @param val the value to set. Default: false. - * @return builder with configured reportLinesUnWrapped parameter + * @return builder with configured reportLinesUnchanged parameter */ public Builder reportLinesUnchanged(final boolean val) { reportLinesUnchanged = val; From ba60ae8ca471193fd99ed00247b9533d903008a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Apr 2024 07:18:35 +0200 Subject: [PATCH 062/107] Bump org.eclipse.jgit:org.eclipse.jgit in /java-diff-utils-jgit (#187) Bumps org.eclipse.jgit:org.eclipse.jgit from 5.8.1.202007141445-r to 5.13.3.202401111512-r. --- updated-dependencies: - dependency-name: org.eclipse.jgit:org.eclipse.jgit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- java-diff-utils-jgit/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 78b35257..6d0fbdd2 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -20,7 +20,7 @@ org.eclipse.jgit org.eclipse.jgit - 5.8.1.202007141445-r + 5.13.3.202401111512-r com.googlecode.javaewah From 903982517a747d012639c85bdbe05721f88b86d9 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sun, 7 Jul 2024 02:20:31 +0200 Subject: [PATCH 063/107] added a testcase --- .../com/github/difflib/DiffUtilsTest.java | 12 +++++++++ .../text/issue_189_delete_original.txt | 19 ++++++++++++++ .../difflib/text/issue_189_delete_revised.txt | 13 ++++++++++ .../text/issue_189_insert_original.txt | 19 ++++++++++++++ .../difflib/text/issue_189_insert_revised.txt | 25 +++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_original.txt create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_revised.txt create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_original.txt create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_revised.txt diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index a753a513..3a4a6995 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -13,6 +13,8 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -227,4 +229,14 @@ public void testDiff_ProblemIssue42() { assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); } + + @Test + public void testDiffPatchIssue189Problem() throws IOException { + String original = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt"))); + String revised = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt"))); + + Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + assertEquals(1, patch.getDeltas().size()); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_original.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_original.txt new file mode 100644 index 00000000..a8dcff9b --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_original.txt @@ -0,0 +1,19 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec mattis ipsum. Aliquam faucibus, augue quis faucibus scelerisque, sapien eros consequat erat, in semper quam nisi id ex. In viverra pulvinar tortor, non mollis eros ornare a. Praesent sagittis tincidunt ante, sed vulputate tellus faucibus quis. Sed efficitur, mi eu fringilla lobortis, lacus nisl aliquet ante, nec fermentum felis dui vel nunc. Fusce luctus rutrum ligula, sit amet suscipit est sollicitudin at. Suspendisse convallis ac dui a porttitor. Ut eget efficitur odio. In a facilisis quam, in imperdiet libero. Cras a orci in mi accumsan elementum. Duis at venenatis neque. Sed vel tellus ex. + +Nam non velit at nunc tempus cursus ut vel ipsum. Nam nec nisi a nisl pellentesque faucibus ut et purus. Nulla in felis efficitur, facilisis turpis volutpat, consequat nisi. Vivamus ullamcorper euismod ex, et lobortis erat tristique id. Etiam rhoncus ante non eros lacinia, in porttitor lorem blandit. Duis porta malesuada blandit. Suspendisse tincidunt id dolor non semper. Morbi et enim sem. Proin vitae elit purus. Phasellus vel enim eget nisl tristique fringilla ac sit amet leo. In malesuada commodo condimentum. Aliquam erat volutpat. Curabitur et nibh nulla. In accumsan odio vel tortor semper euismod. Vivamus ex orci, elementum id pellentesque nec, blandit imperdiet magna. + +Fusce congue lectus pulvinar odio finibus semper. Donec id quam dignissim, suscipit elit pharetra, scelerisque enim. Cras sit amet consectetur nisl. Fusce ornare velit lectus, a volutpat est finibus quis. Vestibulum dui odio, facilisis vel neque eu, blandit placerat purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent quis nunc fermentum, rutrum velit nec, feugiat odio. Quisque euismod nunc eu orci pharetra, eu egestas lectus hendrerit. Vivamus interdum, felis ac fringilla volutpat, elit purus rhoncus dui, ac vulputate turpis elit a nunc. Integer et nisl quis sapien euismod tempus. Duis feugiat leo a ipsum aliquam elementum nec non sapien. Mauris egestas lorem fermentum nisi mollis consequat. Donec velit elit, ornare vel erat at, placerat consequat ex. + +Sed gravida fermentum sapien, nec finibus odio imperdiet eu. Phasellus ornare non dui ac blandit. Fusce eu tristique dolor. Vivamus non interdum nisl. Mauris feugiat euismod purus sit amet bibendum. Nullam sit amet libero nec dui viverra lobortis. Phasellus quis velit in risus maximus fringilla. + +Nullam sed ipsum mollis ante lacinia ornare sed quis massa. Pellentesque finibus accumsan odio commodo fermentum. Morbi malesuada tincidunt tellus sit amet interdum. Cras consequat posuere maximus. In mauris metus, ornare vel augue id, ornare aliquam leo. Sed sapien mauris, laoreet rhoncus pharetra eget, placerat vel quam. Curabitur congue varius tellus vel porttitor. Ut ornare semper sem, et maximus dolor eleifend quis. Ut fringilla est diam, eget euismod justo tempus at. Nulla egestas, dolor a mollis imperdiet, velit ligula porta urna, nec laoreet nisl neque convallis turpis. Nulla non blandit lectus. + +Nunc gravida est ipsum, in mattis purus ultrices ac. Cras ultrices nulla interdum, pulvinar urna imperdiet, egestas dui. Morbi vitae euismod ex. In gravida id elit in ullamcorper. Sed nisl lacus, tristique ut dapibus in, mattis tristique quam. Nam pulvinar in purus id lacinia. Nulla faucibus orci elementum neque feugiat cursus. Fusce quis faucibus turpis. Phasellus eleifend rutrum elit, at viverra erat consequat id. Suspendisse vehicula, dui et dapibus tincidunt, metus metus sagittis sapien, ut sagittis neque urna ac lectus. Phasellus eleifend, augue ut facilisis elementum, quam nisi ornare diam, sit amet accumsan leo justo id augue. Aenean feugiat, leo vitae semper rutrum, velit odio vulputate enim, eget finibus dui eros in dui. Aenean aliquet metus sed est malesuada, sit amet posuere purus rutrum. Ut sed iaculis mauris. Proin at arcu congue, auctor diam pulvinar, iaculis magna. + +Suspendisse faucibus dapibus nisl, imperdiet tristique nibh lobortis sodales. Integer pretium laoreet dui non molestie. Morbi dignissim sit amet ex at semper. Proin venenatis augue quis magna aliquam, molestie ultricies sem auctor. Nulla ullamcorper eros dolor, at efficitur nunc egestas id. Integer leo eros, suscipit pharetra pharetra fringilla, tempus sed quam. Nam eu fringilla metus. Quisque in dolor turpis. + +Integer faucibus ligula at vulputate mattis. Donec in fringilla metus, vel consequat dui. Pellentesque et maximus massa. Aenean iaculis, neque in vestibulum gravida, ligula lorem interdum libero, ut luctus justo purus a neque. Fusce luctus placerat hendrerit. Integer tempor, nibh eget viverra bibendum, massa est lobortis lectus, et pretium mi odio eu justo. Suspendisse et commodo eros. Quisque consectetur quam libero, nec pulvinar nunc lacinia eget. Nunc vulputate blandit risus, vitae viverra ante pharetra id. + +Mauris convallis neque id hendrerit elementum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales orci felis, rutrum dapibus velit sodales vitae. Donec rhoncus fringilla neque sit amet blandit. Donec vel nisl volutpat, porta ligula a, dignissim massa. Praesent varius, orci a congue lobortis, nisi odio finibus felis, finibus gravida ligula ipsum maximus eros. Vestibulum id diam ac augue lobortis molestie. In hac habitasse platea dictumst. Quisque vulputate maximus gravida. Donec lobortis, velit in tempus cursus, sapien libero cursus ante, at ornare arcu massa id risus. Etiam metus erat, commodo vitae sodales sed, dignissim a metus. Aliquam eros ligula, dictum sit amet quam ullamcorper, iaculis congue quam. Vestibulum sollicitudin interdum enim vel aliquam. Fusce et leo sit amet nibh consectetur fringilla. + +Nullam ultrices est tincidunt turpis pellentesque, eget dapibus quam laoreet. Maecenas convallis suscipit nunc sed dignissim. Aliquam ex turpis, congue quis porttitor quis, efficitur nec sapien. Vivamus mattis elementum posuere. Aenean quis eros vitae sapien rhoncus posuere. Donec ultricies elit sed arcu ultrices imperdiet ut quis sem. Nullam sit amet mauris suscipit, porta massa at, porttitor leo. Duis tempor purus nec mollis pretium. Ut bibendum posuere mollis. Nulla vehicula, sapien id bibendum eleifend, ipsum est rhoncus ante, eget pulvinar justo magna a ante. Sed luctus arcu hendrerit, auctor sem ac, rhoncus tellus. Integer eu quam scelerisque, eleifend massa eu, congue sem. \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_revised.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_revised.txt new file mode 100644 index 00000000..eb861768 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_delete_revised.txt @@ -0,0 +1,13 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec mattis ipsum. Aliquam faucibus, augue quis faucibus scelerisque, sapien eros consequat erat, in semper quam nisi id ex. In viverra pulvinar tortor, non mollis eros ornare a. Praesent sagittis tincidunt ante, sed vulputate tellus faucibus quis. Sed efficitur, mi eu fringilla lobortis, lacus nisl aliquet ante, nec fermentum felis dui vel nunc. Fusce luctus rutrum ligula, sit amet suscipit est sollicitudin at. Suspendisse convallis ac dui a porttitor. Ut eget efficitur odio. In a facilisis quam, in imperdiet libero. Cras a orci in mi accumsan elementum. Duis at venenatis neque. Sed vel tellus ex. + +Nam non velit at nunc tempus cursus ut vel ipsum. Nam nec nisi a nisl pellentesque faucibus ut et purus. Nulla in felis efficitur, facilisis turpis volutpat, consequat nisi. Vivamus ullamcorper euismod ex, et lobortis erat tristique id. Etiam rhoncus ante non eros lacinia, in porttitor lorem blandit. Duis porta malesuada blandit. Suspendisse tincidunt id dolor non semper. Morbi et enim sem. Proin vitae elit purus. Phasellus vel enim eget nisl tristique fringilla ac sit amet leo. In malesuada commodo condimentum. Aliquam erat volutpat. Curabitur et nibh nulla. In accumsan odio vel tortor semper euismod. Vivamus ex orci, elementum id pellentesque nec, blandit imperdiet magna. + +Fusce congue lectus pulvinar odio finibus semper. Donec id quam dignissim, suscipit elit pharetra, scelerisque enim. Cras sit amet consectetur nisl. Fusce ornare velit lectus, a volutpat est finibus quis. Vestibulum dui odio, facilisis vel neque eu, blandit placerat purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent quis nunc fermentum, rutrum velit nec, feugiat odio. Quisque euismod nunc eu orci pharetra, eu egestas lectus hendrerit. Vivamus interdum, felis ac fringilla volutpat, elit purus rhoncus dui, ac vulputate turpis elit a nunc. Integer et nisl quis sapien euismod tempus. Duis feugiat leo a ipsum aliquam elementum nec non sapien. Mauris egestas lorem fermentum nisi mollis consequat. Donec velit elit, ornare vel erat at, placerat consequat ex. + +Sed gravida fermentum sapien, nec finibus odio imperdiet eu. Phasellus ornare non dui ac blandit. Fusce eu tristique dolor. Vivamus non interdum nisl. Mauris feugiat euismod purus sit amet bibendum. Nullam sit amet libero nec dui viverra lobortis. Phasellus quis velit in risus maximus fringilla. + +Nullam sed ipsum mollis ante lacinia ornare sed quis massa. Pellentesque finibus accumsan odio commodo fermentum. Morbi malesuada tincidunt tellus sit amet interdum. Cras consequat posuere maximus. In mauris metus, ornare vel augue id, ornare aliquam leo. Sed sapien mauris, laoreet rhoncus pharetra eget, placerat vel quam. Curabitur congue varius tellus vel porttitor. Ut ornare semper sem, et maximus dolor eleifend quis. Ut fringilla est diam, eget euismod justo tempus at. Nulla egestas, dolor a mollis imperdiet, velit ligula porta urna, nec laoreet nisl neque convallis turpis. Nulla non blandit lectus. + +Mauris convallis neque id hendrerit elementum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales orci felis, rutrum dapibus velit sodales vitae. Donec rhoncus fringilla neque sit amet blandit. Donec vel nisl volutpat, porta ligula a, dignissim massa. Praesent varius, orci a congue lobortis, nisi odio finibus felis, finibus gravida ligula ipsum maximus eros. Vestibulum id diam ac augue lobortis molestie. In hac habitasse platea dictumst. Quisque vulputate maximus gravida. Donec lobortis, velit in tempus cursus, sapien libero cursus ante, at ornare arcu massa id risus. Etiam metus erat, commodo vitae sodales sed, dignissim a metus. Aliquam eros ligula, dictum sit amet quam ullamcorper, iaculis congue quam. Vestibulum sollicitudin interdum enim vel aliquam. Fusce et leo sit amet nibh consectetur fringilla. + +Nullam ultrices est tincidunt turpis pellentesque, eget dapibus quam laoreet. Maecenas convallis suscipit nunc sed dignissim. Aliquam ex turpis, congue quis porttitor quis, efficitur nec sapien. Vivamus mattis elementum posuere. Aenean quis eros vitae sapien rhoncus posuere. Donec ultricies elit sed arcu ultrices imperdiet ut quis sem. Nulla vehicula, sapien id bibendum eleifend, ipsum est rhoncus ante, eget pulvinar justo magna a ante. Sed luctus arcu hendrerit, auctor sem ac, rhoncus tellus. Integer eu quam scelerisque, eleifend massa eu, congue sem. \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_original.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_original.txt new file mode 100644 index 00000000..a8dcff9b --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_original.txt @@ -0,0 +1,19 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec mattis ipsum. Aliquam faucibus, augue quis faucibus scelerisque, sapien eros consequat erat, in semper quam nisi id ex. In viverra pulvinar tortor, non mollis eros ornare a. Praesent sagittis tincidunt ante, sed vulputate tellus faucibus quis. Sed efficitur, mi eu fringilla lobortis, lacus nisl aliquet ante, nec fermentum felis dui vel nunc. Fusce luctus rutrum ligula, sit amet suscipit est sollicitudin at. Suspendisse convallis ac dui a porttitor. Ut eget efficitur odio. In a facilisis quam, in imperdiet libero. Cras a orci in mi accumsan elementum. Duis at venenatis neque. Sed vel tellus ex. + +Nam non velit at nunc tempus cursus ut vel ipsum. Nam nec nisi a nisl pellentesque faucibus ut et purus. Nulla in felis efficitur, facilisis turpis volutpat, consequat nisi. Vivamus ullamcorper euismod ex, et lobortis erat tristique id. Etiam rhoncus ante non eros lacinia, in porttitor lorem blandit. Duis porta malesuada blandit. Suspendisse tincidunt id dolor non semper. Morbi et enim sem. Proin vitae elit purus. Phasellus vel enim eget nisl tristique fringilla ac sit amet leo. In malesuada commodo condimentum. Aliquam erat volutpat. Curabitur et nibh nulla. In accumsan odio vel tortor semper euismod. Vivamus ex orci, elementum id pellentesque nec, blandit imperdiet magna. + +Fusce congue lectus pulvinar odio finibus semper. Donec id quam dignissim, suscipit elit pharetra, scelerisque enim. Cras sit amet consectetur nisl. Fusce ornare velit lectus, a volutpat est finibus quis. Vestibulum dui odio, facilisis vel neque eu, blandit placerat purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent quis nunc fermentum, rutrum velit nec, feugiat odio. Quisque euismod nunc eu orci pharetra, eu egestas lectus hendrerit. Vivamus interdum, felis ac fringilla volutpat, elit purus rhoncus dui, ac vulputate turpis elit a nunc. Integer et nisl quis sapien euismod tempus. Duis feugiat leo a ipsum aliquam elementum nec non sapien. Mauris egestas lorem fermentum nisi mollis consequat. Donec velit elit, ornare vel erat at, placerat consequat ex. + +Sed gravida fermentum sapien, nec finibus odio imperdiet eu. Phasellus ornare non dui ac blandit. Fusce eu tristique dolor. Vivamus non interdum nisl. Mauris feugiat euismod purus sit amet bibendum. Nullam sit amet libero nec dui viverra lobortis. Phasellus quis velit in risus maximus fringilla. + +Nullam sed ipsum mollis ante lacinia ornare sed quis massa. Pellentesque finibus accumsan odio commodo fermentum. Morbi malesuada tincidunt tellus sit amet interdum. Cras consequat posuere maximus. In mauris metus, ornare vel augue id, ornare aliquam leo. Sed sapien mauris, laoreet rhoncus pharetra eget, placerat vel quam. Curabitur congue varius tellus vel porttitor. Ut ornare semper sem, et maximus dolor eleifend quis. Ut fringilla est diam, eget euismod justo tempus at. Nulla egestas, dolor a mollis imperdiet, velit ligula porta urna, nec laoreet nisl neque convallis turpis. Nulla non blandit lectus. + +Nunc gravida est ipsum, in mattis purus ultrices ac. Cras ultrices nulla interdum, pulvinar urna imperdiet, egestas dui. Morbi vitae euismod ex. In gravida id elit in ullamcorper. Sed nisl lacus, tristique ut dapibus in, mattis tristique quam. Nam pulvinar in purus id lacinia. Nulla faucibus orci elementum neque feugiat cursus. Fusce quis faucibus turpis. Phasellus eleifend rutrum elit, at viverra erat consequat id. Suspendisse vehicula, dui et dapibus tincidunt, metus metus sagittis sapien, ut sagittis neque urna ac lectus. Phasellus eleifend, augue ut facilisis elementum, quam nisi ornare diam, sit amet accumsan leo justo id augue. Aenean feugiat, leo vitae semper rutrum, velit odio vulputate enim, eget finibus dui eros in dui. Aenean aliquet metus sed est malesuada, sit amet posuere purus rutrum. Ut sed iaculis mauris. Proin at arcu congue, auctor diam pulvinar, iaculis magna. + +Suspendisse faucibus dapibus nisl, imperdiet tristique nibh lobortis sodales. Integer pretium laoreet dui non molestie. Morbi dignissim sit amet ex at semper. Proin venenatis augue quis magna aliquam, molestie ultricies sem auctor. Nulla ullamcorper eros dolor, at efficitur nunc egestas id. Integer leo eros, suscipit pharetra pharetra fringilla, tempus sed quam. Nam eu fringilla metus. Quisque in dolor turpis. + +Integer faucibus ligula at vulputate mattis. Donec in fringilla metus, vel consequat dui. Pellentesque et maximus massa. Aenean iaculis, neque in vestibulum gravida, ligula lorem interdum libero, ut luctus justo purus a neque. Fusce luctus placerat hendrerit. Integer tempor, nibh eget viverra bibendum, massa est lobortis lectus, et pretium mi odio eu justo. Suspendisse et commodo eros. Quisque consectetur quam libero, nec pulvinar nunc lacinia eget. Nunc vulputate blandit risus, vitae viverra ante pharetra id. + +Mauris convallis neque id hendrerit elementum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales orci felis, rutrum dapibus velit sodales vitae. Donec rhoncus fringilla neque sit amet blandit. Donec vel nisl volutpat, porta ligula a, dignissim massa. Praesent varius, orci a congue lobortis, nisi odio finibus felis, finibus gravida ligula ipsum maximus eros. Vestibulum id diam ac augue lobortis molestie. In hac habitasse platea dictumst. Quisque vulputate maximus gravida. Donec lobortis, velit in tempus cursus, sapien libero cursus ante, at ornare arcu massa id risus. Etiam metus erat, commodo vitae sodales sed, dignissim a metus. Aliquam eros ligula, dictum sit amet quam ullamcorper, iaculis congue quam. Vestibulum sollicitudin interdum enim vel aliquam. Fusce et leo sit amet nibh consectetur fringilla. + +Nullam ultrices est tincidunt turpis pellentesque, eget dapibus quam laoreet. Maecenas convallis suscipit nunc sed dignissim. Aliquam ex turpis, congue quis porttitor quis, efficitur nec sapien. Vivamus mattis elementum posuere. Aenean quis eros vitae sapien rhoncus posuere. Donec ultricies elit sed arcu ultrices imperdiet ut quis sem. Nullam sit amet mauris suscipit, porta massa at, porttitor leo. Duis tempor purus nec mollis pretium. Ut bibendum posuere mollis. Nulla vehicula, sapien id bibendum eleifend, ipsum est rhoncus ante, eget pulvinar justo magna a ante. Sed luctus arcu hendrerit, auctor sem ac, rhoncus tellus. Integer eu quam scelerisque, eleifend massa eu, congue sem. \ No newline at end of file diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_revised.txt b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_revised.txt new file mode 100644 index 00000000..de883ed4 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/text/issue_189_insert_revised.txt @@ -0,0 +1,25 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec mattis ipsum. Aliquam faucibus, augue quis faucibus scelerisque, sapien eros consequat erat, in semper quam nisi id ex. In viverra pulvinar tortor, non mollis eros ornare a. Praesent sagittis tincidunt ante, sed vulputate tellus faucibus quis. Sed efficitur, mi eu fringilla lobortis, lacus nisl aliquet ante, nec fermentum felis dui vel nunc. Fusce luctus rutrum ligula, sit amet suscipit est sollicitudin at. Suspendisse convallis ac dui a porttitor. Ut eget efficitur odio. In a facilisis quam, in imperdiet libero. Cras a orci in mi accumsan elementum. Duis at venenatis neque. Sed vel tellus ex. + +Nam non velit at nunc tempus cursus ut vel ipsum. Nam nec nisi a nisl pellentesque faucibus ut et purus. Nulla in felis efficitur, facilisis turpis volutpat, consequat nisi. Vivamus ullamcorper euismod ex, et lobortis erat tristique id. Etiam rhoncus ante non eros lacinia, in porttitor lorem blandit. Duis porta malesuada blandit. Suspendisse tincidunt id dolor non semper. Morbi et enim sem. Proin vitae elit purus. Phasellus vel enim eget nisl tristique fringilla ac sit amet leo. In malesuada commodo condimentum. Aliquam erat volutpat. Curabitur et nibh nulla. In accumsan odio vel tortor semper euismod. Vivamus ex orci, elementum id pellentesque nec, blandit imperdiet magna. + +Fusce congue lectus pulvinar odio finibus semper. Donec id quam dignissim, suscipit elit pharetra, scelerisque enim. Cras sit amet consectetur nisl. Fusce ornare velit lectus, a volutpat est finibus quis. Vestibulum dui odio, facilisis vel neque eu, blandit placerat purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Praesent quis nunc fermentum, rutrum velit nec, feugiat odio. Quisque euismod nunc eu orci pharetra, eu egestas lectus hendrerit. Vivamus interdum, felis ac fringilla volutpat, elit purus rhoncus dui, ac vulputate turpis elit a nunc. Integer et nisl quis sapien euismod tempus. Duis feugiat leo a ipsum aliquam elementum nec non sapien. Mauris egestas lorem fermentum nisi mollis consequat. Donec velit elit, ornare vel erat at, placerat consequat ex. + +Sed gravida fermentum sapien, nec finibus odio imperdiet eu. Phasellus ornare non dui ac blandit. Fusce eu tristique dolor. Vivamus non interdum nisl. Mauris feugiat euismod purus sit amet bibendum. Nullam sit amet libero nec dui viverra lobortis. Phasellus quis velit in risus maximus fringilla. + +Nullam sed ipsum mollis ante lacinia ornare sed quis massa. Pellentesque finibus accumsan odio commodo fermentum. Morbi malesuada tincidunt tellus sit amet interdum. Cras consequat posuere maximus. In mauris metus, ornare vel augue id, ornare aliquam leo. Sed sapien mauris, laoreet rhoncus pharetra eget, placerat vel quam. Curabitur congue varius tellus vel porttitor. Ut ornare semper sem, et maximus dolor eleifend quis. Ut fringilla est diam, eget euismod justo tempus at. Nulla egestas, dolor a mollis imperdiet, velit ligula porta urna, nec laoreet nisl neque convallis turpis. Nulla non blandit lectus. + +Nunc gravida est ipsum, in mattis purus ultrices ac. Cras ultrices nulla interdum, pulvinar urna imperdiet, egestas dui. Morbi vitae euismod ex. In gravida id elit in ullamcorper. Sed nisl lacus, tristique ut dapibus in, mattis tristique quam. Nam pulvinar in purus id lacinia. Nulla faucibus orci elementum neque feugiat cursus. Fusce quis faucibus turpis. Phasellus eleifend rutrum elit, at viverra erat consequat id. Suspendisse vehicula, dui et dapibus tincidunt, metus metus sagittis sapien, ut sagittis neque urna ac lectus. Phasellus eleifend, augue ut facilisis elementum, quam nisi ornare diam, sit amet accumsan leo justo id augue. Aenean feugiat, leo vitae semper rutrum, velit odio vulputate enim, eget finibus dui eros in dui. Aenean aliquet metus sed est malesuada, sit amet posuere purus rutrum. Ut sed iaculis mauris. Proin at arcu congue, auctor diam pulvinar, iaculis magna. + +Sed gravida fermentum sapien, nec finibus odio imperdiet eu. Phasellus ornare non dui ac blandit. Fusce eu tristique dolor. Vivamus non interdum nisl. Mauris feugiat euismod purus sit amet bibendum. Nullam sit amet libero nec dui viverra lobortis. Phasellus quis velit in risus maximus fringilla. + +Nullam sed ipsum mollis ante lacinia ornare sed quis massa. Pellentesque finibus accumsan odio commodo fermentum. Morbi malesuada tincidunt tellus sit amet interdum. Cras consequat posuere maximus. In mauris metus, ornare vel augue id, ornare aliquam leo. Sed sapien mauris, laoreet rhoncus pharetra eget, placerat vel quam. Curabitur congue varius tellus vel porttitor. Ut ornare semper sem, et maximus dolor eleifend quis. Ut fringilla est diam, eget euismod justo tempus at. Nulla egestas, dolor a mollis imperdiet, velit ligula porta urna, nec laoreet nisl neque convallis turpis. Nulla non blandit lectus. + +Mauris convallis neque id hendrerit elementum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales orci felis, rutrum dapibus velit sodales vitae. Donec rhoncus fringilla neque sit amet blandit. Donec vel nisl volutpat, porta ligula a, dignissim massa. Praesent varius, orci a congue lobortis, nisi odio finibus felis, finibus gravida ligula ipsum maximus eros. Vestibulum id diam ac augue lobortis molestie. In hac habitasse platea dictumst. Quisque vulputate maximus gravida. Donec lobortis, velit in tempus cursus, sapien libero cursus ante, at ornare arcu massa id risus. Etiam metus erat, commodo vitae sodales sed, dignissim a metus. Aliquam eros ligula, dictum sit amet quam ullamcorper, iaculis congue quam. Vestibulum sollicitudin interdum enim vel aliquam. Fusce et leo sit amet nibh consectetur fringilla. + +Suspendisse faucibus dapibus nisl, imperdiet tristique nibh lobortis sodales. Integer pretium laoreet dui non molestie. Morbi dignissim sit amet ex at semper. Proin venenatis augue quis magna aliquam, molestie ultricies sem auctor. Nulla ullamcorper eros dolor, at efficitur nunc egestas id. Integer leo eros, suscipit pharetra pharetra fringilla, tempus sed quam. Nam eu fringilla metus. Quisque in dolor turpis. + +Integer faucibus ligula at vulputate mattis. Donec in fringilla metus, vel consequat dui. Pellentesque et maximus massa. Aenean iaculis, neque in vestibulum gravida, ligula lorem interdum libero, ut luctus justo purus a neque. Fusce luctus placerat hendrerit. Integer tempor, nibh eget viverra bibendum, massa est lobortis lectus, et pretium mi odio eu justo. Suspendisse et commodo eros. Quisque consectetur quam libero, nec pulvinar nunc lacinia eget. Nunc vulputate blandit risus, vitae viverra ante pharetra id. + +Mauris convallis neque id hendrerit elementum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales orci felis, rutrum dapibus velit sodales vitae. Donec rhoncus fringilla neque sit amet blandit. Donec vel nisl volutpat, porta ligula a, dignissim massa. Praesent varius, orci a congue lobortis, nisi odio finibus felis, finibus gravida ligula ipsum maximus eros. Vestibulum id diam ac augue lobortis molestie. In hac habitasse platea dictumst. Quisque vulputate maximus gravida. Donec lobortis, velit in tempus cursus, sapien libero cursus ante, at ornare arcu massa id risus. Etiam metus erat, commodo vitae sodales sed, dignissim a metus. Aliquam eros ligula, dictum sit amet quam ullamcorper, iaculis congue quam. Vestibulum sollicitudin interdum enim vel aliquam. Fusce et leo sit amet nibh consectetur fringilla. + +Nullam ultrices est tincidunt turpis pellentesque, eget dapibus quam laoreet. Maecenas convallis suscipit nunc sed dignissim. Aliquam ex turpis, congue quis porttitor quis, efficitur nec sapien. Vivamus mattis elementum posuere. Aenean quis eros vitae sapien rhoncus posuere. Donec ultricies elit sed arcu ultrices imperdiet ut quis sem. Nullam sit amet mauris suscipit, porta massa at, porttitor leo. Duis tempor purus nec mollis pretium. Ut bibendum posuere mollis. Nulla vehicula, sapien id bibendum eleifend, ipsum est rhoncus ante, eget pulvinar justo magna a ante. Sed luctus arcu hendrerit, auctor sem ac, rhoncus tellus. Integer eu quam scelerisque, eleifend massa eu, congue sem. \ No newline at end of file From 5cc2cf4021b2d5dd51394383e86b858ba7662b81 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Tue, 24 Sep 2024 21:20:24 +0200 Subject: [PATCH 064/107] corrected word splitter in DiffRowGenerator --- .../src/main/java/com/github/difflib/text/DiffRowGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 3ff34774..3711bfb6 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -67,7 +67,7 @@ public final class DiffRowGenerator { return list; }; - public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#]"); + public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#<>;:&\\']+"); /** * Splitting lines by word to achieve word by word diff checking. From aff6bfed2953298d031c0b2a5601c6b3da72bfb0 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Tue, 24 Sep 2024 21:46:04 +0200 Subject: [PATCH 065/107] fixes #193 - allow to parse copy commands from a diff --- .../difflib/unifieddiff/UnifiedDiffFile.java | 18 +++++++++++++ .../unifieddiff/UnifiedDiffReader.java | 13 ++++++++++ .../unifieddiff/UnifiedDiffReaderTest.java | 11 ++++++++ .../problem_diff_parsing_issue193.diff | 26 +++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_parsing_issue193.diff diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java index 3a9723c6..1ae3b7ca 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java @@ -30,6 +30,8 @@ public final class UnifiedDiffFile { private String toFile; private String renameFrom; private String renameTo; + private String copyFrom; + private String copyTo; private String toTimestamp; private String index; private String newFileMode; @@ -119,6 +121,22 @@ public void setRenameTo(String renameTo) { this.renameTo = renameTo; } + public String getCopyFrom() { + return copyFrom; + } + + public void setCopyFrom(String copyFrom) { + this.copyFrom = copyFrom; + } + + public String getCopyTo() { + return copyTo; + } + + public void setCopyTo(String copyTo) { + this.copyTo = copyTo; + } + public static UnifiedDiffFile from(String fromFile, String toFile, Patch patch) { UnifiedDiffFile file = new UnifiedDiffFile(); file.setFromFile(fromFile); diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index 7306904c..b3a66ab2 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -51,6 +51,9 @@ public final class UnifiedDiffReader { private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile); private final UnifiedDiffLine RENAME_FROM = new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom); private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); + + private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom); + private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo); private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); @@ -103,6 +106,7 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, FROM_FILE, TO_FILE, RENAME_FROM, RENAME_TO, + COPY_FROM, COPY_TO, NEW_FILE_MODE, DELETED_FILE_MODE, OLD_MODE, NEW_MODE, BINARY_ADDED, BINARY_DELETED, @@ -122,6 +126,7 @@ private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, FROM_FILE, TO_FILE, RENAME_FROM, RENAME_TO, + COPY_FROM, COPY_TO, NEW_FILE_MODE, DELETED_FILE_MODE, OLD_MODE, NEW_MODE, BINARY_ADDED , BINARY_DELETED, @@ -344,6 +349,14 @@ private void processRenameFrom(MatchResult match, String line) { private void processRenameTo(MatchResult match, String line) { actualFile.setRenameTo(match.group(1)); } + + private void processCopyFrom(MatchResult match, String line) { + actualFile.setCopyFrom(match.group(1)); + } + + private void processCopyTo(MatchResult match, String line) { + actualFile.setCopyTo(match.group(1)); + } private void processNewFileMode(MatchResult match, String line) { //initFileIfNecessary(); diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 431f7b06..3d851561 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -435,4 +435,15 @@ public void testParseIssue182mode() throws IOException { assertThat(file1.getOldMode()).isEqualTo("100644"); assertThat(file1.getNewMode()).isEqualTo("100755"); } + + @Test + public void testParseIssue193Copy() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue193.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getCopyFrom()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.pcf"); + assertThat(file1.getCopyTo()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf"); + } } diff --git a/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_parsing_issue193.diff b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_parsing_issue193.diff new file mode 100644 index 00000000..15cd6e13 --- /dev/null +++ b/java-diff-utils/src/test/resources/com/github/difflib/unifieddiff/problem_diff_parsing_issue193.diff @@ -0,0 +1,26 @@ +diff --git src://modules/configuration/config/web/pcf/account/AccountContactCV.pcf dst://modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf +similarity index 99% +copy from modules/configuration/config/web/pcf/account/AccountContactCV.pcf +copy to modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf +index 13efef5778..1a08b0befc 100644 +--- src://modules/configuration/config/web/pcf/account/AccountContactCV.pcf ++++ dst://modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf +@@ -1,16 +1,17 @@ + + + ++ id="AccountContactCV" ++ mode="default"> + + + + Date: Fri, 27 Sep 2024 23:31:24 +0200 Subject: [PATCH 066/107] introduced a more complicated test which can only be solved using the linear space version --- .../difflib/algorithm/myers/MyersDiff.java | 4 +- .../myers/MyersDiffWithLinearSpace.java | 4 +- .../difflib/text/DiffRowGeneratorTest.java | 57 +++++++++++++----- .../com/github/difflib/text/test.zip | Bin 0 -> 110173 bytes 4 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 java-diff-utils/src/test/resources/com/github/difflib/text/test.zip diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 54ffed91..2517de46 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -187,13 +187,13 @@ public static DiffAlgorithmFactory factory() { @Override public DiffAlgorithmI create() { - return new MyersDiff(); + return new MyersDiff<>(); } @Override public DiffAlgorithmI create(BiPredicate < T, T > equalizer) { - return new MyersDiff(equalizer); + return new MyersDiff<>(equalizer); } }; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java index 6d4451b0..ca4114c4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -231,13 +231,13 @@ public static DiffAlgorithmFactory factory() { @Override public DiffAlgorithmI create() { - return new MyersDiffWithLinearSpace(); + return new MyersDiffWithLinearSpace<>(); } @Override public DiffAlgorithmI create(BiPredicate < T, T > equalizer) { - return new MyersDiffWithLinearSpace(equalizer); + return new MyersDiffWithLinearSpace<>(equalizer); } }; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 897a37f9..f881c474 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -1,7 +1,12 @@ package com.github.difflib.text; +import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; @@ -721,7 +726,7 @@ public void testIssue129WithDeltaDecompression() { assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL"); } - + @Test public void testIssue129SkipDeltaDecompression() { List lines1 = Arrays.asList( @@ -743,17 +748,17 @@ public void testIssue129SkipDeltaDecompression() { "banana2", "banana3"); int[] entry = {1}; - String txt = - DiffRowGenerator.create() - .showInlineDiffs(true) - .decompressDeltas(false) - .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") - .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") - .build() - .generateDiffRows(lines1, lines2) - .stream() - .map(row -> row.getTag().toString()) - .collect(joining(" ")); + String txt + = DiffRowGenerator.create() + .showInlineDiffs(true) + .decompressDeltas(false) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); // .forEachOrdered(row -> { // System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, // row.getTag(), row.getOldLine(), row.getNewLine()); @@ -761,7 +766,7 @@ public void testIssue129SkipDeltaDecompression() { assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); } - + @Test public void testIssue129SkipWhitespaceChanges() throws IOException { String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")).collect(joining("\n")); @@ -780,9 +785,33 @@ public void testIssue129SkipWhitespaceChanges() throws IOException { Arrays.asList(revised.split("\n"))); assertThat(rows).hasSize(13); - + rows.stream() .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) .forEach(System.out::println); } + + @Test + public void testIssue188HangOnExamples() throws IOException, URISyntaxException { + try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), null);) { + List original = Files.readAllLines(zipFs.getPath("old.html")); + List revised = Files.readAllLines(zipFs.getPath("new.html")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .lineNormalizer(line -> line) + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .decompressDeltas(true) + .oldTag(f -> f ? "" : "") + .newTag(f -> f ? "" : "") + .build(); + + //List rows = generator.generateDiffRows(original, revised); + List rows = generator.generateDiffRows(original, DiffUtils.diff(original, revised, new MyersDiffWithLinearSpace<>() )); + + System.out.println(rows); + } + } } + diff --git a/java-diff-utils/src/test/resources/com/github/difflib/text/test.zip b/java-diff-utils/src/test/resources/com/github/difflib/text/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..7b071fcda15a824af0fe38c29196582251aeae10 GIT binary patch literal 110173 zcmYIv2UL?=)3xO)R}ruvDAj`WA|Sowq5@VxklsXkjnojT1w}fD)PRT}sPx_fD7}h^ z)R-VG^aKb5LJI#$@csU*bs;&NGiPSco;~x#b@lEZ{)hd*fdl^@U=zwWIsX2_*s9)v z1L`PtuyWvlhdoTv$;aJo#lk(2yC#8#`$A@$>E|@%fHC2pA|IpE%NN>o3eAf$cPvCk z#gz&^_FowlFfM=76WZL-d%}wA4R7WO8|;qw=k-3>7_FRx*RHlIQPBG^p|)w;zsi`Zewar2nop#RV!p& z4%ubj#PofaOt09gx8CkAG%rCAXnqYU*Njn?6$>1Q*cHemCa5{E>T=dLQvl_$r*FVC zG)~K{9zh!%j56H&i9!4#dsV4gK+K3zMo}Tt$Jx?{9ehoEm%Bp-i^$q-s9B#PV+229 z*)nu%p-bL%Z*A7D>^IL|sWowVI10U<2gz{rfa*xrpjv6A%N~0MJQXuUySfQ}9S(9Z z@<~v|1kYiwUn{#?IXTp+;rdDIZR%8)k=QHe)F-S5aBHD&`>@p9l;cG#PGGc=MzHYVaRTHe_8DIUhkfF zP#vo2_jW@GGz(vE&%fNb!U0jAbjQp0hWGPxM74i{msvt#PpjO`(>F>lc7AAfe!XY# zi#?q*(07wf=T$ZG(B&gC8|m#09`(GnlMRQ5Jr^Fm)hg!ZXG7p{rS9q$=&|fy?yT(+ zy-fX6${6p3Q1yHDTT3LYO~q5IWmwnLGHvOPXy={F$3D#N`W9v{EzcATdxU;6=F{5x zq-{V2b=2e24U@yXUEajS?vt(gLN*<$Rnm5Q)F(0|58vTJwtjwyYu}`V zk4j%H`~>g)W;8|)T@1w$|3u`W#>&c29`AH(VBz+#g_P<`OxLDHX0VI(_(yITe(gbX z$41ess|B{H5xTK?u!|3^-SK{{)O;ayi}_Qhax2DA9cMHGa3> z%Xe$LyDj+wu|ux4GmkrNM9$`(PBI$TiQT9pZ+Ec~=3J1ytScT>i?GCkEwl@q{V1gJ zqJc`?Z(qdR1E(?mSWHOT0`l0^;KnDJ|Lme4?lEg+T2(BL=T|7b^spad)4`7n4V~Fp zeqjK4)RNishxYEjy+RKgkByJ6Yl|MWUf$QLw0Qi649+P|S#fr04|P(LqEM@O@Ore$ zL5>>NPpk_w_lJ%f3GX@(XYX@~t`G*^sVb5;9`lc5dJ26Lsp!Xptvq{NSE_|hA6ctu zWDY|uw)?VOq?V%;{?Yo4c}a9HeKKjWTCH&PJ-NfB&a8+WEyE9G= z!Z%CWoX#0KQ+qC;)$q`%QY)yn+1AOe<<+TS_fY8GsX{J2HF4Lg6RRgT1Yc}q3Nv7?j?b!0+le87$&6=WyuEY3uY?)7_fewxT+f;# z@z3M8^K9QS4_=$M3srpDv7MSUD~tkk8a5zEE_o!`@@8d!Afs$4yr=!4;%D2X?5!C+}G8wPUx_$2UJ* z|0!~N^j4wVa~1*PR9#28H@9v-e&v41Vm&O^zfn2KMZao9`ul6X+tqqPx0B-q0$D_c z1-8b0VsAZPa!Qo2cyt>2*@fwi*uyV}Rb9MZa@`g?1GakR8TCX5&mVWJHLrX=F8}7e z-skVr0#_35EQ?F(>-Rs2>U}F9CNQ+&ckTCCQ9gy=rGrn5Q*-nZ{xp^0gAN)CndeR1 zDnG*GQ=a6tSu6GMR@iIlj^Kmb9-fJB?p=9!Af+-!K=@Bng`)0lFq&dc3rMdc3`J6+eW)cGlX z(fKORc$>(H)mzW6YbMUR@L-#+z6nXJ^xFDh?qkd}mgiPo%DcK97dDP`IY$mEf+Cb@8Ua-1Gmq6U+kL64eEHgBQ9qkyHWF2_ng8wOT6fpsDJ#g zn`tzQeDP1Ni>u7yoU81Sau(Z5571g~cHq$aZa`CVHbm6NKF}0;`0jolyyTR|P(X9( zZ4Vcvq2EFGCitZs6P*VywGTDvg$4B;-}3&j2{(Fm;^GlE)!bn+t3;m2XmVwh?#DZ> z>W-?yw-pp8)jwwSY@5!ibYH3qK!~3nvA7}ZTQ_!rlaT!)?cwDkvlT(FSW9mU&$Ue3 z+`pum_=ThB%$>C7@UhB-#}k(>AAh2saFK8)N->T-zBlp~GwEN?g9hfp8PWF{WZMZ#F3*u}Dj zg+IiwKRYzks+|3PO;Ez(T;uYrj2~~IZ?02Jqw5~ONSj0bM~N^Q86!LU-%d}{BG_Mb zRK5U54Lmbn!mm#yX&Fei-#S=lbSgzA`|Ws9_}K_{O_%B?AJ0T~*rw~HRo+UMDe*0x zUcL1|m={*+k;4%$#(Ux8`@`)$XC?GS&xpkL8wy9phLm}=H|Tqw7D3A79i)GXIb9w6 z!s0uIRnR^6q{<<6=bzJ1_)=V$gQCT17h{X`mK9NR3k^KRO;A(!q&^u11F zO%{1@UV7-8%oXL!4$3BZZFVUeD49#WsRE7;qyAWxcVtL&GAYdA-n+VIShe`74De1a z8T=r<20yCk@AiLW1~?@2e=mH2$*;g)$h4=p|1@n)66oOfVeu+UHaS=Q&Fj5MU8=0w zJ*V80X2a8jRt?EyZnmhfOH1tIT9M)dp{gsRWDWI#u@9PVr@Cr2@5Gl4>U2mPN_}%H zdrl)dT-!TSRU3P#P&0R7!>#X|4Me(2?zqsPMeNTrY-Dz?__E~#9LF@@j@N`!ySFcl zf-GU5M~js5?H*p$=m;qTtV)XX5d&5%vY*}vfAmBKcbORxreE;-oO-(JoM`(^i3`?m z+50~yU$jC-S1Otavh$>J@KL7c(m8C~K)$X^A8YUFk}}h4f4+>g_;~A-i14W`zz?%N zy{La)O3d9l2tUy`d2!ulyoDU9z!G3EooFe0UbM;%%#&48bmAaEE(aBQ+G zegzB*7vvkcrhhH-@~G#;n?VjSW4G)|Uia*`rTE7G7R#+|B@>QuN(FN`xaVHYC{Run zU`KW+r(SuxAuRD#HG6CegX^;`^J3C{>-EZZqD3%!%%JHLyPDZ%>8-w#a|U|{g!nkB zFnPZOtYm#l(%M|}mgJIsuH4WqJ1lL+}#vrx*24&?e{lQLuf z7Ha0tgVyP&K2Z23@?m?!fj9{-uc}L#V~gFlK%95NAjyTSgubw<(4>k860<6)*nMSI&=l~=pegg|v_ z&*Gbxa&hc2+axVSaxB)Y?x)jJ)9W_PtII>%hSlc{$72ob&83p?yae)w0m0&KlXai3 z1EMl0ykLXH*62k~wRgMvh~4N0mse(8@``$9m9C@2Pg8E65dC+PDOqpl%{CG@DCaJW zrnz?Gdk20I3^ogcNon<6E9`0ZrW&(k{E9x`skBIoV9e9#RO95TRTf8{ORhxj?}|6E z2?`mWulr1)it8uS_$yivI;t3-j*GI;?!GHBt~GrFkPY(2P`t(65cxODeY$tbx$I~M zgdnCp0}sL5k)~;J!BJALknBoyGb9ZzBqUo(Y3}ciesG;_{8P%-22S3*PW)c0l2&~0 z4YwS-f*VUGXT2gSOOJ^d#`ji@eY@mxu+AdtV4j3A5Xq!SCvIY=hGYdZQ@EvNd~a~w z%ZSLc&nyaFJDnFXOFsYpr8Bn(d*jzt8;<@Xb@zA=R12BXwRKc_C|07}D@{sCQQ(F~ zv42X<>d6y2#SaCP&NrO0i=5G%PA<@Dy9>x`>n+Exqxd4a;nbsKJ(DZv4j5h1UwP)H8Fi=fQ|(2XyWa_X z)>G<|ap*?S+TLi?e|sGTkY>Td?0DI)ur~)IYwjNS_};}NDdXOc=aTkfEjLmx+T+yJ$)1XnEIG;<*-|J)ZL%gmGPRs9BWCP)9_maAw1;an#c8Y19ZKueFNVSlHx=qji*n++SajQ~74kk37x|9e0hF*6fX!JmE!JR(pDJf=NP0 zGBiuHl4_IxFT0Sj@WVBYsKhb}t*`&`3md(&mAKv|uP?BhE&Np!sj1$T6v@7w-~Ynm z^R0-RQKe?s)DJ$n!qmW~LvQDKoiFNoVSE;nEPLV{%I}iao%|L@1;ya$**);Z^&u#K zb5;_jyD)bg)ptir;}EoRGW(yp-#IFZD|^o^Ja7l(-N%xT)LA(5^2j%8NU{m40`UqM znXyUI`*G~`%W#_6%}vw&-sS$ zKp)?43rmPc@QA#5Z_Cdl%Ow(>JbyRxoL0-T`|%H=4&)s>VrM65ALLIE>JrWsSKVpv zecfVaa_qXMl~&AA#UP4J`~#$7st~(R?w?1Rmyb{@f49UFBc46BFVrk6<8HLbx#gmD zyz5h3YMv_c+ey1$+7@3Ij-Q2}H=G4(*)xd^^nYzB1PcBro6y}VTX_0-fJjG6z&WCo*5$7)&u>Ql$7IUNhg_Z$ z)f77G{9F4#IKMut%*de}*TSxDxyTbGEJx4Rbb(m^WINEuw3&miYH5|3vgeUH5cloR?8fHzZ>{XJb1s_7 zpa10#0)!cAxG?H=41(mHO}9P~SN+st@NwmN5u*;?1BuGqZk{r+oKHK)d}XTJuWBvc zI(S}z>(8JCVE;j*vP-W9v<`9<@l~1BJ&yXf`3tYzwYvxJ?U*DaJ;?bOd_yw&vXt%d z_cv(M_I6QWE6yf8jTVE4)J9|3;|IAOO|5m@jdwM>sWS1{epZ>8%``rRUW-p6bA(S# z$z_32VyZ&+MYIS{b==|h?k*1I#IWmKowz3~Jb(ZJ}O?(h7R z7@tLvycmH{QFCJk~f;_FJYSQ=s0bM;B5yDYLFE}r(V#)>&je9nCKy~V1v zl>7-Xyz!-IyYRMhbLNvPcZ#VyEG=d|cRByvjn1Hv z>x~JrZ#>H;MqSi2ImSK~R>`G=8smp!W8#TQA%l`0MPiqJ3fmq3hu@3$lt-1smnx+N zP3Op+)LN59-GRvB%aZmzRMA`GTo)f6JMYq?bE(gPs>FgdycYW{ya!>cF41?2XDj+9 z>m_$r>wm+@$GY+12S5Hepg_!F*2F(iJvGL*JYHra!+C`t+4oz}uJmcLt%f%{bLmy< z+S{@0hX;b;g{4Mp?ygbQo<^d1X4!R7+}HY@B3cq%Bp*1P;k4DO63=6bz`~|6Ba*fc zTaKHprCz^Zx&FH4WqrGdxzwxXZ>TD(dNBKr?I9@kZT6Z7uA&e8de zMRMXdn752ma~y?3`$ZcqFTHxI^~H^BN?3MhE9aRT8ln6G;}h*$AN<1ex_P;R`=tJe zUzr*Th|BkNrTo(}<1BV9yd*ZX(MI}Nk!+9%XRtIh?#bo^@2{-ro#F|2LP z*j3GAA}lQS<z^Gjiv-I|%N6cZJH|L&RAAa)vG~&IUT!!qsH~#3n zYg)g>tmF`3w{QD89#2v<5T?ZUPkruuDPqk@sJ%xOSRXY}(>W7&BYWxIZOuF7i+CZ^ zvguQ|?E`e-?CO6$N3cTEli4)-ImDaFqG zcV-Kw-52`bO3GGWPfqdO(K+$wLhs+rRCm5vO?spvC@;t^af&1GpF<~}zSW{_2{Chi zD6daP-#YXUm%qA6h;o|mK+hLW-v;55fc}CnuZ$IndkG;Qhojn3=6sufF8Pi|ZPYjW zZ25B+F44G3I#tHL;B<@m)X#fcVyusdu~k?mDNx-v_6Ov4q534TXGk_Vg{+8%1}w1gRr<`Mx_6#;4r)zVonlIDDL( z92Sd9vQFBLgHCCi4DPAyD#<(ghJH7iyc%SPvHrNVrt4rl=U(!0baf|o_+CJwtI@k( z4dPI8{%Ln5%%mkci$?2Qkgi$f?`w@4gxA-zQ^4nXcQO;U`aJ&Q1kL7yB|M(|0hxNw zgV==Y^#3T1H|)uqwEFBM$<-HxkxMdl5+$_0FdYqIebJ$1u%#^CJf~J}ne8w4bp$!??yKU<>_iBi8yF_2U{?2tcRHHm-W3{Rt7R?j8X*Mc#F=Q_Xwi`(D zb~D;=5*6cTt`*0(4}dUT{*A~{*!sYeW${2BMAA7^zOaf#$H^s4gnX=SF6D$6bL)+> zJ)KKGU}6;-HND&EbXYWythY00<(?sf=lT&edoQUnVEzwh`V&mS!`OFmmd&YzCGB9}%|#FYhT5ab@;9ZTVGu0^Qxw_Y z2Cy*UUeXEtkNey#R3(N-*NNg+YteCoEaSxQf9QGsBG@<+0Q~N_0Us*47Ig3@pznV_ zsHu|~R{^EC1qT9JXGt}qqEg?Djf{1N*@TAEpXlF@q+LJj%JBs@GECZaS{20t#$l}7Pj<~0(Q3P|h?C`oHbK|O3zR8|i_ zBf?)0+6(65qOv%t`Nqjfp3L?m6?5I&y@^-b?Mv3yHn*k?@!Q7QQYp~Z_^8C3 z_HM_(y=p~utBla809Vp`H34t!Td+FGSz^7NzcpW>c@4tU zJnmZHKy-g#Of5i$s^gl5sMV04%^4CQrWxCvTjA}?cpWlotG9ilVeWRxpFQNV+3!&C zUHkBPd`Qsf+9v@k@fzkB`MlMRB5XFdhhMfsE{-ABpJ6|Cs}dpP+0XC89UsNm_n_$mauv!VNL9$ zQO6hK@*R5rrY}H~aqtv;<1U5gqVN%tf08SM!GWbM7}P!mIqr z@@CnDoS_Q-WU5)XwvZ!rSjn7c&!>KSc$&-~*J8yfF{T@JwQ;CXO1C+RL-AU>LNARH zQWvD)R5giIvTBMkZ(U^b)Lsi;BLuD6h8optgtFmHDcmPjL$K=XfBIQx7cj)A(qDRLBN=^d;XRF zXRvk6`4rFd=*vEyL5oxxb$t;2>lxL?lX7=qbnoOy&-(O-W|d=>osNOlS+8CV^KVEA zO*s+AjiYk1bXzqpQ)UK9gV2ouV#~iD^7x4*{>D_o^n79tO*MEOw=laIgnx7%cDtE{ zx*;{_;mO7W!jTaWpx%7Dw$Sq zep5oUvMJ?3^V-%%c;dH%SjECznlLSRJx|5WCvfg@sX{0L=eBN%46~MKsNR7z$el8z zz&&|Zx2FD(u}<)c3Gx7+Rf_#!x{7+hc-8>qrH6o7W&3tXtd3U4&rtRCf}B08kkEPs z^Cs#QuI;$yFpiVeBRyQynT?%r`(NH6jVFTd!(@JmkG!fJ#x31G+MeB zZtw|DX5KJV#49O7f>B$zsgpmpm!u${@X;Eo1GsT`jqeAxTCGW_Fx%arx-HI|3DFTOfzaP zl9SL2ld^tEh|y?OIfm{XYzUdchgu<7!fCV#YE3)agCvskub7ZvuGr_jdpM~X=Y{y| zj@;=EV-dT)&B_7X^>23UR%>hyv&Ej6F9}&vUW@c4BHQXK?t$v^~?XP`HV>S{!;a^AD)TP&Os0rJo!^ zz@t^la08)~TRf+q<2yPTNK^$aJSS%c_F$`oQ|iW(6=T}WqSfn1)FV68#Q_@)&{J3) zPX=1^^ERGfidV2_IEdbzwnwIx!a7lD;$7P%*S%5mNnnfVg+^ zyV8dQh~yMQHQoB zsosBLP)fNnv}{2qf!x{M48Znl+G!)`mZTVKcVdc)oA*}vo5g=G?+$L)OMc&eDXZPPI2Y%jeFr2;%mG(Ug?`O4N1&F@~O~0vY5XxM>jFU^A(gqycm6-Rs{YOshnB z+K{peZqziwox(Y%B_k09QWIA7yTs$ZU85DGpeD1uje)jNKFX1e@*S_{I^x)4xyCDO ze3bFc^4i6IlZuVc=;GSU--Q`|GRWpD>?w)WP480vIP8qa7Ay7^H~oe$dadby?wu|) zz9NPdW46>OC@5jKhTB=?8C%EH5tqI&x0Cu!o*Q#2vs(vmZuW$4?}nq;kWPXjgQ((0 zKWsJhHqN)ydarYIyw$g4Dpb>eLvyH+gOvBaVc6c}hmog-Y&X*p^argYXaxSlbKG3y z1C8x;oRx;v!`A1xE2|GQRAQKWaOl^K&pqa2j`tLLFdezCChRoRolMF-$8OikWvVWI zF8bPlQ0wz4#k^Lop{ZN>t)x`eJpJMITLtCkIV!=g#YZ~y|5_Dio;xU;ioDK0&7~(T z>~!?SuNQJ0$Mar1_m>SLx#f9k7+_nUKh`^0GUSY5?MSHcy!*~iLGZ-!=_<_Gw=bT5 ziD8bMQjA=DuVu=jW!7O{EY`(zr2i4mQ1>JCJb2~6{blDt*5f^kr5CIyf(0{N{f~lu zj4xHxKH|dH7;cATetPff`#>Wp$=)K~(a=J#t^XqX32e@B^so09JSGe+@6UKvCf25W znGnY$6jVo#It#y^&yoCcNpb-<7=3(6&?-5v3w23Sc)IB-dmHLw;KBbYEBh-W8;bOP zTmvD5I@fRaw;j1JeJ)po=|!by&axGiUFxn}#HjXAV|I|qNtU0lE|%B6kK9J#V>} z`V?9FV)giRd&$w&O0jbzsjRMTt2tul`o^wdtS3sDSg<)UaV+lJ9B*nb3`A*Y1ifZG z;wg6Gz9g>t`nUJr!k#}ixq8l7F7pI4fZgS4zM&dVuBphm@7l5%aelSpt54#;;Z7Yt zN_e2#WAp7%yfSy;`}))q%tr}>Cpri1UUXM)Jvvf#UrGGjh*Ok}MriJos?F(4Q44nV z7th`Fazwv?L%D`ZJOVEKoJU@Ke)s_Y&_Qf*~jE<#$l==EIjjZ_4|)kM_y>8D;G;=-V{2N{k|*z z#q-5S@lzRoQ18g&Umjom`R$mia4IWDv&$F%a^Evs-9zFCt&5M+eFqM79%+9RAFuY| z?Aw>{80_VOa*Y^K*K!+;^PIEmg^s?ab^!C`@QSi;(Jukz^g85Pl#9cU^uNDgm2dg& znxrtnOHc2_QIQ!A_Sh-U7`>Ca9|LEL`PkdYA&RUc>la?gRu^Bu@m|+ZDtw@Im)k7B z=gW_?zvfCWm{D*J3(AsCGist?v$cc1pW&xD-zI9r?5Ng>efm=SzFV;MxlK&a)mTBR zWpp>u4tzI1<23!B_k$m-$3Muv(7S387^wZ4xr2x;Rsa;`q^ zkq*-l&l*Em*7x)54@spLiXYgmCl^Gi#?9vhp1dlWTK#~SI^xT{t)G1XGhhGQMeig( zwBfn`RJ!7=u;;26>+~lsdcZeA3kY~O9Ve_V{3-l$%y-X*=YCg{#JM?UUnFpN3h^GF z7Cd`=nwCRm_5@!9AH6I=_?D5}`cb0yn_aJiV%MYcDVUP@CqQG>B;1;kuoIKqMD0BO z_)$ctFmn|3XBnZ1iGoO2GvE`L#}a0$q4?K+Tyc&~!^ zm;B$gm-Xg9WqMHw32lfAJckRuz{Uctr6$C)75WA5Uuz!1354p;U%nArnC}vl6IQIg zNpr0>F=E$Dy)L~QXPm=pI&An$uy{geD?8!lU4EU&7Pu``_dIv`s*yH&@9yf-wSwAX z_Vt_Aw=RwNlKqwljj;^ERBG7|cC%?a}xD;3IJxE=w3?)t! zH9om?s{V^V%e@BKcyX)plk+H-)2p3mnTPP-uJYsE zbV`AGwX9yn{`}%4M1o~~ghDXp;nbsF{MP@oDeu0M{GjJW@dsTfL%C{;{)K3}DgG~_+~da({#Rc0Pi#s>qE{S;hRa>t zLV1*aYDrwlxN7ICc$VAwv->sllgEZeRLH;Rd$zeSs5TMh=f>qJ=86b-qfPj!Ry@{t z4xZb*!s>#)8%j2&)SqW@{A!cGRMJJfgxQu7Lihc5jgZ339)q8D-hBLne|AVTY<&H) zc2|4$4OeZyEI*{21=aSNw=q-kaM9>JBUlnmb2=fEC$qnPS$iYz{Oab7lpnzXPiEUb zf{0!(hgw)J&?dDTl7^@+8zt0(HXHcLo1I&Z`6&wM%*dZ-o#G6h=qfEh)bD9OO)x}5 zec;}MIU#@EW9$l8klk8-UxT(zdIBl#^A_#ov4Nxyq(+A+RoK-Z==moU4-Rt zo`=!+r^c5f>KC58?kVt^{V_DV&h>9W$rmlJdx>#{&vqy^E3}iwT8(S%QUB!icP&KH z3d6iBaZnrEnCjQuK`?p4Xt~)zxd$WY<)i@1!LA+ck-hq@fOUyPzsk;kQR)I=gF_Rz z|I~`=n}5V@PfbNX;?gnfn6Vz?8I?+r!z4aDzAgGtqFDkGWyvGzI_d)iPM@+MZJ+VAbVjrdgxq!+iDFI@E;Y zm1~q-cvz}Wc{?&wbA)(cS+!po#zr;=C5d8(()jU7I0qu zS1h7?>A-^@1=p)x5IcoP)cEzEe5NgmHI}g+D<`U)jh2VGi9O;4^{4=?R~fM@GwT73 zUlhuhI&|*k@JeN{DxfEov{-)}cB^m3u;hCv`nEbZe`U${WKqb_nt9UtnOi645O{mU zLy=c9iW~Hh_V@HZKigktWscMUZ<&q^{;Z$+#ghL3e3$|IJLvn_S9VzPxvj-c(_lN} zltxFwi`D@Ld??>0?X$Hy9!?y$DWSYK4*4O4A6AZjrCvLndeoLTbLgA@NVG5dhi7K% zU>RSKyt!;!y}e>|9>>6By|0q`YU=>&JmH1h<#XbXu0=Xs36baTyV;DbHypPE)tl_6SsjAZ+ImA2%^;XB%hhZ3nUurkZujq+jOxtza;;u=otT;O^Y$uGUY-vi zBwIFbb*ij7js1`=8m~o(x6~gk^%&dA{NYckx0zXoT4^5EzKirLu9*>8sdNxw#i5bbVjj8Erm5Z$z z$1Zx*7;p7<&cmsSqrTlUC?B*&Fk-c~UpcG9G&uxXR%}@Wc^i}Uw$MCldAqG&t)<>( z)V*ZO-5DLi=Sv)9mxrcpg-5P`cCWZnqjZz^yE-CfU{IcF_q)UcLtI9Uq*IkvUQ?xG zYQB<~eh$i$zhN(ryOY~}&254aT$9Quw{QM@@+Fg7`4KY`YmJaymUMI*1Yd-VPlDiS z-j=81h;!4k;W-t1>(hS-q=i2OE7A&K+=R6AuVWT}bgV#n4d&m`=%4~+rbg$Yf{O1< zu2^rhYGFaMM?H0}akZ4X;md80fMXmeHI_BY*m>LJ?PM1G2r2+E*H{EqdFM+g^|0LP ztpVvOLHVKX!O*3sx7(c+_cPrmTIH9Cwo7h99yQK)YupD=zs4OPXop~ris-@8i*liS z)cg2qwfXv@=6<8vRsZU=8uz83dsLLiH}_;$#0ILm&pjZ{y?Rl8dCInWRK9mCE6bhO zjnq&hyKneV2m}Rvrkd2fDI)99Hd2Sg!7M z->!0>=#wWP`vXu8YnG= z>SVrY@AZ;e_956Q#Myes^dC94yeWAc_gEC+7)*F&_BlbX6s|6>P#>m* zR{a^qg@|KAFx_@qMlhe|y4P|6kz!A%MyrnZFWV9nPahq(u=O^dM2drZRrymwOY2ts z6?C1vZ854p8^+ia;;z}K%d>e&&5R+RAY26s%YZ#*`GGNPQ8PRj!F{jAY*KYRET$1I zO1InM&bNqtc~^KW=mJW08NuC$^>iyvDBVGz7O|}IHs!DfrSN5_Gc0RlzDwxf}Y z)u}63xUPvK!gw6}yq~!O&f+dP_L#+;$BwXDn_`%HmH!y3+V}~%IWuIhMALgLW*RG% zzy1$x%bI5Gz<6Q9eeYm09VT_6QnW`0eznS^MOYi68u)d-C0MfjHKx0q`xQ||Fh7cEdinQ)rcj|Hn8z~9m`_RP z{K-~uxl#GU()(7RK4yRVv1sNG_zP>q#*L9h_oI*Z4e|Nv|H;Hw1pbRugOUt)Bs$vq z7_Xq2Ty_hEZO?I7=KtAmOgxN7tBy6qs8M?Nch$i3|F{d)P>lvH$6d4bLxHR0|ASF& zz%vEbl$&S%U29A{gum8M06mLNLKFAT_LUhNf~E!kOff=_6jNjvj(R`-lc~esD$$QJ z*|BFh7*ecr9BEi4_N)dvm9h_&l9I&QNE|5{>n{HH7{nJuUa#gaJvlk|6$SgZ+53Nc zKP!o>$#2;P-X&O$qu%X1lNC912%i%|UveMV_oZQF_AB{X$r^FhMT+4FK~hCzt-9U2 zwgpvF(D-)~w(_gFTFqBf&DwtWNM;>oKgzUQud6{^) zB*p0C*xlk(#tI|}0GFiOSh+N_dc}C(x9!E;ujUT#rW(xyzyF1)IFsE!zfKIf?P&bh z629g3vzlVIBN*MsfALWo1}kYiwI4}|sQ+P6<9X*#(cPMBkWx$1ehx6RvtO50kZjJA zg#RtP{fbIX0(5`}x0ksuB-D6B}B{I}-a@A`tulSqM;6br1>H z-}IwF1$SsI2xqw;0i`eFO<=|CcW`q8ZUV6nCJC;nwjN+fD4r_q96~E3vAuzK13q_3Z^`xu7jMr8nBNwzA5oERJ~gO zGjIF%6l`cQ0@ft8uFe+Trjp{%hzY)(@y=hZlWeg7OseUM=Dm2Kwt*#xYb9Pc(WZlHMT=pr!|?c1)&}1Dz>$2sz=` zxSxSh6_ItL@n546J7wby`vRck7cjjA_E-=-92ro?0>`7ee|sY-uUAd?fzY{+9QuhU z@HhT@8^l=X3MNl@{@u(R@Be(tN*p>3Rq~8UfuW1{eFCK~hyego-u~~HX1)L>NG7wk zAT8_5UqC4j0Vox!SnVJ2xT^HZ&kI)z?hN>PN9CwSz%d{_Y=WC;T%x|o;t9r z5`R4nGdK;EcFsR7<(_xY8{@j56p$NV9G4VUh`;wCr~&J0b;MvyVT4;0>-7Is;Z<`+*Y(z_X%?d(-wIT>IFN z7L_H|lbdwfDF3WRY16NyRAwam7hDHhCib_$w;9ZC>!k$!wdqZ1lCIFhO_5V*ce+sC2quJth8G`cP(uivXNm?&< zaKQBZ4W3?l=DhVCy?r&6u8d-#I0KGE75c@bq(M6NSx2C%fB@1;&PanE!WqZ7G=v^O zge$Tyi2rZq@ZWDo)QAg#^!9DL4=_kT=v0zkcL;Cl?Cs0?(X`FTCFr4`@?dhZgcswP zjDCd71aQlVEFFdxKgMc5f;Q4i6uSj-XNvB#oUzAhJB%_&rsM~(7Ob21Pa{dAycynR zWcc?2c}at7uP_*eonHdktOiKnKQLH_ou35@qu%rjV^RAT>Y@P*pB<&Jbi^QM0kkO$ z`&sGq+U5dlhWmgq3aJ$Wuc@dT?AyT2@Lho=#c#Xhy0(-AzaLeK%p> z79&PSBT`AE3FBBgSd0ZwPn4ilRyysmmVhFeI$pphS?dYj?{rGU4FjLV*(_%49jwas zGZEJv5itt#oTf0wP*ZHU9}q2bYoW&u7|ecVAYmZKNplqpqNb6Etcx&72J)fDfldI& z9ccdh)ZbnN(hV|?jjP%hDg2Oggu=F(l>=6#nnCWMQBY*PEbN7(_n z7X!R?!1^*OndGSmN`+K21(^f?#z%TK4+r;E+&HfO_BptjWRsB^#0)J3+ffrJdi9cq zl%?0@aUS%j}Z9bkuYjv+LJ0KgN_QNbD83Ot=U z-YacwsP6(G#*j8fNJ?_+)rmJE0M%h_^cF$z_I*5EM8^edSxL_>B?H`R&H%x(rp5}) z;Gj!g!HD-Z)&UvLdSgij0v!?wG6$z8{D0L2iYwK>mjnhtdaSzWi`4JApfzGGed#Qx z*9O76f{`#-KTzEPL3(zu^KJ~%;h%#*67PaQ`cfEP@)v*w1RbHig8hZWp)>d?hHV}d z0SqGmQkkOkI+h7ky)1!=A=i+~M9`(yv7*4V`52mBifmIr66p~2IWr7FlE8#TFN!pS z6?!`dZ47K^paV}?XAA)(b&#@(?mjsH8djx>fi!FtY#OlcBVM5|l09!V%hHocvZb^9 zBc0`aS^M&u4;Y}M1|+hNvbb7DM*s!p!1@9@7YtAgmufs8PrI`+EHN=UFtQt{#Re#6 z2duW;z%EEz99?Na6Qfrcv5+2M6#yi2-_0FHT|%HB*I-fthtTD8Cn*##@zE3MfVsk$ zFb4*J*)+!0`|XU;%p?$WA#vDmXP}vhtm_bhh>yYe2=0YQ{FP7ABHe2SD!*0&=xLS! z4dF%muP^PB}Qx zapSL{kGaNlZovYF&VpYaz}N&m7zZ4RJ`;dR^ByvWP8(T*a^L{?1z`nJl}E39Sy`wp zoi>D4x}uo+F3@QxJNc6?qJ(_Fw;~4LWC@)nV3@#?VFA;bs|<2wEXDgf()lu3VsIKN zn|6-@7q+3CJ^=?o=$xJd!vuKGPs{@kJ54I!PG=E1GK64ry0JJgAoQnqFoL6qFcRF< z2(&M7?Jt6a8;XDD3k2W>iax=B%uA2ayF0uQ&=aFWuSSOuH#!{|J#b^cb2Ls9dasODxo$F(C{kNt2+MUganj%aW6l*Xk8PO$JRu=yhoC?9{ z+(wSu>0>K6!^xKzj^PyBCU30M=Ldt!&?(XVwTWS{mro}eT)Dxxk|nCSkK&Wb0l%?8 zf6(WIgGlLpjgrauLk0(&fXuAx0jkkQ$z5Fn^G?&5%g_h>{=xJ_5h}`zt)&Sb%F-J; z-U{+yMHZslKmrop>6`#lE(*$hO}A%Bwa0d@R(6@&C+%fMTNLp8HA~L} zRyuq~3!{Zuq_lr)>Y_u^$F_Phl&#I$fe&Uj zD^YQ0Ka5TX0J|+P>OAs#-?>x?nvA#0Y?09Z1^DQ8YfwCGT?kGlD{IEnfr?W})8h~w zkQsaLh0B_B+sGN>)MacwuB{6t&$NFKauH-jchEVZru+E7evk%Lj-hGbyO&E)^AXv< zYqF(a4K^N;05juhN?ncGcOrvl8h?*skGTRuaKM_xfHY;0qUkT#~vzewlqB`-S5+UQtRO`IM>)^^J7hzn(UiVzJSaYg{~1)NKxC0Z zGPvN}e>03Fmx2`>NO9CpLx%qjO;GSZ8SsNTL+P*vr!a6$gJcJ9r$F()y39jRRM_su z!p@UwRi!L=3SCElY(zJRuV|VeUnU-UqO_=PDnOvi6UZbg@IAco%w_yFZv=e-?38af zn@+DT@e&I#a?)wn>1G_Nq_klav#r3`LMR>!e4kZ7=Lqj#lBB_Yc!V(O%RP83Sj>!3 zsh~soy>9ki-xg7k*uW+ZF75{=%;=Q~*Qez@3JHTf+vP0Bga0Z2^t0w4k|R z1r)@7b@fqJ#Afc=A{JuyT>6(ezJS!HM%_&)PQ9rN$1;|$jHQ8WTi?_`52=p}yj{`y=umnroZ(?Uc;Z_fJeb2&|^y?7@?R7eY>tP&)R+Sm59G|2ahwC`<`WG^5{ zj(@|Eo&0P`ZFB`&jrNgoX!?KbU3pv#`1_YAzEoPRTs3I1u8MRY)3V8uLK0F_Zk^IG zUDF_hkX>}>7_u5kCpFd7Oe7&mx|^oy&{R{?R8uq6T))qBXG5rs-)sN*`o1ig&wQTG zbG)DT^L})F@bId~FVsUGKWdK5?)5(k^fdKnq~y+^YT&zS`+pCO>2zNJ{%UIbpG$fT z(L2Fc%I4&O_iM_MdmTRb3g^4i=>gzMRQo?&mwj0wiT_U?uj%{ZU;C1BSvlaPVIK57 z(Lh%Wn{9@;IoCDlb)RvOi5>K$?x6Xu4!zzE{Adv<@hB2}^*4)!`^@x+=Sk0f%24pb z!!!pz)p9D4*Rtcl`=|v29|}Sv_xyW-eQ)t~Za#K45`0RNe!B^TFmrN&!?isb6T=T8 zz}#>UQe@1#C3Xrc%6s4b>(`BJ6yaW5ZO`z*4Xn9U9c*4QSy)b(es6g@HHN!vQ^f<-mghYm7P$CDc1&{J-{2zk2+6dbpaI^MOiMS7{|7CX^#9I5 zhlNPFC3c;5{thrU#h4XyNs{ED$}(i=B=THaVi+VT?=4kOd>Q`!?5Q;Xqj^eDeNM3$3gEa&4<4=_$W5 zJvkY8Gj~RH3yKL9{PtYq`&K` z%=AduQdqt#If;%)*b+kA74hiLQjtuXm@VoHBHu|U8{InKxWUHGtn3Ibg>q}9JKjvq z_wrH+19zk6MO*Xr8<|FDjI-YDrnbA?k<-|BFSKG;1Uk|rWmUAB(Qb^RR-j&Xq>W=k z>4^=dtsQ>2_E$gEUiVO26CLTOwO?xMuc3?wdb-PQ7$0WgwzRz30M9gXHN3Y{*Y8*v zxzy?A8<~TFi8k(r_o%j7^L9rp%Vfnlnn`U9m8ALCKV7w8kzCmyu7zf@h}d;^w(l(Zl#F0iF8kGH6%MeMx#W~-8o?E}3arctyT#YglQjfT$3LPOzFxsjPcHTf8P zs%m#f7Zk?W0vuCu_VrA{TPs+*4$0f>cvoA!bzX;oWJumNSi9U_yEfiVy>5J4J*xT= z2eNnlE^(3Fn|`jb|6L{!{PKscOhnkFJQYekD(udsW3kH~%-xo9Vbi-!Ki{>N$lH7= z=}3Fz4C77N*RyzY)@+b2TzxnB&D~pvAIG}xSKwlf9j9m4Vy*v_ ztozv;$)&vgBQqp;*A1Olf9Mn)%KT;FE^Ks7S=F=_JhK~mms=m7w8rlIvFs;nYr>M= zplOj)AeVO^cQ-2gEyj7TM`@%f%lzS%o1|S4ZFzA8X)8Ngl05g89|Cu3eQ34o?p?)3 z{JYG3)g8V?wTUX{6ImU;#ptr${bR3~k-ul{Y zH^YNtZ0~L_4|=OZ*c0`Rv8+IUli&U6b<4qz^IloJrm3y&)k8&UPB}a@M%Fw(v18lK zMCm|V^_L{$290%hl!9mAVt&$4@>6q%%7qx$-CG?_tE+t)$pBH-vodexUVFo( zt9Bn%mZCy|fhA1i<$!ck1_brvPd+=Xq4o}9+cdm(xi za*lO>PCw`8N7^-yxQY6Az0ry`yO`~(4c_RkB(3(WE9Bm`e#GX)J4pw%HgRw#s+jI= z2i1P_SYLNmXZ>Ho8XI4SJrSz&vCpX|UGl0gmduOU``B!Dt^)o35s#w4ER$xzy4$UL zk9vf5tv%|841^pJ=9)X{*7>bK`?>BYSnLUtmI3o^n~JxaZF`E0S>*%`_ZH@1x^tUn z-hu6mxv_rLQYtd~YHDgXHn+pK3i7k;D?*C0a8(vdu#nS!)xOatEN!(9uK_9Ib~XAJ zX^m#hYFMk5p&0WoJh#dKmfN%xg7;tjhWQuwzR98Mi7RD!TimEDPJT|QYl(I;VF zzTh>I%k7w+Qq(_{d7jSgO!>C!G+&0NRLDv;1@&~kOESAbr<2!$q!kx8G!UAvCLt-i zTOuE&<;pqj^)Oul#&9y*-q$xNBcYxc57)iw1#S-K(&@z(HuwNYh$9{1_v`vwFWc#V za9qu}jodnao||Gpoxc|q>8bjsWZFCy8=LQs-(OisC?-zgwMX(G+Xo~O*WC`^GyQ1L zq_F)0lhS{c>XAgWvp)2nB;tRPi2q3<{wIm}pCsacl8FCFBK{|d_@5-=f0Bs*Nh1Cy ziTIx+;(wBe|4AbLCyDr8VsRG+0v67e?}(lEc@h$s`!Xa1Sf336Dd3QL;g_uElCty7$I6a1IIT%Q?dK4Tt@ zVN18#%zJDK_1sfJBZ$o?E;csK4K;m98U)&f-b1Yb4ViY5IwgYwqy;)ckxe)|A5Dd-}vk`k?e-EFI+AlSWh4U|<^D z;|Le4fX2q6mBv?$qEVV@mQ?z~E<;$L>E3YGg5{9b>f`yIdGI;&*?WXK%yx*R9TtDw z%$fDpgJeX3vbN^>c1F*n@H8-HP1%d7#t5ywa+hW#al-IBTO+WXuw1{!!}jk8a&D_^ zd<=+GNy#&e!`o>)j<+0~R{(9L#szdTn()8kvy8#-ZoKnkYju`sYciAQuStaaJXOt+ z%=Jt;QGqCxvBH%2t8&V+eRonN)5_r4Pi1l>IXC^5W*(-QzN^eycb^q`N4m8zY?>O& z{T{6~{@nxSMs5nbslYSO_&B{Yg9i1?V-}RJzLdKnM-nS1&2!wNRQxQzk$mT!|E`id z;Dd?=9k$jfk`th1BVoUOGo z@AHHi9#_$hF|iW`r=hz%lD+E60>*T3p5y9F9H*^ZJE(0z zdd@k!Ny;?dnoCSr`}|{u!FR|$H8m%dB8#+(HBrBp-ClVY&rF$XKnFJzzCF()4^C0? zMH1?=Aa0*8f6`Mv}zUA4Rub$?14HDqgqkr9sjOWZoR87?bU71 zV%sg=>tXGY>V7^=JUvq7QZya;zrQmv+0}$w3?D6FTL?1Yxi75UcqJRJ$y*$t%ign_ z!YgvauB?!K3~S%L?`O#BGP&;arIoC8_nEDaOn)Z*l?ZD`IABewTlBGXl!T|Z|IKdb zDO=;rRTmgOXsG5U5~8#i0lP$mBb)0q%`Op{X4-_dS|gT@-94w)5MvgwOegz%@Fm+G zPm_J71Zz+8w}kkBFDoM^uI0hBvUX2tcb<VlA7M3%7Ds-fM3{Frb5jUNEs*@``^K?_6h#lU}lw71) zo#}O(Q{9agyG+A1W$#_%6I@8~VIs>?IjJYLLQT7JUN%2fUXIO7kx0NTdeB)==xM+B zzBQan^G(Z8KD?a=G_`eeHc;BwB&O>#IQWfMewj7loG_aF`p&qS6!*f!6&LNw@S5$J zENB!-i|Vf6tn)nF*E|R>m(4I$5aH%Kb%76fyZTWzIEIk~$vce`R`=Ah#VOBrFZ)*V zAD()(%F1$fxHT#o2q!mtR!)N)`X_h#3HpQ1ZhDaS{G^mL7KIVr;9_zrw;8XQ_A8G2 zp4>%kTt;_BC{Xj!aI_ z&W0fG7+V7aZbucPurc4Xw3&j_;qKdjOyV^L+3Cj^7;%YILpD*Ak8JZj}nQ)&8C?Y1bU;ABPGK zg_di{MIt*+@NzgARAQrK;UTUE9hR^LS**gSjfj83dTtO((nxlrAFg?4aBB;bv@24J zn5L5EX)4m*P3BfL64p^lbBG&N-$dqT(`YDIoJBRRK zwH>bYD-su@PTk&s<3z9Zbik~CZKez__F)Khv)hSqrWn(=_zj8%BksBh#k$(lISAD} zXhj9yvy;q~o7zqw#E{k>HwZp8 zqWxt9S&o^r@opEy&Wy;m?8v(wu6>w8_9)ct@I~Xxi8q2h>11*Tnr!SIT;Pv`>K{>q9l2_&OaGd!t~`L z4M`dvcx*D~cvCG&)5CX7bcd1{38CaOYHdt0{KajI8!yHc8Eu}foB zGxsF~>C&0v#@L^Ly76E#gm;4J&tg%+I~nc8ej95;>(KsouQucy^%qj(99=`NQO<>} zM=7g4$67`E2PrS@WZ{F&lzq7;Q+OWTDgR5x#YE@jHV2_AsmPo2X+bk0lC2k5DS6+& z%z00Ca=2LlDe@$&PNE-j>8NHgCL`Sy@JY#>#4Zh2UQ0J4@~=Wj&;o`NeRC>@fzz{< z*0j52MQ=yEbm?Fq?A`MeNpc=>9xS>_?MiIoxZKF-$N5BMv}Gxs|)H zQ26e1@fcnW_l`9qh^+p|+aIdYW$I8JWVbLT=n9hb)|z%_Qv(`FqtF`K2$67ZyC&bcfDoE<6;9)n_U>^O;@*4bL--X>25DMLkwk z+tvze_F&d+tMlKI=H;*R;v@pG%#3&wk6j zzg1GD*|IWrkJG8$mQx?&yP_wT!J_vFP-7ZoPZhcqPc4JJGZVoZCGB@rEZ71&9GKWZ{8!=XnUn2adup((g%<=v!av3 z)wFG>L{ChV)k4XRH3{mnjb5qhwv<36?VN-7iYTkwoOwC_D}h+9@$R4`MW1#;NGfTk z`7ZwlQmfF zka!bnN;>zgfM}!WiHELDMIBQKlM~pcS?D!y!PhKfiF~Z>U6QCJmqni9!)U5KY2Mq`*oMi(3pFV-Zs-K%PWPeQ zeQIJ(6z~hVKm=BzrUMVM-BRm|(MD%Sr$|*yCWyv1I;u9QDT-!olvF87x8DIjewF(` z(D9N|dYoXBr0MP1aj*i7-C0Jaj*lqv#E1S_qOlQ$iKw?J+RJNWV`LJ2C|=rv4~@YO zqW2w*F(i-qjkByyISH2(i052pnpT3#3rtK;6jF-Z zCcFo}Qz^mg)=?L!yh%>}jF@t&x;BClR9A`9Z}2!^b-LU6*r{#G2Mi-@g3S=Sj-BR& z&)C&QfSj)=PLI>tk)!-Fp`na+OHwg*uj3L*?#nf?gu---VBa&lMSt9VjKuF}RIL1U)+4Q!X()x7E(bpjcUv zxcRD5<`~Lkr)O?^?QHR8QI^tF*p!E-sBzsD2eBkbqsapSibC;Ztmt;AoyqMv@cZ~b zkDW3?n2F}-dTkTFuO*qNjp&dnL%RrHFh~Mvg+6Y~rg8O^H?ZjQcgmqcx zUHEno7G&hz5FY9LsNpejVake-#GK+F+1SQO-YHvIolTRn^q^li@iyx`LhGH^yId!w&v z>QH`OpPNpMc4^|wHIBNMyv^TdVv?LT!3w*A>^LMq*ijyN1gfhzu=j@Xf^*)TymnImO9HBGh z<}fp*+=J3s5T?(qbjv)h@fnAm2-bsGakVv48a9IdK#Gorc@acXOzX^Ht(TASA!8)=@lVNE}@ zca;?`1ll~OP}NqkX?I6df~gXzyP2V>{0hfAqw<(1lIXFVo4D8Ew}K+OTU3JYYE9$! zcj`7nD1|YiShB^gA}d^kYNDj!;h-FQ;+x05b*z-aok}#Vz;@Z&oJi*&r^AKM!@Zv? z6*}D6QQ(13BXl&Vwg0qNiI-uEX;MAw?RFt&pI9wYg4GaglN)DBa&2;bw_E?Xmi%gF zj&7VZS~?P!BOI9%?XkGs&^MI8DRHp%MQB?3ONp^EA5_2NI%P5>Wtipu)QwS`d-z&p ztJOs%)TyA2Mxt$<3<9eSx}5X-?Ik?@?M5^zX%h_I>{aMo?4dy5y+4MNvz2{(Ft=5e zfvsvqK7666mGlM!-xCbE*#`ZI$@WcK&!lR`t1Wbsu=h6-WIdk87DL`#!Uwz9frt8TyScHDOL&KixiBaIayvH%6I~t5G19 z?cfFR(-c18#GM|-p2aXM@2aZbPxrMyOwtX#c@9#0!v;eQN+!Rw_tek!FN7wRph#Q2 zL$Q?YQgP%5q(tPlM@Lsjn!&;9Q$igrOzsZOop=zhUCP>sIbDDmGRtKiVn- zg9;f0M+g(1w%z;l!gIE!SJDiJgcWC&GAcH9c z^4%>w3^GS6k&1!*%xKO(np^ugT8QW=YgtrEUsLsk#C3{WMoYrA%ylR@8D_aB-yHC3?F=7=-$CW1`1NObUixw4Z1Df2 zK*%RURH9CUKfH?nhh<}%CxJiad}<4I$X`(8{{@lP&=X|>+&9R|8~ELScj1phOnIGk zk&WnFt(5N8Y;-1XS9NhFDn`p~ccsA$_VJ67J8~a1p&`2>3={m1qu-o+;2+-@a5T*b z<>~0OxaP0(M~yJINcZ+Kn4XNhM~#hW$^SV{*}e3^Voyin;vJjJ<{wr))mYJaR5Ga` zJVhgQYqnd_%LYi!VG~By{(a^5mU`Bsu$xkomd7XEFln}Ufs!kX5h+3n%v80_m96Fk zMaTtkRSU{c9nXtR(jHfB^=2Pca(*)1-(z3yV>KB`cBl2+>tdIZUzY`?rE<7iv9D}o z!)IhmEvfoTK(^KlcecBAYqAr<_ddODORG+i*KViQh<$81|EuqEOG~_QR@)bZy_wsl z(X@x-C08v*cwKw91i8jk>jF*-`CRI~bC<$y{3?`br$d?oQ_A_lmQ)nI_LhJ2dv1uC z+kLUpnrneD*xQ{IbNoG=x^@)t?!14eA&2^%G|m5{9JMjtUp9nJiH}ipQSgWS%psn) zKKVk1Qd+2M%(;BN{CYHGsoN8f*x!y}h_Nt)Y0TXGdeC zRw}--Fb2h#6Or`uR5=NV1pRTn(+cuC6w0j9^kc)&73#&eE@SL(~Xc zJv=W~h^?;>rnz0H5aZCji;|yJy?eODgMTIX$JoUPxmwXD*=idPobR-FVU7Ib^(>NT z>pSmm*Uhi5Ij9{9-MD^tat=HPA6?OasLydd&f1vRa>wcXi3LB=w^lY$cjrycnwi~$~4N42vp%w>wADdTA*s6hix%~cOr1UyE>>cyn zYwz-t8{ZJ`nQeUYW>zk&EZaLYVR7iZr>-x`&o?o5l6<)M?hXZ1(_$&lmlu=wJX&!J zd1hX^E~3)7h=L{@6yeV_`z&vgBT7W$JF zr;A0VoS(k)VJ6neBsW9xjD&7UV^OsP-G%v-ysPgTWgmMO;)4|22P3-39Xl zznvABO=p-EoSz=vocJW3n7MED8=W<>lh}(CoEAJ+TqynmV}N)fWRo>16(QJsAlR1^j-8Y;J2|Rwna(@9l zp)loGdd3Q~#00%{h8QOi-MQ0_s0qn$k~Cz7u~!tFaxHNbFq9No!uB|^rP4|8w8a?- zt5#cL5&guKMVaD^lmwMb4YD#?Kn$(7GoyN;xZdgu^5zFBa6V=7PYQ@idlIpGd@_^o zXHGo@ITUW2k#t^Wau*{)uH-g#?kS92y6MakQLPV*8%`9jajuqF%_8C9esD6K!@?baIH5qg~wJ|iII*sQ8L zFT=?(3#QGS{r4Upcw=Iyx7CytS7VE#GFOn-;#DW~MmJXkOEV1!KOJP-AhI7Vjuv)m zyRW&oc-l^nyfO({%8hj0BlCmjOLmyrB*@NhIdRF~X3h221vaJ`;zDNvuL@kw%PdIy z75P6!b7>{h^a35PY0%`5pInIA<^hW`_obR(IyCmnY_>m}X1hjzt#4P(abOqM3u0At zr_7$QWM)7Bh8CuzC+~z2@uSKX9k7?Rv=^up4vZkkM^|0Y#vv#(l#ZzjRZQ{*GDYsZv@N$LsMrBUT z%Fxgw205HHEN8H}CxX%q8Hu%SPJ(5(+~pN~U!Tf!*&rmOcVR_n)-N(MU8Z2yoT@Ku zXmcSZhP*F~=-{y5l8wUkXm#{2M7#_Kz1yy6-VJ))WNthviJ$IYP?*=ULZ z<5*}BW8!1@`k<#1u!&c{D6W1Z{^Z%2X@bkhRP23`*?L49uftfMggwDN`}R-j{Ot%u z>9Uo|l52LT?lU4~R|Q;H;9Z`gR?e=i5#KJRbTr+xX;EZCQl(3PcZ9pc)f1Npp(Kdy zY?oIRiTh)0wM`tvj*$PH7dzMBMf$1fYcuHqht3N4nq#KUN>ekHi{2-V3^GQCrgz-d z4^7Z*od4S;yM0*xJN?q5e)^rNP3Sv)VHP4ALy+b9n1ygtE4vDhBX^*uY+9FJS(0>L z&MML=513l2wN)T+h>i2F%FlZ3IMe*HY$?lD~ zzCD4?%CMPiP)hJ?EEG9r)%@hO#yY_{HPh$Km0uk0B7Oa`e5H6(;-oNRG@;OlP>auw z%x`>XD8G-kYIEy0D`S3OkpY3ldVhicCj-mcQ3e*{i481yk@=h|e4#;znHc50s_Wwl z<7?GHMbRgApk1`}<4m)D9li5;vebsc_)eWqXW{XT8XCJW0%|mc^}n4S`DH)zqsitY z^A|3V>{vNU=kqe66{9=kHxEQwqFt z=ADpKe_^>sRz;d@L8%Cq39e48V|o*&gvMjm6h|(2TU729=z2(K_4C>5{Lj2hjv5|7o|w&?5;SAy5A9z6GDZj0^5*dLcFFI{EnDG? zHk|2kZTk634wsIqZTkOVs=vqRV+cGX`y9s{hEvMgy!IitP zk}#=Qar)2U1qXEZ2uMNgkaYZekBAK8HNIVi4UC%J*7N;ni$hKp`M}%>mRi>s{^6g2XNQ&dHs_4#er!BKADF2@lwL^d-3J`}o?}Pc}L`#0Q;;70d zk86YP?mM>3YiVjksEyTtZDJ41TqOTjpwR!9mv=2yjTlWvsY&U*S%EuF{hwsQs=@0M ze*f_@BKgP2ut8+!9t{(^&i5+-n-NSHczV*;et5SDj8oGrK?4A6f-h7vm7`3n!t|#v zv<<)h|ED}k^DIjYfrW@Js*~VnW6>f>CIaDAg42l`qQso@#&6N za^iPfGP4u?{y0KOjINp{_(U&j()0QQ6tf%9^Yi7!f@IApVZWV4iRn*IwOu5yckRHM ziYS7cG(eSAo|<9ipaZhl6o4h)_``Bfu*{i@Q^ke!UK8Lu7Ay-$AqZtVOIpRLNG2pr zYViWF`rP6Zzb_(bbxd9GSW2*kA(G@HKzOqJRhG;sqT*1nu3DE-8C#v)gz6cd{|`*#h#;E_enfy2BDi0$2R!{MbTMu!f3h;66N{UIKp zo0Hof4I4hh-kVtubSHdmhxMAyM80@d{=?#g%%Cp0Cb4A$5rTh*B~#k^E29GmMb&|~ zK+|kE4%#X|*gE`#U~-tXn1PbFdp98WjlQxK!wV(6hfKl{$6f^@S^Qj z{|Jc=qipxmCbr$%21jorbrJbMVXKjq3BSv`9CUeY_d8xDl9TAp;@uF+ij3Hi)Nm?b~R>FS9MC*uN0A)-s` zBwz`N{BrOF0AuO^3~FK$g_Z_9A;L%?FKP(L`$=a5pV$Y8SGT$Fn%0x7#q1;SsU#|Z zHRAwSGf`0tJar^CUv2InW#-cuz52xRNGd{Xm5gnIMtXV$n1n*N1E6D_{UK%?wGWEtS70h`a&Sd}880Ns)kCDI=5EL`Mmw5hu1;&24_X zVjVah=po@oINr|v86$%g-lGI7nkN>lK*Gyr1p?OskfwjQ7V2D`<$Dte`u}8={vftl z1#HuaaKsa?b_z6*3d`p3IWd0#h9%&{*Z?L4#Z@KKefr}OXtO`a0lTpI=U=d!M@(BR zO$$tetJ9LQNk{%cQu{%&mZxxZRu}UlanI+Lx0Q?B=>eed;KRlJK;aMa_T1npd*9$` zj`AZUotCDKKS`S#vGxMTp=XWROas{ZlN&hJAwUjcmx@g71teQrM?oo}EXr#?^do#2`;9*$;F zKNbFZ1`MbdRiEmm-M>AYbI(8G-4?<`iPU^Pm9#r zJsRnF{UZh6xs9?Of6hh|=deBmLY45)A0!5X{8Qp%`DZ`F$4BP%`d=wSP(oWe>j%kT zz#H_gVY5DMpqUCzl9CB!iw39Fa%e_BM)}|lcPob_*pT5py?Em&8-6c77~r7E_B%*} zgzTSQ7l_+h*$)|%Q0)9cZV(`6B7c6YeD5E;o1!3cX`jukp@j3gQ4-Ec6H7QhOVbyl zlS(1{X6^{(ZEr7r?Gd%^`wkL-TdK0y^B_yL(>czkFTXtPag`s1JDumE

N|ojP_1EK$zj?KxBk`1ZC=v@;yA>NGD0b>k^86!^E;|5x&1JF zR8W1B$WT7h@F&GeO&=(aU(s1BC`I8=_IbZr*!FA>oa@7ygpx#^&5-piU9j$?aLU&Tk6UE7V zI`(ky`Efe-cMt)oG5eN`G6ME!Ii$CV0Lh>aO_ZyY9s0`%GAk3@p=LBYD)T}(qkMpl zV3G%KHAROrf`3`#_5J0apv4(Z`)G@Eu=*RK8WUyZ(i{B-i$DH|@mhA(_jf!{J8<~! zbCmAIab~*dnZ?4(Uwr^ifDuRYjLUzHFbpyAuWuh89^}&`KMD^H)oajxn;AJ%|3~sJ zd!@<9RXxKVpN;Q*szx+1IsOH@ob4BJf&^FVxMB{DEc!FC?5tU7>cnWq=mTJn5RTcs z9_Oj?^pDRAa!ouyn21Gx9w6*bK>M}T;F3sD#mYpbkTtc5I0LCZ5?ag3tdVq^CiV)i zfh(lek>G0lG3tM^U_LA$`gF!PD;#EmL4P%qS2hTg+SBiuK$xmdT)jI8Z8I7i^aFuB z>$!Yg7$7>yITsj`W8q~(w=aiqP%H~(8=y+ZKxz49Vvgv=)e04Eq`0%b(bfwdo} z5q@+i&|57NsKc!-`{?~GQ4u+PJ?Eh3#J)kk8eSofd;aPRM?JwH`ssx6Z?cK(~txD0dFgaiRfvwKIKsmO%P& zUheD(jx6%QP_rZea3hD4Yk2b(e-vmjaR;eXKRm&935467aFoUYXT}eHxW# z18pZQ=usD=@teJi@fAl``!c6VnY=FM*G|vi&=I$+XfSHFe3pbKzdQOUiHnZ zeOjYW^^{Lt>5(&pgZY+e;R--McG$)BDN}wI7xxU44?!XxC)=R~zuYRV)d1i|LSJoR zK3VakQRs}th5=I5KnG*EvOJze5sQJgPtLQ z`*1Wv-J#y3PbBuc@g|5g-tL0I#FRff&^OrPBh(}ITMK-2¥GN5PUFApm4A@+9BE z+vZ?mu=?*QK@Wdcx!ChwQh=ED#z(nzT|x^sdq?@pHNOo2)F%* zo2x_d7TFLwG{YJgKMRENApB=NEK|82nk=NF@p9_?vg zmzeaQZjNNCZMvz_&Qzgn_!p;>>5=#G2-*4X)t8KMA#kliAIf8Q6bt|PEmqmdnzgg3 z*Hc=x^sXfMq8B$y>(DqI4)580lIQ+4b&hra#p$ngq7`Lp{dFK5C=}TSbH3A-okc2C)Uj!zcGTwn??O*=_{`(l00JqaZxa_XtZ}a~>B|yjRB2bO?Z%!E46ETtX z4gYZ6U3I$O=tclq(jlYW67XG}Z4oYn`wi*Vu?u}g=>|e&-1Y+P0B@@=GRR5D9evva z#u(jtM`*yHKw-ldPweH4e@S7JGH@>IxneKCVvg@Aj|8A(4Xuf{@jWg?6o?DjW zDBM>U+Jp9Z#v)ZnOjW{3@=H%PZduk_l0I+oNs_+sAo&Q$w)D!<0PKr9_MH z1?jy@pVWJ#BTh&7oS%jSEdwBwIQMy*<{?SNS>&qu-v>Mb@_tUfKMT%4fBVr^R7CmA zp9a=hU8}wz?^}6@KGE1e#t!K*>zgXkK@|CQ0oIA|HnSk4f6vhbSE78lZjgxjMbJoZ zf^`NgUduRpLnd3a=f`cy`y0v59D)XCvu!7A!LQKD-D3S?RbC{V#86d>E;FNE@aazS|4F zi(6~Q?d)(pPB%{Bmv3^YeU z+HXfv1Rd;>#^1J9PV~j0P|Zl%*Y-08Dw@Iep$fmln`Rq6(d|QAh$rsr5&sbmc6Ko~ zs&9wgsQTHoJ-rJT#~NP+2_&8Vjll6>r@prawf4Re7OHz9-gQ(b8^=yPVQezvL0Zhh z6_zpBD8tti+Bdua`i3-=ddQ$y$Edj#ALdy7MCU4r1)#DSbbR-OL0 z7c+xbc2)wDP;6STLNJ_3y`jB%;(EP!+?b$PgT66o)8R(c_UG+?u;%RMYgoH*r8f=qjq7Ke34x zplYE|TAWnmQREjh?0OjpBLp^JmHfrY$g|=&VMmhwbWoyB408nJ$X&(X#6kElNAB76 z-P3;3+4#y`do=#U!=Z1*<=8(uITDRjiW@4S)V~(Vb7{%_BsV3iI6G=%(x`-!y@guI{>TCC?1b2zr4{nFH_yu^I&mM0mVY}*p!Fe>QMClUQ`t>jF*a}Mg&D(!Th=s%De3(?jC627JdZto2ZVd7EBe08)KT6!bfCOloJ|8T#f0+}o0_*5oS#1Y z<_zvd5agq)F7VmuQW9gwPIm+B^sa0UCM^7$sT?CY7Zlf-A}jP%W9ZAHV(*L0{?|r1 z{bR-}zPIv-gyY*ArwJ|?J=C;n`Iwdx)EM8f;ZBW6EVTp)ckGUg>V@JTol^1$8y}V; zi0L#Elh$M~M(lv$i5W>h=b&K8F`#KY?O&f%1)L5!6qJa61RY!m^@ZagpPL9&vQ)|3 zC_2iqIhM@WStI~I8?zB(+9T?N(E6HRx=UtG)ch^84zTXW?aDT6xnH%|dz8~jhi8qQ z|3|8GyvL!<(3_X^G>E2S*O^bmLj$Fq+}JVsKEiZ13pLaLN(RIIqfiD|3brR0lthz; z76mx-F`nWi0_CD_rEw2y?D1PD12fZ^iW_$PV`v&1XCg@B(UD<35etS*O*eUunM}bf zl0Ke@`KpoW&q8FqSoQV{@!_L@smX7PB4+|Rd-eW;>dE6azVk0UHbtd}l%Z~ff3j@R zaX%Rta&+aDf7|d*FSqn#)0>+_vYGR(m!QuAHIO4+vX&VxBL7PI&u{4EqsEf-HrUYH z7kpr#Gqm7{=wL4L+)(V_P|W}?5`{#3>x|=vM4W#N6_@b*AcG$A_45MdLFomTpF1$Q zI$j>*feJD0#w+z7B)lcM|NE*EfEI@5rH!Y9i~~Z<^68s$&!I z?*Mlo6M>R=72_G!_@V^U3|lq(){HeVlDK5RCrYku1kM@j5IUz@q`kPdn5*mcOqbgIMnQDOZXRUh)VQ}+tCE;aXAm>buZ?^AZ^*Wcf2}sq*iZcxoJ?>n z{e;NBp#sP+uelxL*4H0QjeEgW50)$IbO24T_{0#QJna(PcxXs~dIp9>j(a`R4?z~U zFgETW2}f5ARi&gA6;Kb2Jsk{4$#L1O4ZhzXN;Y&>dPKbc1EIV-K8EF)isd6eHp~sV zk};(W<>oR;m&S+u^$(umW$vc+s3k94lL2ej9jy-INk`RlI^ZMJQEheCFi<2ubG) zt&I!8VuZJ5T5!JAY=eoUi0Q+n`<;dKh~uVgJ9hFPqX}shI3L@&%O3Y=El>9k5inF9 zH)I5ixxi3b2>h7NZ;ptK4Ar$Y+jVSYUE7S#Ga_TAYYTLrJiF|9QTNA%#yqSN9Z-IC zp;#QSD4lb3NW69*A2K0;jlV+tdo<+bk7;#9i~HHwlPwO&QUZm>P16?4+;$bmut?+B zo*#*;d^*-zGGUI7!;akblccW=l9;A07`rAm=st0>#z2LvPxker*sa3PVfXlC zCfDOP1%E`S5vPUFnEdqu(tLq6uG7E!(D(qP%8urLCdP~i>JZ|=SwRU~wb}HqmRJVn zSj$9@C#PZnfgCEc-x2SnVil(%nUFMTP-dUG5;2A;KOge(>~j70Z*5N^bLg$jb{H3~ zwXXrv^V^hq2lbIY!m1cqWuNax9?hXo8@F5pq>!vS*!cGhM_`@Ra(Ern&&m!fL>Unu zl-PAkziq39Kk8ouM*yS+vdBIb>soXBXbXFR-exb!4#Mk@P*4_szH6o_x#jYBUh16 z`PqA0dR&B-k)ByD z-}NOowpvy~(Mgbd?Oft-I2o|_nP3@G4d`c7^K{dhC8An8oKJq+Dj5RA_| zE)3k-Y7#wmA3AAp0noO(Z*m~O`#08&+tlgD3b!FR>u*v0%~7TICQ*jv1M8?3yR0sE zTt@zu-9Id_#pYk=)DO**2i8%k2v&gNruiqna}?`+XBfM_ccvx%4cn-CI-}fNM)SR_ zco}SSXQ}`~c!NyW(jyf?20@Fq%bF<0ip+B^ot-hOjA98Fu#W$2=Q=hu1as-3+RN+j zvwN4v(kq~!qkQj>gdk1!Zgb2JIwj`-f*8XG6*sPu%NlQOBeq={FA2&#_>|42}&{X*`hszn<04O+g zOWtYz#n~O*d^?6q%rcx}RQ9kfy4ul4&#A5J0;M z8YQ2Srv4Z1&HhRypM=A2xAvb*81*Z}cgS-C8BaunyTjEJmk7;6wyk~qxuu4f!Ayz4 zX0U*TzCG~`TZB8Y13hIGP=Z&Hr8JfeD8c_TzKQMNuQ&ccpn9m7Tkn*Wi^gc;ThB_- z*B4siql#A84)v77>Ir$gYe0%}-87Jyk1D=VY8tvammBDUF->?Bg0D)n8>kYU*k4eb zFrZ50H?WuT=p==n)mD%&q1PGVGd|pv`XTP#>H-)khyNJW>PCKI5?v*cjT-PKik&V~ zMqJzFj2v9sr9E^7V9$nIzn%@Z#7V8K?X6rF4SwJzB{0tUs4d+CmUca{-vKM$-N;wh z0ZY3^cx@Nv%Zmlc%6~3!*YKAcel~FT)_{IA!aK{516^a5P%>QH5KbMGoweY2z?n?& z@f1M06h)OC%hRc8;&AP%(gwm{3&_Np>3?rpa=WeUhiq>gl|Nae z6&gx|nSK|}q^HvHN+3E9S>lV9zR$|97f6osQF~gXR5_n)L8%Cq39e3L4cWXrbc?Sb ze>Y`Z?`}$=3%+O!x&}soO#Gt*ipQ4fXA=jk^69jUZATB7B?`dfydD-+4_UoT9I|?O z*e2MXEr3gVwg4(?yKAJSrMV+4PHKemi#Xao)>EC3pB{qs#Fa_+V+Pj~|1+tC{w^}A zBCeIcwk)wDYD zS8o=VNy<`gm@icj5zf*6JIdtC#SQ7eBfsnXE*xf$71$WPz@Uv*ak)zp>uL0hT?MJv-N1yag4TCD{Xh0(@j2DN@| zv8dD!7bJ=bm0b}NmL#Iobi@)m+M-2?)v2+i5^W2}8Zg>0g%~v=NZ13UAqEH`1hT#F zK9-oU8JJd{dX9QL#N*}NcmMzYZ@b8OaPGg%YWmULYyg2b1{du1IZS9K=mvFM5lPUT z?w{tSw6WBqUKV~21s=N{EJBBO&J(S47uTfw(=(GPUH#J>0uGd_d+ugfwRz;u>DkAP zA+Nuwom^Lf({nBW>P^B<9&-5km8gXrVLx<|ndRd5aB<0pfwr@glT>-xJ5ZpXZ`!}J zY$SBDcI4eqqoI`!-p@uWbtyvvQ>o9u#FLs}PFS}4z20?whK=*j_S1ZPl&yA+HaVtM zx_HbQdNPN<8QclGD+GV@s?V{n%2(Lgp%l?!SHl&p-$1a4A}CV(>uIsrYiwn9c+4JWvS zNG(y(A7200xf92Qmx?m4J%V$XiH{%swC_r|#5UP=PT=b4XYb#6Sok(HXXR+uL=AUy zf&;jiz4)WxA*+&+8s`Qq$Ltq#7+t6_2nsC<94rBWZU4m_$B<`Oo<`0z0K7_rQ>y=W z!-_Z4QD3|Z#Wf3wOZ=jpgLl_Is$rL7mQR0OICH^#qe(6vQy4>xLs}I8ZyabTMR(?o z#mNp7@4sZOMyRj$AnA=W7dW1Bg*5`%7BzQf|Fk2?lEcP$)jA(e1Tq4Xz)C3q zD_eDn&;A3>lfNTwbpZ4_GDOEUmlZc*ZUK5g>e){ZoH&@)usBX|s4#DA617Pvmi9@% zlOc6+UegB^-hIoj-;BhRvkkb9L?)v!EZ4twY@k^o>uywh_Zw0d2XDplG`9xlBllj* z%G_f#p~kO~hLtJWEXD)$2($=iFHnxm5i}1N2w0L`mqpw?nqT#ge_4dI(xm( zea?6?-ih*W4xe8kZtO*$vPA@739C(w*wj63{$~Iw7bbl(L-CL;Gg_V-+b8{}B-^T- zl}cAPTKUmCL{AF~LtVxkw0B3i|E?d9z+36z1w9@MBSZb&SK_7Q>shmaF?2$_6==Q` z3hAU;6pEwOpM%H3!`wg_2ii;+8QNG5@K`~~-0bXOTT`e=d&bth4YsC2GMNtS`7N(- z72)9M%L_AMd1;)=R4l>}6oE&BvjOroxBaz~)zub$a4}Ddnn*D}9JN~aA4EYY21h2W z`HQ)kWN(u9_aL*<^-DDc58(dN&6>v!X6rFM;l^iGI&hp&W{TngD4AAN=B9SMXr=L51{MD?t)3_jD51 zzi(i5$Bu`FP0!q8tch9@`rG3skiKgxw4!_R)LVqv&XFhJPr;S)%@LP)Js$6U z0~7(lnik=*6}i(~e~lYZXiVE}ytv=$vi)2F$5z<`Q4}RwfhdlD*feM0ue-}}>Wn<8 zuX1mAm#!;VJRY#uKqF#E0SIHRjE0^53U0qI>vfT^apktKs(#f_Tvqrz%>Ia-nZ6$5 zNoz!)E_#ne3&@&H`#TO!wK^702bo7F+8JJu8j``V}G8VI@-oLg8nNa!& zk{{VE=vn* z815WE_O=9|o^@F59k1FRB4+Wmo z*g?+=_&4kp2YHReY>seoM&A2oOMjiW+||e*tRlwBeN&+VEjK@+u80gtt(o*s?Oncy z-Ly%S3l9Rf5gxdW);qW53&wF9F}IlJHu}J1x6$aVjefQ^);cMv_QDzSWM_6gEH1j1 zn|f~JC@by$S8P;tbS$D&^y9Yr2BcKyrsIq!PuWtBy`utJIh7n18#hv*z?xD?komLZ zg;r+=`6!aZt#ZtqQB*UaW@BsZp=R-jvgX|P_6hE1HZFo4&&4Qa`gt;-y|@`Q_|#OG z#B(wGEJnV6Kt8YRY<(a?lE&PK1~vc=ob9ZQ7@M9m_QJ(GFC>rz#kFqoQmbvJ7VXsAp9F+x^H2+Zkz= zi~X~+o}(4V)I%f^h-K#8@B^)+jJPB#o27S70c?(uAwi6FYJ?JDoGn=5Qz9@F+|9FU zboM_LLrnd6=EJp{=LyV!Kt?v8u{c9HA5wwHZi1IfKNhA%48Q9OQ?~D!o+#$;VI#3%nMvGO5bMU54xhZ&}XMN`xpw)72`&=md zB)IVDlL9jgkKh3{skon&$>RG3c41O0AlP+1eeT3%AlSV=UUjxHO|UDPv|!hSV-S?E zSs8*MsZT|Wr$yk>U-eNQw?%xa+BbE{#EG;&)sJHGTlQ{chFc^i{ljZ0;nUz1zgLpyO*V>n0uEd$uL7sb%nj6RWZaAVwqFwRX4D104?iLX~Vo0lTv0# zS3uu5LT$aQ2Dah1$|@!yY^7xUNw(RE)aoH&Yj|MhTr<7edc*Zy7adN1Q`PTxwjKkT zn}zx&;d*Y$6!^d<^4`JK?MQyeOZs7EjZG-(D)rLBY;)b`9R8w7c-#)SPkq}sG^pi& zZ!mFJtt~l{5c_s;z#D**sl&4hS74D5F<>N<9~IjlU4N!5$9!wHt=!51mQ}22o*O&s zO8>UJN@FOQE(jLw8qmCB8}ZA^w>^9chC7{d-DJIfW9W3iTyTKa=f!~|4V$h(`Pt8| z-!5qLd${XX0EV`}X@z3b!`trt2?Me5W7rd2oHC;C14j^Y$C{y|(jl<*%Xc@6vfE#K zT^OSgo^rKWd1DsbD|>_XWCVCHg?8DA9+SXv^VkX$?*yY5HCU+2MO1LYgj*B6B2o}L zC?RL>xCOHPaN(t(dTV-L5KYB!Xr#oaC%Yv!dWZv^(+%R-5{*EF*r+%}dGAM;Fd4(F zqY$9S=A)vA4Wx*?p_PY`bzBf7~L0$rXRO4RbYp!$LQCpr{6xU?O8}1tY z=!V|!GBA0wyP%>=z_{0b7@ABJ7t4PNNyIU2iN)&+=QD=FPhk>KhG5^`P!gA^feGm; zJ{a0ipcAkGQ~oxf5jofBjTrZ~X<*Pu&t9Tv?v`l-jU*sQZ?1MgK&IcM;JXo4%XwSA zK6OtX2_wmHreC}!ME&9){(~eiT-Uy#CIKh`5CBSRhLGYKY!sDnL6$&B+_?A6Q0IS7 zQJlbJH{{8|4VhnS1`f%_wh_-we9F4ODb#lQo+!sDh?E&iC1~Wa98)G}ji0|KF=0cM z6o!BR<(rK&N`X){B#xf~LGhhJDZ(?NL5Ych!ELQ`kK~(!a8Ryfq_gJ2!AQG3{Q1ac z*$l_g1yA)ciF0@OH4Lsz`vUlC#zBHhav5Pq6RijUHZ?y4#GK8BJ(T6AS15+aW#-=o0EwN!#y8U0USj`AHqC^g63 z-CwPuUun0U>O{XuK$Igxfzr-c(-_`ROw6Yml`l)xMi4O}2_;^eS_$>2GF<37O$Jlf zn#Q=ufO;!@(E;g&USye!)I~+JC=ivv>Ju}`%G&xOwNAv6t8rTKfgXwWohPg~r9fF< z1j)37da{hpQa7^EMXFA_xW?|qVhQRx-bU5Igqk`sIz787eM*YV1+V4g;Np|XV)H&K zt53orE7{f*6~QJ(D~Tw2Y}32qHdEKB5Y!i{b+sr+)P)vFT*#1)ims!mS*%{Rw;iRo zDW{&IRJ)KhwfQd2(PXtQjR1i~K%EDxyfH;FkV33d=?+th`kHt;B^}a;D)B6B5T4Z6 z?UCcCB+%EkchtpegV9})D!Z=8ShYfeKT%VIt<%{Z>XTbK60CjcH~n;)Hi?HiC7;T3 zMC;=0dmNRO;Db#3MfFR#a8Cz3%cd(*2R=@|abI|%Ovp~t{_fD8iQ~3(iBLaaN^;Ut zQd_$^iE+^4W-(8_ghrs{;F5HmBGlUU2pqMhzTJbA(BbZ=-bl~P438jJ-{;_1y8V|e zHS#MsT@2&*!_j$#YasZ4Bp`jYsQ1XcLu${<{93M%k;1@rtiw0tr|}#K9xiX^kubaZ zzs_=NI2w({Q8E6ygkG?sT^Dd$b2Tyemcy$lTe=&0`7fWpOY`->^|c7;c&Y805~UKX zNEt{x#$@v7p6NPGKq*%$N=ZZI`qg9OXXlc7ZMygywT;tM;5$x|a|w{>kWABFN$G9P zM?)RxI_+a?=!x)JcYB%=Y!fMy5}%iat65Lc9)70~;^ucyNV#;Z-~05AOB8b90f@)* z6|h#XQSanU&!HUDE|qAON?W%#$ZdHiYp|B_b*k-O<_9J4f@^T#OMbe`d*G1%GB$)n z{=a6~_J*HrMM3ND@|@_}3hvK+HJCSUiTmo~u=znv;pyU4YoIvZ|C~=>Ux(RCihVss z?)5|*(=2Bxy`|AVz7+b>Ni&FTO;gn+$?pwp} zs!tQ5idcCno33tA>JK$|9M}|O;s@PYacfkmI2GqiIp(qTW9vA2T@CI$?lXA7m({#i zn%H#w#W*N3PP?kPCpMzRGCpT!Orr*T5xKgXiet^`B1S7d=x*V;tcX2SEsiQpdM~zM zb%f_#21_Nj7Afm>ot~XReW|#Pj^k+@34c*oDvq^IqvCJx?G$-0>Z3ofzD-%qn#tpx z)KD~^5w%ODC>Dyo0jrJ^`SNsa^;mUAC9l<%5|p049t%~(#1`eUL>|A(@fUcMAl)+7 zQb>@NQ^`zzCF29uyJg;G{Jwao8zNIQ@5if>@H$qecW-S4pQ5un+?OQCVd^-wP=Us? z>MiAa&3mXA9gieSqt}thx+FYyNqjN7!^0|F*Tkm=bEUmCh1R@}A1mZ6g$wy$lwXQS z%!{CjC<;w^4b*i;ql7wBp%%r#4|TmhT@rNB|I+eAk_=A1oa6g|4i)r>Th-|F8Ib&de?3Vl zY(amZJ6(2^qST`BWDS2qujt~UK%7pc&FLtDd|BGoh&rfsZ@14wG+D*wqba>w{x2-D zyj@@kEn4q=B{xso)z)6d=h7*P77k18^C%U~FH&{wJ Date: Sat, 23 Nov 2024 21:39:25 +0100 Subject: [PATCH 067/107] upgraded some plugins and dependencies --- pom.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 52b653da..0203eaa3 100644 --- a/pom.xml +++ b/pom.xml @@ -65,13 +65,13 @@ org.junit.jupiter junit-jupiter - 5.7.1 + 5.11.3 test org.assertj assertj-core - 3.19.0 + 3.26.3 test @@ -81,7 +81,7 @@ org.apache.maven.plugins maven-release-plugin - 2.5.3 + 3.1.1 true false @@ -91,7 +91,7 @@ org.apache.felix maven-bundle-plugin - 3.3.0 + 3.5.1 bundle-manifest @@ -105,7 +105,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.11.1 ${javadoc.opts} none @@ -122,7 +122,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - 3.1.0 + 3.6.0 verify-style @@ -176,7 +176,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.0.0-M4 + 3.5.2 **/LR*.java @@ -199,7 +199,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.2.7 sign-artifacts From ace11bc484d2dd8051ca33840257db72712e9b90 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:00:14 +0100 Subject: [PATCH 068/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.13 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 6d0fbdd2..7e7d7c82 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index c18e88b0..27da211f 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 UTF-8 diff --git a/pom.xml b/pom.xml index 0203eaa3..799c35f3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.13 GitHub Issues From 0f7c32b98393f118546555c5bf633860de5ffd1f Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:00:16 +0100 Subject: [PATCH 069/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 7e7d7c82..8220036b 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 27da211f..802dd9a0 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 799c35f3..6d4ae5f8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.13 + HEAD GitHub Issues From dad43159a85a68656c73d2d2cc6c6c86b9bac4ab Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:08:00 +0100 Subject: [PATCH 070/107] [maven-release-plugin] rollback the release of java-diff-utils-parent-4.13 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 8220036b..6d0fbdd2 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.13-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 802dd9a0..c18e88b0 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.13-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 6d4ae5f8..0203eaa3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.13-SNAPSHOT java-diff-utils-parent pom From 52e61d653fca450ca0df5e51d32e8486fdd531f5 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:10:34 +0100 Subject: [PATCH 071/107] upgraded some plugins and dependencies --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0203eaa3..09ec71e2 100644 --- a/pom.xml +++ b/pom.xml @@ -81,11 +81,17 @@ org.apache.maven.plugins maven-release-plugin - 3.1.1 + + 3.0.0-M7 true false forked-path + sign-release-artifacts From 23c80bc30dcfd8b1c152359725970ff1bab4b83d Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:13:47 +0100 Subject: [PATCH 072/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.13 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 6d0fbdd2..7e7d7c82 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index c18e88b0..27da211f 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 UTF-8 diff --git a/pom.xml b/pom.xml index 09ec71e2..408ad040 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.13-SNAPSHOT + 4.13 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.13 GitHub Issues From 94aafa7e68fe11917b06fd09db9d08107b3fb640 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:13:49 +0100 Subject: [PATCH 073/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 7e7d7c82..8220036b 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 27da211f..802dd9a0 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 408ad040..a15e39a9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.13 + 4.14-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.13 + HEAD GitHub Issues From 509d5071fd4b90299127e3188b7b06c58fc44452 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:36:20 +0100 Subject: [PATCH 074/107] added missing sources plugin --- pom.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pom.xml b/pom.xml index a15e39a9..c0552ad1 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,20 @@ + + org.apache.maven.plugins + maven-source-plugin + 4.0.0-beta-1 + + + attach-sources + verify + + jar-no-fork + + + + org.apache.maven.plugins maven-javadoc-plugin From 1fdcf13803ed38a96da4dfbeb48c1288ee187d77 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:38:43 +0100 Subject: [PATCH 075/107] added missing sources plugin --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c0552ad1..2ff6c49e 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ org.apache.maven.plugins maven-source-plugin - 4.0.0-beta-1 + 3.3.1 attach-sources From 1560c2203470382e6b1fb84f3454484042fa0dd9 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:40:23 +0100 Subject: [PATCH 076/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.14 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 8220036b..48e8059a 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 802dd9a0..dbac789c 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 UTF-8 diff --git a/pom.xml b/pom.xml index 2ff6c49e..9cbad909 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.14 GitHub Issues From b7c1aaeb2ecb013ba2e09c7b7d1c9fece2170655 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:40:25 +0100 Subject: [PATCH 077/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 48e8059a..057a9939 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index dbac789c..15c8bbd3 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 9cbad909..20833896 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.14 + HEAD GitHub Issues From 5245ee8b778a039a5b51c1ed67f2ce91c5c70ad5 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:43:15 +0100 Subject: [PATCH 078/107] [maven-release-plugin] rollback the release of java-diff-utils-parent-4.14 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 057a9939..8220036b 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.14-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 15c8bbd3..802dd9a0 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.14-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index 20833896..2ff6c49e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.14-SNAPSHOT java-diff-utils-parent pom From 0307c80edb51fb79ec2c3c72a2aed8357d2eb9be Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:49:57 +0100 Subject: [PATCH 079/107] downgraded plugins again, certificate problems for java 8 --- pom.xml | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 2ff6c49e..9cfd03d2 100644 --- a/pom.xml +++ b/pom.xml @@ -81,12 +81,7 @@ org.apache.maven.plugins maven-release-plugin - - 3.0.0-M7 + 2.5.3 true false @@ -108,24 +103,10 @@ - - org.apache.maven.plugins - maven-source-plugin - 3.3.1 - - - attach-sources - verify - - jar-no-fork - - - - org.apache.maven.plugins maven-javadoc-plugin - 3.11.1 + 3.1.1 ${javadoc.opts} none @@ -219,7 +200,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + 1.6 sign-artifacts From 1d8148ac056ecaaf61495c636e2f35488446ad5a Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:51:38 +0100 Subject: [PATCH 080/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.14 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 8220036b..48e8059a 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 802dd9a0..dbac789c 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 UTF-8 diff --git a/pom.xml b/pom.xml index 9cfd03d2..b0b79a87 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.14-SNAPSHOT + 4.14 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.14 GitHub Issues From af9d84cd71bb39aaec1c9db72e79857737a4b620 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:51:42 +0100 Subject: [PATCH 081/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 48e8059a..057a9939 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index dbac789c..15c8bbd3 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index b0b79a87..6945139c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.14 + 4.15-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.14 + HEAD GitHub Issues From 71981d8faaff266654964ce3334e7faf64d2b9f2 Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:58:04 +0100 Subject: [PATCH 082/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.15 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 057a9939..daac9fe1 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.15 java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 15c8bbd3..13ecfd39 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.15 UTF-8 diff --git a/pom.xml b/pom.xml index 6945139c..f5d42c12 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.15-SNAPSHOT + 4.15 java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.15 GitHub Issues From 4b3367674af140eedf5be9dd95308fc51c499bef Mon Sep 17 00:00:00 2001 From: Tobias Warneke Date: Sat, 23 Nov 2024 22:58:05 +0100 Subject: [PATCH 083/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index daac9fe1..9e3f2b96 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15 + 4.16-SNAPSHOT java-diff-utils-jgit java-diff-utils-jgit diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 13ecfd39..95560b01 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -7,7 +7,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.15 + 4.16-SNAPSHOT UTF-8 diff --git a/pom.xml b/pom.xml index f5d42c12..31300b5b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.15 + 4.16-SNAPSHOT java-diff-utils-parent pom @@ -29,7 +29,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git https://github.com/java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.15 + HEAD GitHub Issues From 0f8365b35d30faa300b442cd90f57f95a60eaf45 Mon Sep 17 00:00:00 2001 From: Tobias Date: Sat, 23 Nov 2024 23:09:14 +0100 Subject: [PATCH 084/107] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70dfa521..06c39d76 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Just add the code below to your maven dependencies: io.github.java-diff-utils java-diff-utils - 4.12 + 4.15 ``` From 527e83cee1babf73ed0e31358088794be05cd381 Mon Sep 17 00:00:00 2001 From: tw Date: Wed, 5 Feb 2025 21:25:54 +0100 Subject: [PATCH 085/107] fixes #197 - corrected revised filename output --- .../main/java/com/github/difflib/UnifiedDiffUtils.java | 2 +- .../com/github/difflib/GenerateUnifiedDiffTest.java | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 727008db..a6f83b64 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -352,7 +352,7 @@ public static List generateOriginalAndDiff(List original, List generateOriginalAndDiff(List original, List revised, String originalFileName, String revisedFileName) { String originalFileNameTemp = originalFileName; - String revisedFileNameTemp = originalFileName; + String revisedFileNameTemp = revisedFileName; if (originalFileNameTemp == null) { originalFileNameTemp = "original"; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java index e13d41aa..b8667818 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java @@ -51,8 +51,14 @@ public void testGenerateUnifiedWithOneDelta() throws IOException { @Test public void testGenerateUnifiedDiffWithoutAnyDeltas() { List test = Arrays.asList("abc"); - Patch patch = DiffUtils.diff(test, test); - UnifiedDiffUtils.generateUnifiedDiff("abc", "abc", test, patch, 0); + List testRevised = Arrays.asList("abc2"); + Patch patch = DiffUtils.diff(test, testRevised); + String unifiedDiffTxt = String.join("\n", UnifiedDiffUtils.generateUnifiedDiff("abc1", "abc2", test, patch, 0)); + System.out.println(unifiedDiffTxt); + + assertThat(unifiedDiffTxt) + .as("original filename should be abc1").contains("--- abc1") + .as("revised filename should be abc2").contains("+++ abc2"); } @Test From b5b7dacc6e0720bbfc634b5d15e283be13e570f9 Mon Sep 17 00:00:00 2001 From: epictecch <57089715+epictecch@users.noreply.github.com> Date: Wed, 5 Feb 2025 21:48:53 +0100 Subject: [PATCH 086/107] added support for merging inline deltas split by whitespace or a minor equality (#168) (#191) Co-authored-by: Tobias --- .../github/difflib/text/DiffRowGenerator.java | 31 +++++++- .../text/deltamerge/DeltaMergeUtils.java | 79 +++++++++++++++++++ .../text/deltamerge/InlineDeltaMergeInfo.java | 51 ++++++++++++ .../difflib/text/DiffRowGeneratorTest.java | 46 +++++++++++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java create mode 100644 java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 3711bfb6..e540004c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -24,6 +24,8 @@ import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; import com.github.difflib.text.DiffRow.Tag; +import com.github.difflib.text.deltamerge.DeltaMergeUtils; +import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo; import java.util.*; import java.util.function.BiFunction; import java.util.function.BiPredicate; @@ -75,6 +77,14 @@ public final class DiffRowGenerator { public static final Function> SPLITTER_BY_WORD = line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN); public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + public static final Function>> DEFAULT_INLINE_DELTA_MERGER = InlineDeltaMergeInfo::getDeltas; + + /** + * Merge diffs which are separated by equalities consisting of whitespace only. + */ + public static final Function>> WHITESPACE_EQUALITIES_MERGER = deltaMergeInfo -> DeltaMergeUtils + .mergeInlineDeltas(deltaMergeInfo, (equalities -> equalities.stream().allMatch(String::isBlank))); + public static Builder create() { return new Builder(); } @@ -170,6 +180,7 @@ static void wrapInTag(List sequence, int startPosition, private final boolean reportLinesUnchanged; private final Function lineNormalizer; private final Function processDiffs; + private final Function>> inlineDeltaMerger; private final boolean showInlineDiffs; private final boolean replaceOriginalLinefeedInChangesWithSpaces; @@ -194,11 +205,13 @@ private DiffRowGenerator(Builder builder) { reportLinesUnchanged = builder.reportLinesUnchanged; lineNormalizer = builder.lineNormalizer; processDiffs = builder.processDiffs; + inlineDeltaMerger = builder.inlineDeltaMerger; replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; Objects.requireNonNull(inlineDiffSplitter); Objects.requireNonNull(lineNormalizer); + Objects.requireNonNull(inlineDeltaMerger); } /** @@ -370,7 +383,10 @@ private List generateInlineDiffs(AbstractDelta delta) { origList = inlineDiffSplitter.apply(joinedOrig); revList = inlineDiffSplitter.apply(joinedRev); - List> inlineDeltas = DiffUtils.diff(origList, revList, equalizer).getDeltas(); + List> originalInlineDeltas = DiffUtils.diff(origList, revList, equalizer) + .getDeltas(); + List> inlineDeltas = inlineDeltaMerger + .apply(new InlineDeltaMergeInfo(originalInlineDeltas, origList, revList)); Collections.reverse(inlineDeltas); for (AbstractDelta inlineDelta : inlineDeltas) { @@ -465,6 +481,7 @@ public static class Builder { private Function processDiffs = null; private BiPredicate equalizer = null; private boolean replaceOriginalLinefeedInChangesWithSpaces = false; + private Function>> inlineDeltaMerger = DEFAULT_INLINE_DELTA_MERGER; private Builder() { } @@ -673,5 +690,17 @@ public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) { this.replaceOriginalLinefeedInChangesWithSpaces = replace; return this; } + + /** + * Provide an inline delta merger for use case specific delta optimizations. + * + * @param inlineDeltaMerger + * @return + */ + public Builder inlineDeltaMerger( + Function>> inlineDeltaMerger) { + this.inlineDeltaMerger = inlineDeltaMerger; + return this; + } } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java new file mode 100644 index 00000000..0d9a0a16 --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java @@ -0,0 +1,79 @@ +/* + * Copyright 2009-2024 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.text.deltamerge; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.patch.ChangeDelta; +import com.github.difflib.patch.Chunk; + +/** + * Provides utility features for merge inline deltas + * + * @author Christian Meier + */ +final public class DeltaMergeUtils { + + public static List> mergeInlineDeltas(InlineDeltaMergeInfo deltaMergeInfo, + Predicate> replaceEquality) { + final List> originalDeltas = deltaMergeInfo.getDeltas(); + if (originalDeltas.size() < 2) { + return originalDeltas; + } + + final List> newDeltas = new ArrayList<>(); + newDeltas.add(originalDeltas.get(0)); + for (int i = 1; i < originalDeltas.size(); i++) { + final AbstractDelta previousDelta = newDeltas.getLast(); + final AbstractDelta currentDelta = originalDeltas.get(i); + + final List equalities = deltaMergeInfo.getOrigList().subList( + previousDelta.getSource().getPosition() + previousDelta.getSource().size(), + currentDelta.getSource().getPosition()); + + if (replaceEquality.test(equalities)) { + // Merge the previous delta, the equality and the current delta into one + // ChangeDelta and replace the previous delta by this new ChangeDelta. + final List allSourceLines = new ArrayList<>(); + allSourceLines.addAll(previousDelta.getSource().getLines()); + allSourceLines.addAll(equalities); + allSourceLines.addAll(currentDelta.getSource().getLines()); + + final List allTargetLines = new ArrayList<>(); + allTargetLines.addAll(previousDelta.getTarget().getLines()); + allTargetLines.addAll(equalities); + allTargetLines.addAll(currentDelta.getTarget().getLines()); + + final ChangeDelta replacement = new ChangeDelta<>( + new Chunk<>(previousDelta.getSource().getPosition(), allSourceLines), + new Chunk<>(previousDelta.getTarget().getPosition(), allTargetLines)); + + newDeltas.removeLast(); + newDeltas.add(replacement); + } else { + newDeltas.add(currentDelta); + } + } + + return newDeltas; + } + + private DeltaMergeUtils() { + } +} diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java new file mode 100644 index 00000000..cc6b399a --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java @@ -0,0 +1,51 @@ +/* + * Copyright 2009-2024 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.text.deltamerge; + +import java.util.List; + +import com.github.difflib.patch.AbstractDelta; + +/** + * Holds the information required to merge deltas originating from an inline + * diff + * + * @author Christian Meier + */ +public final class InlineDeltaMergeInfo { + + private final List> deltas; + private final List origList; + private final List revList; + + public InlineDeltaMergeInfo(List> deltas, List origList, List revList) { + this.deltas = deltas; + this.origList = origList; + this.revList = revList; + } + + public List> getDeltas() { + return deltas; + } + + public List getOrigList() { + return origList; + } + + public List getRevList() { + return revList; + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index f881c474..76c03044 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -12,6 +12,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; import java.util.regex.Pattern; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -20,6 +21,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.text.deltamerge.DeltaMergeUtils; +import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo; + public class DiffRowGeneratorTest { @Test @@ -791,6 +796,47 @@ public void testIssue129SkipWhitespaceChanges() throws IOException { .forEach(System.out::println); } + @Test + public void testGeneratorWithWhitespaceDeltaMerge() { + final DiffRowGenerator generator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true) + .inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**") // + .lineNormalizer(StringUtils::htmlEntites) // do not replace tabs + .inlineDeltaMerger(DiffRowGenerator.WHITESPACE_EQUALITIES_MERGER).build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult(generator, " x whitespace before diff", " y whitespace before diff", + " ~x~**y** whitespace before diff"); + assertInlineDiffResult(generator, "Whitespace after diff x ", "Whitespace after diff y ", + "Whitespace after diff ~x~**y** "); + assertInlineDiffResult(generator, "Diff x x between", "Diff y y between", "Diff ~x x~**y y** between"); + assertInlineDiffResult(generator, "Hello \t world", "Hi \t universe", "~Hello \t world~**Hi \t universe**"); + assertInlineDiffResult(generator, "The quick brown fox jumps over the lazy dog", "A lazy dog jumps over a fox", + "~The quick brown fox ~**A lazy dog **jumps over ~the lazy dog~**a fox**"); + } + + @Test + public void testGeneratorWithMergingDeltasForShortEqualities() { + final Function>> shortEqualitiesMerger = deltaMergeInfo -> DeltaMergeUtils + .mergeInlineDeltas(deltaMergeInfo, + (equalities -> equalities.stream().mapToInt(String::length).sum() < 6)); + + final DiffRowGenerator generator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true) + .inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**").inlineDeltaMerger(shortEqualitiesMerger) + .build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult(generator, "aaa bbb ccc", "xxx bbb zzz", "~aaa bbb ccc~**xxx bbb zzz**"); + assertInlineDiffResult(generator, "aaa bbbb ccc", "xxx bbbb zzz", "~aaa~**xxx** bbbb ~ccc~**zzz**"); + } + + private void assertInlineDiffResult(DiffRowGenerator generator, String original, String revised, String expected) { + final List rows = generator.generateDiffRows(Arrays.asList(original), Arrays.asList(revised)); + print(rows); + + assertEquals(1, rows.size()); + assertEquals(expected, rows.get(0).getOldLine().toString()); + } + @Test public void testIssue188HangOnExamples() throws IOException, URISyntaxException { try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), null);) { From 803fc4a18838a7d02a26ff916688fb0aa85391ad Mon Sep 17 00:00:00 2001 From: tw Date: Wed, 5 Feb 2025 22:04:26 +0100 Subject: [PATCH 087/107] fixes #191 - downgraded patch to Java 8 compliance --- .../main/java/com/github/difflib/text/DiffRowGenerator.java | 2 +- .../com/github/difflib/text/deltamerge/DeltaMergeUtils.java | 4 ++-- .../java/com/github/difflib/text/DiffRowGeneratorTest.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index e540004c..9ec50a84 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -83,7 +83,7 @@ public final class DiffRowGenerator { * Merge diffs which are separated by equalities consisting of whitespace only. */ public static final Function>> WHITESPACE_EQUALITIES_MERGER = deltaMergeInfo -> DeltaMergeUtils - .mergeInlineDeltas(deltaMergeInfo, (equalities -> equalities.stream().allMatch(String::isBlank))); + .mergeInlineDeltas(deltaMergeInfo, equalities -> equalities.stream().allMatch(s -> s==null || s.replaceAll("\\s+", "").equals(""))); public static Builder create() { return new Builder(); diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java index 0d9a0a16..b2580957 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java @@ -40,7 +40,7 @@ public static List> mergeInlineDeltas(InlineDeltaMergeInfo final List> newDeltas = new ArrayList<>(); newDeltas.add(originalDeltas.get(0)); for (int i = 1; i < originalDeltas.size(); i++) { - final AbstractDelta previousDelta = newDeltas.getLast(); + final AbstractDelta previousDelta = newDeltas.get(newDeltas.size()-1); final AbstractDelta currentDelta = originalDeltas.get(i); final List equalities = deltaMergeInfo.getOrigList().subList( @@ -64,7 +64,7 @@ public static List> mergeInlineDeltas(InlineDeltaMergeInfo new Chunk<>(previousDelta.getSource().getPosition(), allSourceLines), new Chunk<>(previousDelta.getTarget().getPosition(), allTargetLines)); - newDeltas.removeLast(); + newDeltas.remove(newDeltas.size()-1); newDeltas.add(replacement); } else { newDeltas.add(currentDelta); diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 76c03044..7725bb0b 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -818,7 +818,7 @@ public void testGeneratorWithWhitespaceDeltaMerge() { public void testGeneratorWithMergingDeltasForShortEqualities() { final Function>> shortEqualitiesMerger = deltaMergeInfo -> DeltaMergeUtils .mergeInlineDeltas(deltaMergeInfo, - (equalities -> equalities.stream().mapToInt(String::length).sum() < 6)); + equalities -> equalities.stream().mapToInt(String::length).sum() < 6); final DiffRowGenerator generator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true) .inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**").inlineDeltaMerger(shortEqualitiesMerger) From 110a0da37bf02aced82dab720ea57982dd42c458 Mon Sep 17 00:00:00 2001 From: tw Date: Wed, 5 Feb 2025 22:44:27 +0100 Subject: [PATCH 088/107] fixes #191 - downgraded patch to Java 8 compliance --- .../test/java/com/github/difflib/text/DiffRowGeneratorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 7725bb0b..beccad8c 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -839,7 +839,7 @@ private void assertInlineDiffResult(DiffRowGenerator generator, String original, @Test public void testIssue188HangOnExamples() throws IOException, URISyntaxException { - try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), null);) { + try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), (ClassLoader) null);) { List original = Files.readAllLines(zipFs.getPath("old.html")); List revised = Files.readAllLines(zipFs.getPath("new.html")); From 8397e20ddcdedc40be2de7f2f7885490f59dc5a4 Mon Sep 17 00:00:00 2001 From: rushii <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:23:11 -0800 Subject: [PATCH 089/107] fix: parsing unified diff chunk headers (#198) The regex matching chunk headers does not account for diffs generated with --show-function-line --- .../src/main/java/com/github/difflib/UnifiedDiffUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index a6f83b64..94786b6c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -36,7 +36,7 @@ public final class UnifiedDiffUtils { private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern - .compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@$"); + .compile("^@@\\s+-(\\d+)(?:,(\\d+))?\\s+\\+(\\d+)(?:,(\\d+))?\\s+@@.*$"); private static final String NULL_FILE_INDICATOR = "/dev/null"; /** From 01a7fb16cfc33abc3e6ac4adcc2db79438f56fcf Mon Sep 17 00:00:00 2001 From: tw Date: Sun, 9 Feb 2025 20:53:14 +0100 Subject: [PATCH 090/107] updates test dependencies --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 31300b5b..aa1deae3 100644 --- a/pom.xml +++ b/pom.xml @@ -65,13 +65,13 @@ org.junit.jupiter junit-jupiter - 5.11.3 + 5.11.4 test org.assertj assertj-core - 3.26.3 + 3.27.3 test From c1aa689dce9fa2703292f7282e919bc0b1a9fa2d Mon Sep 17 00:00:00 2001 From: Davide Santonocito Date: Wed, 5 Mar 2025 15:43:04 +0100 Subject: [PATCH 091/107] Fix a typo in DiffUtils.java javadocs --- .../java/com/github/difflib/DiffUtils.java | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index 8917772b..e0ac521f 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -42,7 +42,7 @@ public final class DiffUtils { /** * Sets the default diff algorithm factory to be used by all diff routines. * - * @param factory a {@link DiffAlgorithmFactory} represnting the new default diff algorithm factory. + * @param factory a {@link DiffAlgorithmFactory} representing the new default diff algorithm factory. */ public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { DEFAULT_DIFF = factory; @@ -52,9 +52,9 @@ public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) * Computes the difference between two sequences of elements using the default diff algorithm. * * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { @@ -65,8 +65,8 @@ public static Patch diff(List original, List revised, DiffAlgorithm * Computes the difference between two sequences of elements using the default diff algorithm. * * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised) { @@ -77,9 +77,9 @@ public static Patch diff(List original, List revised) { * Computes the difference between two sequences of elements using the default diff algorithm. * * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. - * @param includeEqualParts a {@link boolean} represnting whether to include equal parts in the resulting patch. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, boolean includeEqualParts) { @@ -89,9 +89,9 @@ public static Patch diff(List original, List revised, boolean inclu /** * Computes the difference between two strings using the default diff algorithm. * - * @param sourceText a {@link String} represnting the original string. Must not be {@code null}. - * @param targetText a {@link String} represnting the revised string. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. + * @param sourceText a {@link String} representing the original string. Must not be {@code null}. + * @param targetText a {@link String} representing the revised string. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. * @return The patch describing the difference between the original and revised strings. Never {@code null}. */ public static Patch diff(String sourceText, String targetText, @@ -105,9 +105,9 @@ public static Patch diff(String sourceText, String targetText, * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param source a {@link List} represnting the original text. Must not be {@code null}. - * @param target a {@link List} represnting the revised text. Must not be {@code null}. - * @param equalizer a {@link BiPredicate} represnting the equalizer object to replace the default compare + * @param source a {@link List} representing the original text. Must not be {@code null}. + * @param target a {@link List} representing the revised text. Must not be {@code null}. + * @param equalizer a {@link BiPredicate} representing the equalizer object to replace the default compare * algorithm (Object.equals). If {@code null} the default equalizer of the * default algorithm is used. * @return The patch describing the difference between the original and @@ -131,10 +131,10 @@ public static Patch diff(List original, List revised, * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param original a {@link List} represnting the original text. Must not be {@code null}. - * @param revised a {@link List} represnting the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the diff algorithm listener. + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the diff algorithm listener. * @param includeEqualParts Include equal data parts into the patch. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. @@ -154,9 +154,9 @@ public static Patch diff(List original, List revised, * Computes the difference between the original and revised list of elements * with default diff algorithm * - * @param original a {@link List} represnting the original text. Must not be {@code null}. - * @param revised a {@link List} represnting the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ @@ -169,8 +169,8 @@ public static Patch diff(List original, List revised, DiffAlgorithm * "trick" to make out of texts lists of characters, like DiffRowGenerator * does and merges those changes at the end together again. * - * @param original a {@link String} represnting the original text. Must not be {@code null}. - * @param revised a {@link String} represnting the revised text. Must not be {@code null}. + * @param original a {@link String} representing the original text. Must not be {@code null}. + * @param revised a {@link String} representing the revised text. Must not be {@code null}. * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ @@ -194,8 +194,8 @@ public static Patch diffInline(String original, String revised) { /** * Applies the given patch to the original list and returns the revised list. * - * @param original a {@link List} represnting the original list. - * @param patch a {@link List} represnting the patch to apply. + * @param original a {@link List} representing the original list. + * @param patch a {@link List} representing the patch to apply. * @return the revised list. * @throws PatchFailedException if the patch cannot be applied. */ @@ -207,8 +207,8 @@ public static List patch(List original, Patch patch) /** * Applies the given patch to the revised list and returns the original list. * - * @param revised a {@link List} represnting the revised list. - * @param patch a {@link Patch} represnting the patch to apply. + * @param revised a {@link List} representing the revised list. + * @param patch a {@link Patch} representing the patch to apply. * @return the original list. * @throws PatchFailedException if the patch cannot be applied. */ From d15ecbb5683a29d94910fb3d2d08148398809346 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Tue, 25 Mar 2025 23:16:19 +0700 Subject: [PATCH 092/107] Fix typo javadoc --- .../java/com/github/difflib/algorithm/jgit/HistogramDiff.java | 2 +- .../com/github/difflib/algorithm/DiffAlgorithmListener.java | 2 +- .../src/main/java/com/github/difflib/patch/Chunk.java | 2 +- .../src/main/java/com/github/difflib/patch/Patch.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index e3f6cd5b..6d15f609 100644 --- a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -28,7 +28,7 @@ import org.eclipse.jgit.diff.SequenceComparator; /** - * HistorgramDiff using JGit - Library. This one is much more performant than the orginal Myers + * HistorgramDiff using JGit - Library. This one is much more performant than the original Myers * implementation. * * @author toben diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java index 37d51813..a141d7be 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java @@ -24,7 +24,7 @@ public interface DiffAlgorithmListener { /** * This is a step within the diff algorithm. Due to different implementations the value - * is not strict incrementing to the max and is not garantee to reach the max. It could + * is not strict incrementing to the max and is not guarantee to reach the max. It could * stop before. * @param value * @param max diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java index 7e55ac0d..50054074 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java @@ -26,7 +26,7 @@ * *

* Text is represented as Object[] because the diff engine is - * capable of handling more than plain ascci. In fact, arrays or lists of any + * capable of handling more than plain ascii. In fact, arrays or lists of any * type that implements {@link java.lang.Object#hashCode hashCode()} and * {@link java.lang.Object#equals equals()} correctly can be subject to * differencing using this library. diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java index aaff7d94..305f2de7 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -224,7 +224,7 @@ private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext ctx, Abstra private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; /** - * Alter normal conflict output behaviour to e.g. inclide some conflict + * Alter normal conflict output behaviour to e.g. include some conflict * statements in the result, like git does it. */ public Patch withConflictOutput(ConflictOutput conflictOutput) { From 8e8ba4793965b02dc6d1196045edbf2634c7d4a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 18:52:05 +0000 Subject: [PATCH 093/107] Bump org.eclipse.jgit:org.eclipse.jgit in /java-diff-utils-jgit Bumps [org.eclipse.jgit:org.eclipse.jgit](https://github.com/eclipse-jgit/jgit) from 5.13.3.202401111512-r to 7.2.1.202505142326-r. - [Commits](https://github.com/eclipse-jgit/jgit/compare/v5.13.3.202401111512-r...v7.2.1.202505142326-r) --- updated-dependencies: - dependency-name: org.eclipse.jgit:org.eclipse.jgit dependency-version: 7.2.1.202505142326-r dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- java-diff-utils-jgit/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 9e3f2b96..93e12d59 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -20,7 +20,7 @@ org.eclipse.jgit org.eclipse.jgit - 5.13.3.202401111512-r + 7.2.1.202505142326-r com.googlecode.javaewah From 755024eceec4246e3453dea5e157b3b04ad69cf3 Mon Sep 17 00:00:00 2001 From: tw Date: Sat, 24 May 2025 23:11:54 +0200 Subject: [PATCH 094/107] downgrade jgit --- java-diff-utils-jgit/pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 93e12d59..5b01d2c5 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -18,9 +18,10 @@ test + org.eclipse.jgit org.eclipse.jgit - 7.2.1.202505142326-r + 5.13.3.202401111512-r com.googlecode.javaewah From de0e157a8ff43a92a8940d7c9455357a4663ec68 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 7 Jul 2025 21:58:42 +0200 Subject: [PATCH 095/107] fixes #207 --- KEYS | 46 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++++ 2 files changed, 50 insertions(+) create mode 100644 KEYS diff --git a/KEYS b/KEYS new file mode 100644 index 00000000..b301c122 --- /dev/null +++ b/KEYS @@ -0,0 +1,46 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: Benutzer-ID: Tobias Warneke +Comment: Fingerabdruck: D477D51812E692011DB11E66A6EA2E2BF22E0543 + + +mQGNBFJQhigBDADpuhND/VUQwJT0nnJxfjAIur59hyaZZ3Ph/KIgmCneyq7lzYO6 +xa1ucH8mqNBVNLLBhs4CjihBddU/ZKTX3WnZyhQKQMZr3Tg+TCNFmAR4/hnZ3NjZ +N5N5gUj/dqVI2rIvypIuxUApl88BYMsxYpn2+8FKeMd8oBJLqFRJ3WNjB4Op2tRO +XRWoxs1ypubS/IV1zkphHHpi6VSABlTyTWu4kXEj/1/GpsdtHRa9kvdWw7yKQbnM +XuwOxtzZFJcyu0P2jYVfHHvxcjxuklc9edmCGdNxgKIoo0LXZOeFIi6OWtwzD0pn +O6ovJ+PL9QscMdnQlPwsiCwjNUNue20GBv3aUIYc+Z8Gq0SqSan5V0IiKRHMJkzd +FAhnpkSFBvHhPJn07BCcb1kctqL+xnLxIdi7arq3WNA/6bJjsojc/x3FdIvORIeP +sqejhtL8mCBvbMAMHSBrFxclMp+HSz2ouHEEPIQam0KeN8t1yEqIy3/aYKMzHj9c +C3s8XOaBCbJbKpMAEQEAAbQ9VG9iaWFzIFdhcm5la2UgKGZvciBkZXZlbG9wbWVu +dCBwdXJwb3NlcykgPHQud2FybmVrZUBnbXgubmV0PokBvwQTAQIAKQUCUlCGKAIb +DwUJCWav+AcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEKbqLivyLgVD/8IL ++gPYvGZcd52WqUqXlZQQQWheMNGlxKd85MV0Kf1TlukHEzaC2XE0Hjz3RNWBroL2 +QZ+CR/17XX9+10VCAdDYtArClFo85B1+TwST+g42Y7rHqghhetW9jnvGxbkCIUdS +093zg0kGgDJN4/Gy2slOsWjQpDzBfziuxTZcX/kJEJS6/H5eia/UAsR53wCN/12F +jzlRPAhZcUg0rAxNs0Kd6gF5TLfG6BgRhZkKSsiT+JS/ADanCoLDMd4NGiZ/U5PB +bdVnRT39x8XK3dGCRTmCgXnlg4imcoCNKTLKARJoCziQ+sy0AnqDLyUqwESnFB8l +YfpzlDoxmfdFVvZLkPG+WkJ6Ioiqcq1fUOdmsJme23gPCuu3hoV6Ysnv6Jpid1sy +6feYDEbo2qb4J8Q6Jwa5OYVLrJqS61g07EmvG3PvIQgi3rKvlPd9CTouFzOGNelW +W8TE+62Uixu1VlKxKUSUVXm65U5Vhx+OdAm25SPkm3A/qaGfQ2OP9iIk/Mba6Ckz +Q4kB1gQTAQgAQAIbDwcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAFiEE1HfVGBLm +kgEdsR5mpuouK/IuBUMFAmd12+0FCRrKLosACgkQpuouK/IuBUMN3Qv8CSdONLjX +DbS4sCR3U3T33m0Fl22DVRGP9+Oxfzu2qz6UNjm+rxOY2+gktDjeQZqQH3bMiJIV +9oVPE2/8QbhgZGJ6Yal5tKXTlTkjNxssITJH6SOLD0F0DRWWfqC8fm4Mnex2O9Uz +vML1YTZeHFoi4Qe3cIdR41x73ltmuRIpqpHO5upw5mXs8h6g/HpGACrQSv/MMthE +/ftVyzn/aAwB3ozR28D6nK6DlheffvjUGBfIYCbOn9Q5peCIuL9zUr3KDR332NWG +1EFGYXAfedtTQhab3VyF0XJy8U29JtpDNd1DeoKNYiQhmN4VDmAYzpgnbAw4OEuZ +UEtWO2QMXC9bGyy7mm8jGFk3ORmk1oINTPczCkz37SHrQ4IIawDZfrR8a+Q/VyYW +3sNXQ1y7PzLTrR2L+uOsO5/P4qFpViielJst7uiO+1I/eoJ6GONFurzEXXZB7Cu8 +dA67vgGwgVaMosnTOz1Pyc+mzzG5H3i8gjqQQl+JEZFG/+ng/fyvnLjZiQHWBBMB +CABAAhsPBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AWIQTUd9UYEuaSAR2xHmam +6i4r8i4FQwUCX58d7AUJERB2CAAKCRCm6i4r8i4FQ8COC/4i8YPxqtk/Z6nEHXSc +3F6GB0He9UZG8LMBbLD93nooHAY3VzOaUcg0/8SN+HhtFmAVcgshKpx/7ysBO8hK +wKArPN+W458O4VGm6pmKyuVqoSkOpQYrZHzHuTEBFoHYbeDh3LrHIlnC//l18U9A +n9pCN42RFKhcjhZYs17FkHcTQbH1rcXfZe+vzu+70ZyGyh+3FB+32OgW6lGD6QKK +lD7DfoP2BaYVe2OlhPdZ2ubQBw/qV7Or5KlzBWff4Nbv2nT9Y4uFGvo01CxEEi4z +mc0hsK3yGeP26V49kZy3PAJNjyy4gPhG7Xw06JTw0rO2hcgjtdGWgH2/l7PCS0qg +PPBMGDCbb5cz++8YWUHT0togJ2K49BqKpJlt4soqYFPWFMS0QeI90yV3YeWOL+tV +hLcJ6K/RhSg6eagAASa0AbuCGkgnHJW1ZlyFnnketvihTkCe2yGvBgxY1F7S2jGN +B2zkOzYjaWqSN5SlpSNBcSY8NLWGdyQoDEjY+I56J+IuWPE= +=H+XU +-----END PGP PUBLIC KEY BLOCK----- diff --git a/README.md b/README.md index 06c39d76..cbcba7af 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ Main reason to build this library was the lack of easy-to-use libraries with all **This is originally a fork of java-diff-utils from Google Code Archive.** +## GPG Signature Validation + +The gpg singing key in [KEYS] is used for this projects artifacts. + ## API Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) From c7f4dfb1464753c530868319819f196efa19fff5 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 7 Jul 2025 22:27:16 +0200 Subject: [PATCH 096/107] fixes #208 - reformatted source code --- java-diff-utils-jgit/pom.xml | 110 +- .../difflib/algorithm/jgit/HistogramDiff.java | 108 +- .../algorithm/jgit/HistogramDiffTest.java | 108 +- .../algorithm/jgit/LRHistogramDiffTest.java | 74 +- java-diff-utils/pom.xml | 124 +- .../java/com/github/difflib/DiffUtils.java | 377 ++-- .../com/github/difflib/UnifiedDiffUtils.java | 858 ++++---- .../com/github/difflib/algorithm/Change.java | 40 +- .../algorithm/DiffAlgorithmFactory.java | 8 +- .../difflib/algorithm/DiffAlgorithmI.java | 40 +- .../algorithm/DiffAlgorithmListener.java | 23 +- .../difflib/algorithm/myers/MyersDiff.java | 330 ++-- .../myers/MyersDiffWithLinearSpace.java | 419 ++-- .../difflib/algorithm/myers/PathNode.java | 156 +- .../github/difflib/patch/AbstractDelta.java | 169 +- .../com/github/difflib/patch/ChangeDelta.java | 114 +- .../java/com/github/difflib/patch/Chunk.java | 309 ++- .../github/difflib/patch/ConflictOutput.java | 5 +- .../com/github/difflib/patch/DeleteDelta.java | 70 +- .../com/github/difflib/patch/DeltaType.java | 46 +- .../github/difflib/patch/DiffException.java | 11 +- .../com/github/difflib/patch/EqualDelta.java | 58 +- .../com/github/difflib/patch/InsertDelta.java | 70 +- .../java/com/github/difflib/patch/Patch.java | 617 +++--- .../difflib/patch/PatchFailedException.java | 11 +- .../com/github/difflib/patch/VerifyChunk.java | 6 +- .../java/com/github/difflib/text/DiffRow.java | 159 +- .../github/difflib/text/DiffRowGenerator.java | 1344 ++++++------- .../com/github/difflib/text/StringUtils.java | 105 +- .../text/deltamerge/DeltaMergeUtils.java | 87 +- .../text/deltamerge/InlineDeltaMergeInfo.java | 37 +- .../difflib/unifieddiff/UnifiedDiff.java | 82 +- .../difflib/unifieddiff/UnifiedDiffFile.java | 372 ++-- .../UnifiedDiffParserException.java | 29 +- .../unifieddiff/UnifiedDiffReader.java | 885 +++++---- .../unifieddiff/UnifiedDiffWriter.java | 367 ++-- .../com/github/difflib/DiffUtilsTest.java | 437 +++-- .../difflib/GenerateUnifiedDiffTest.java | 421 ++-- .../com/github/difflib/TestConstants.java | 13 +- .../algorithm/myers/MyersDiffTest.java | 84 +- .../myers/MyersDiffWithLinearSpaceTest.java | 120 +- ...WithMyersDiffWithLinearSpacePatchTest.java | 743 +++---- .../github/difflib/examples/ApplyPatch.java | 24 +- .../difflib/examples/ComputeDifference.java | 22 +- .../difflib/examples/OriginalAndDiffTest.java | 89 +- .../com/github/difflib/patch/ChunkTest.java | 64 +- .../patch/PatchWithAllDiffAlgorithmsTest.java | 174 +- .../difflib/patch/PatchWithMyerDiffTest.java | 58 +- .../PatchWithMyerDiffWithLinearSpaceTest.java | 64 +- .../difflib/text/DiffRowGeneratorTest.java | 1725 +++++++++-------- .../github/difflib/text/StringUtilsTest.java | 63 +- .../unifieddiff/UnifiedDiffReaderTest.java | 777 ++++---- .../UnifiedDiffRoundTripNewLineTest.java | 39 +- .../unifieddiff/UnifiedDiffRoundTripTest.java | 292 +-- .../unifieddiff/UnifiedDiffWriterTest.java | 83 +- pom.xml | 500 ++--- 56 files changed, 6881 insertions(+), 6639 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 5b01d2c5..766719b6 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -1,58 +1,58 @@ - 4.0.0 - - io.github.java-diff-utils - java-diff-utils-parent - 4.16-SNAPSHOT - - java-diff-utils-jgit - java-diff-utils-jgit - jar - This is an extension of java-diff-utils using jgit to use its implementation of + 4.0.0 + + io.github.java-diff-utils + java-diff-utils-parent + 4.16-SNAPSHOT + + java-diff-utils-jgit + jar + java-diff-utils-jgit + This is an extension of java-diff-utils using jgit to use its implementation of some difference algorithms. - - - org.junit.jupiter - junit-jupiter - test - - - - org.eclipse.jgit - org.eclipse.jgit - 5.13.3.202401111512-r - - - com.googlecode.javaewah - JavaEWAH - - - commons-codec - commons-codec - - - commons-logging - commons-logging - - - org.apache.httpcomponents - httpclient - - - com.jcraft - jsch - - - org.slf4j - slf4j-api - - - - - ${project.groupId} - java-diff-utils - ${project.version} - - - \ No newline at end of file + + + org.junit.jupiter + junit-jupiter + test + + + + org.eclipse.jgit + org.eclipse.jgit + 5.13.3.202401111512-r + + + com.googlecode.javaewah + JavaEWAH + + + commons-codec + commons-codec + + + commons-logging + commons-logging + + + org.apache.httpcomponents + httpclient + + + com.jcraft + jsch + + + org.slf4j + slf4j-api + + + + + ${project.groupId} + java-diff-utils + ${project.version} + + + diff --git a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index 6d15f609..0476b269 100644 --- a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -35,71 +35,71 @@ */ public class HistogramDiff implements DiffAlgorithmI { - @Override - public List computeDiff(List source, List target, DiffAlgorithmListener progress) { - Objects.requireNonNull(source, "source list must not be null"); - Objects.requireNonNull(target, "target list must not be null"); - if (progress != null) { - progress.diffStart(); - } - EditList diffList = new EditList(); - diffList.addAll(new org.eclipse.jgit.diff.HistogramDiff().diff(new DataListComparator<>(progress), new DataList<>(source), new DataList<>(target))); - List patch = new ArrayList<>(); - for (Edit edit : diffList) { - DeltaType type = DeltaType.EQUAL; - switch (edit.getType()) { - case DELETE: - type = DeltaType.DELETE; - break; - case INSERT: - type = DeltaType.INSERT; - break; - case REPLACE: - type = DeltaType.CHANGE; - break; - } - patch.add(new Change(type, edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB())); - } - if (progress != null) { - progress.diffEnd(); - } - return patch; - } + @Override + public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + if (progress != null) { + progress.diffStart(); + } + EditList diffList = new EditList(); + diffList.addAll(new org.eclipse.jgit.diff.HistogramDiff() + .diff(new DataListComparator<>(progress), new DataList<>(source), new DataList<>(target))); + List patch = new ArrayList<>(); + for (Edit edit : diffList) { + DeltaType type = DeltaType.EQUAL; + switch (edit.getType()) { + case DELETE: + type = DeltaType.DELETE; + break; + case INSERT: + type = DeltaType.INSERT; + break; + case REPLACE: + type = DeltaType.CHANGE; + break; + } + patch.add(new Change(type, edit.getBeginA(), edit.getEndA(), edit.getBeginB(), edit.getEndB())); + } + if (progress != null) { + progress.diffEnd(); + } + return patch; + } } class DataListComparator extends SequenceComparator> { - private final DiffAlgorithmListener progress; + private final DiffAlgorithmListener progress; - public DataListComparator(DiffAlgorithmListener progress) { - this.progress = progress; - } + public DataListComparator(DiffAlgorithmListener progress) { + this.progress = progress; + } - @Override - public boolean equals(DataList original, int orgIdx, DataList revised, int revIdx) { - if (progress != null) { - progress.diffStep(orgIdx + revIdx, original.size() + revised.size()); - } - return original.data.get(orgIdx).equals(revised.data.get(revIdx)); - } - - @Override - public int hash(DataList s, int i) { - return s.data.get(i).hashCode(); - } + @Override + public boolean equals(DataList original, int orgIdx, DataList revised, int revIdx) { + if (progress != null) { + progress.diffStep(orgIdx + revIdx, original.size() + revised.size()); + } + return original.data.get(orgIdx).equals(revised.data.get(revIdx)); + } + @Override + public int hash(DataList s, int i) { + return s.data.get(i).hashCode(); + } } class DataList extends Sequence { - final List data; + final List data; - public DataList(List data) { - this.data = data; - } + public DataList(List data) { + this.data = data; + } - @Override - public int size() { - return data.size(); - } + @Override + public int size() { + return data.size(); + } } diff --git a/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java b/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java index ac85f985..5dc33f82 100644 --- a/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java +++ b/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java @@ -15,14 +15,15 @@ */ package com.github.difflib.algorithm.jgit; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; /** @@ -31,57 +32,64 @@ */ public class HistogramDiffTest { - public HistogramDiffTest() { - } + public HistogramDiffTest() {} + + /** + * Test of diff method, of class HistogramDiff. + */ + @Test + public void testDiff() throws PatchFailedException { + List orgList = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revList = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = + Patch.generate(orgList, revList, new HistogramDiff().computeDiff(orgList, revList, null)); + System.out.println(patch); + assertNotNull(patch); + assertEquals(3, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [DeleteDelta, position: 3, lines: [A, B]], [InsertDelta, position: 7, lines: [B, A, C]]]}", + patch.toString()); + + List patched = patch.applyTo(orgList); + assertEquals(revList, patched); + } + + @Test + public void testDiffWithListener() throws PatchFailedException { + List orgList = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revList = Arrays.asList("C", "B", "A", "B", "A", "C"); - /** - * Test of diff method, of class HistogramDiff. - */ - @Test - public void testDiff() throws PatchFailedException { - List orgList = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revList = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(orgList, revList, new HistogramDiff().computeDiff(orgList, revList, null)); - System.out.println(patch); - assertNotNull(patch); - assertEquals(3, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [DeleteDelta, position: 3, lines: [A, B]], [InsertDelta, position: 7, lines: [B, A, C]]]}", patch.toString()); + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate( + orgList, + revList, + new HistogramDiff().computeDiff(orgList, revList, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } - List patched = patch.applyTo(orgList); - assertEquals(revList, patched); - } - - @Test - public void testDiffWithListener() throws PatchFailedException { - List orgList = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revList = Arrays.asList("C", "B", "A", "B", "A", "C"); - - List logdata = new ArrayList<>(); - final Patch patch = Patch.generate(orgList, revList, new HistogramDiff().computeDiff(orgList, revList, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + System.out.println(patch); + assertNotNull(patch); + assertEquals(3, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [DeleteDelta, position: 3, lines: [A, B]], [InsertDelta, position: 7, lines: [B, A, C]]]}", + patch.toString()); - @Override - public void diffEnd() { - logdata.add("end"); - } - })); - System.out.println(patch); - assertNotNull(patch); - assertEquals(3, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [DeleteDelta, position: 3, lines: [A, B]], [InsertDelta, position: 7, lines: [B, A, C]]]}", patch.toString()); + List patched = patch.applyTo(orgList); + assertEquals(revList, patched); - List patched = patch.applyTo(orgList); - assertEquals(revList, patched); - - System.out.println(logdata); - assertEquals(19, logdata.size()); - } + System.out.println(logdata); + assertEquals(19, logdata.size()); + } } diff --git a/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java b/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java index ccbc2f30..955f5f7d 100644 --- a/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java +++ b/java-diff-utils-jgit/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java @@ -15,6 +15,10 @@ */ package com.github.difflib.algorithm.jgit; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; @@ -26,10 +30,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import static java.util.stream.Collectors.toList; import java.util.zip.ZipFile; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; /** @@ -38,43 +39,46 @@ */ public class LRHistogramDiffTest { - @Test - public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException, PatchFailedException { - ZipFile zip = new ZipFile("target/test-classes/mocks/large_dataset1.zip"); - List original = readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))); - List revised = readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb"))); + @Test + public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException, PatchFailedException { + ZipFile zip = new ZipFile("target/test-classes/mocks/large_dataset1.zip"); + List original = readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))); + List revised = readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb"))); + + List logdata = new ArrayList<>(); + Patch patch = Patch.generate( + original, + revised, + new HistogramDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } - List logdata = new ArrayList<>(); - Patch patch = Patch.generate(original, revised, new HistogramDiff().computeDiff(original, revised, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Override + public void diffEnd() { + logdata.add("end"); + } + })); - @Override - public void diffEnd() { - logdata.add("end"); - } - })); + assertEquals(34, patch.getDeltas().size()); - assertEquals(34, patch.getDeltas().size()); + List created = patch.applyTo(original); + assertArrayEquals(revised.toArray(), created.toArray()); - List created = patch.applyTo(original); - assertArrayEquals(revised.toArray(), created.toArray()); - - assertEquals(246579, logdata.size()); - } + assertEquals(246579, logdata.size()); + } - public static List readStringListFromInputStream(InputStream is) throws IOException { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { + public static List readStringListFromInputStream(InputStream is) throws IOException { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { - return reader.lines().collect(toList()); - } - } + return reader.lines().collect(toList()); + } + } } diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index 95560b01..e0c1139f 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -1,67 +1,67 @@ + - 4.0.0 + 4.0.0 + io.github.java-diff-utils - java-diff-utils - jar - java-diff-utils - - io.github.java-diff-utils - java-diff-utils-parent - 4.16-SNAPSHOT - - - UTF-8 - + java-diff-utils-parent + 4.16-SNAPSHOT + + io.github.java-diff-utils + java-diff-utils + jar + java-diff-utils + + UTF-8 + - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + + - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.6.1 - - 1.8 - 1.8 - UTF-8 - - - - maven-jar-plugin - 3.0.2 - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - - io.github.javadiffutils - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - false - - target/test-classes/logging.properties - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + UTF-8 + + + + maven-jar-plugin + 3.0.2 + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + io.github.javadiffutils + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + target/test-classes/logging.properties + + + + + - diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index e0ac521f..e43be506 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -34,195 +34,190 @@ */ public final class DiffUtils { - /** - * This factory generates the DEFAULT_DIFF algorithm for all these routines. - */ - static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); - - /** - * Sets the default diff algorithm factory to be used by all diff routines. - * - * @param factory a {@link DiffAlgorithmFactory} representing the new default diff algorithm factory. - */ - public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { - DEFAULT_DIFF = factory; - } - - /** - * Computes the difference between two sequences of elements using the default diff algorithm. - * - * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); - } - - /** - * Computes the difference between two sequences of elements using the default diff algorithm. - * - * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); - } - - /** - * Computes the difference between two sequences of elements using the default diff algorithm. - * - * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. - * @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, boolean includeEqualParts) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); - } - - /** - * Computes the difference between two strings using the default diff algorithm. - * - * @param sourceText a {@link String} representing the original string. Must not be {@code null}. - * @param targetText a {@link String} representing the revised string. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. - * @return The patch describing the difference between the original and revised strings. Never {@code null}. - */ - public static Patch diff(String sourceText, String targetText, - DiffAlgorithmListener progress) { - return DiffUtils.diff( - Arrays.asList(sourceText.split("\n")), - Arrays.asList(targetText.split("\n")), progress); - } - - /** - * Computes the difference between the original and revised list of elements - * with default diff algorithm - * - * @param source a {@link List} representing the original text. Must not be {@code null}. - * @param target a {@link List} representing the revised text. Must not be {@code null}. - * @param equalizer a {@link BiPredicate} representing the equalizer object to replace the default compare - * algorithm (Object.equals). If {@code null} the default equalizer of the - * default algorithm is used. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diff(List source, List target, - BiPredicate equalizer) { - if (equalizer != null) { - return DiffUtils.diff(source, target, - DEFAULT_DIFF.create(equalizer)); - } - return DiffUtils.diff(source, target, new MyersDiff<>()); - } - - public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { - return diff(original, revised, algorithm, progress, false); - } - - /** - * Computes the difference between the original and revised list of elements - * with default diff algorithm - * - * @param original a {@link List} representing the original text. Must not be {@code null}. - * @param revised a {@link List} representing the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} representing the diff algorithm listener. - * @param includeEqualParts Include equal data parts into the patch. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress, - boolean includeEqualParts) { - Objects.requireNonNull(original, "original must not be null"); - Objects.requireNonNull(revised, "revised must not be null"); - Objects.requireNonNull(algorithm, "algorithm must not be null"); - - return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts); - } - - - /** - * Computes the difference between the original and revised list of elements - * with default diff algorithm - * - * @param original a {@link List} representing the original text. Must not be {@code null}. - * @param revised a {@link List} representing the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, DiffAlgorithmI algorithm) { - return diff(original, revised, algorithm, null); - } - - /** - * Computes the difference between the given texts inline. This one uses the - * "trick" to make out of texts lists of characters, like DiffRowGenerator - * does and merges those changes at the end together again. - * - * @param original a {@link String} representing the original text. Must not be {@code null}. - * @param revised a {@link String} representing the revised text. Must not be {@code null}. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diffInline(String original, String revised) { - List origList = new ArrayList<>(); - List revList = new ArrayList<>(); - for (Character character : original.toCharArray()) { - origList.add(character.toString()); - } - for (Character character : revised.toCharArray()) { - revList.add(character.toString()); - } - Patch patch = DiffUtils.diff(origList, revList); - for (AbstractDelta delta : patch.getDeltas()) { - delta.getSource().setLines(compressLines(delta.getSource().getLines(), "")); - delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), "")); - } - return patch; - } - - /** - * Applies the given patch to the original list and returns the revised list. - * - * @param original a {@link List} representing the original list. - * @param patch a {@link List} representing the patch to apply. - * @return the revised list. - * @throws PatchFailedException if the patch cannot be applied. - */ - public static List patch(List original, Patch patch) - throws PatchFailedException { - return patch.applyTo(original); - } - - /** - * Applies the given patch to the revised list and returns the original list. - * - * @param revised a {@link List} representing the revised list. - * @param patch a {@link Patch} representing the patch to apply. - * @return the original list. - * @throws PatchFailedException if the patch cannot be applied. - */ - public static List unpatch(List revised, Patch patch) { - return patch.restore(revised); - } - - private static List compressLines(List lines, String delimiter) { - if (lines.isEmpty()) { - return Collections.emptyList(); - } - return Collections.singletonList(String.join(delimiter, lines)); - } - - private DiffUtils() { - } + /** + * This factory generates the DEFAULT_DIFF algorithm for all these routines. + */ + static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); + + /** + * Sets the default diff algorithm factory to be used by all diff routines. + * + * @param factory a {@link DiffAlgorithmFactory} representing the new default diff algorithm factory. + */ + public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { + DEFAULT_DIFF = factory; + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised, boolean includeEqualParts) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); + } + + /** + * Computes the difference between two strings using the default diff algorithm. + * + * @param sourceText a {@link String} representing the original string. Must not be {@code null}. + * @param targetText a {@link String} representing the revised string. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised strings. Never {@code null}. + */ + public static Patch diff(String sourceText, String targetText, DiffAlgorithmListener progress) { + return DiffUtils.diff(Arrays.asList(sourceText.split("\n")), Arrays.asList(targetText.split("\n")), progress); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param source a {@link List} representing the original text. Must not be {@code null}. + * @param target a {@link List} representing the revised text. Must not be {@code null}. + * @param equalizer a {@link BiPredicate} representing the equalizer object to replace the default compare + * algorithm (Object.equals). If {@code null} the default equalizer of the + * default algorithm is used. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff(List source, List target, BiPredicate equalizer) { + if (equalizer != null) { + return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer)); + } + return DiffUtils.diff(source, target, new MyersDiff<>()); + } + + public static Patch diff( + List original, List revised, DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { + return diff(original, revised, algorithm, progress, false); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the diff algorithm listener. + * @param includeEqualParts Include equal data parts into the patch. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff( + List original, + List revised, + DiffAlgorithmI algorithm, + DiffAlgorithmListener progress, + boolean includeEqualParts) { + Objects.requireNonNull(original, "original must not be null"); + Objects.requireNonNull(revised, "revised must not be null"); + Objects.requireNonNull(algorithm, "algorithm must not be null"); + + return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised, DiffAlgorithmI algorithm) { + return diff(original, revised, algorithm, null); + } + + /** + * Computes the difference between the given texts inline. This one uses the + * "trick" to make out of texts lists of characters, like DiffRowGenerator + * does and merges those changes at the end together again. + * + * @param original a {@link String} representing the original text. Must not be {@code null}. + * @param revised a {@link String} representing the revised text. Must not be {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diffInline(String original, String revised) { + List origList = new ArrayList<>(); + List revList = new ArrayList<>(); + for (Character character : original.toCharArray()) { + origList.add(character.toString()); + } + for (Character character : revised.toCharArray()) { + revList.add(character.toString()); + } + Patch patch = DiffUtils.diff(origList, revList); + for (AbstractDelta delta : patch.getDeltas()) { + delta.getSource().setLines(compressLines(delta.getSource().getLines(), "")); + delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), "")); + } + return patch; + } + + /** + * Applies the given patch to the original list and returns the revised list. + * + * @param original a {@link List} representing the original list. + * @param patch a {@link List} representing the patch to apply. + * @return the revised list. + * @throws PatchFailedException if the patch cannot be applied. + */ + public static List patch(List original, Patch patch) throws PatchFailedException { + return patch.applyTo(original); + } + + /** + * Applies the given patch to the revised list and returns the original list. + * + * @param revised a {@link List} representing the revised list. + * @param patch a {@link Patch} representing the patch to apply. + * @return the original list. + * @throws PatchFailedException if the patch cannot be applied. + */ + public static List unpatch(List revised, Patch patch) { + return patch.restore(revised); + } + + private static List compressLines(List lines, String delimiter) { + if (lines.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(String.join(delimiter, lines)); + } + + private DiffUtils() {} } diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 94786b6c..ca6a34ad 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -15,11 +15,10 @@ */ package com.github.difflib; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; -import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -35,433 +34,430 @@ */ public final class UnifiedDiffUtils { - private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern - .compile("^@@\\s+-(\\d+)(?:,(\\d+))?\\s+\\+(\\d+)(?:,(\\d+))?\\s+@@.*$"); - private static final String NULL_FILE_INDICATOR = "/dev/null"; - - /** - * Parse the given text in unified format and creates the list of deltas for it. - * - * @param diff the text in unified format - * @return the patch with deltas. - */ - public static Patch parseUnifiedDiff(List diff) { - boolean inPrelude = true; - List rawChunk = new ArrayList<>(); - Patch patch = new Patch<>(); - - int old_ln = 0; - int new_ln = 0; - String tag; - String rest; - for (String line : diff) { - // Skip leading lines until after we've seen one starting with '+++' - if (inPrelude) { - if (line.startsWith("+++")) { - inPrelude = false; - } - continue; - } - Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); - if (m.find()) { - // Process the lines in the previous chunk - processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); - // Parse the @@ header - old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); - new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); - - if (old_ln == 0) { - old_ln = 1; - } - if (new_ln == 0) { - new_ln = 1; - } - } else { - if (line.length() > 0) { - tag = line.substring(0, 1); - rest = line.substring(1); - if (" ".equals(tag) || "+".equals(tag) || "-".equals(tag)) { - rawChunk.add(new String[]{tag, rest}); - } - } else { - rawChunk.add(new String[]{" ", ""}); - } - } - } - - // Process the lines in the last chunk - processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); - - return patch; - } - - private static void processLinesInPrevChunk(List rawChunk, Patch patch, int old_ln, int new_ln) { - String tag; - String rest; - if (!rawChunk.isEmpty()) { - List oldChunkLines = new ArrayList<>(); - List newChunkLines = new ArrayList<>(); - - List removePosition = new ArrayList<>(); - List addPosition = new ArrayList<>(); - int removeNum = 0; - int addNum = 0; - for (String[] raw_line : rawChunk) { - tag = raw_line[0]; - rest = raw_line[1]; - if (" ".equals(tag) || "-".equals(tag)) { - removeNum++; - oldChunkLines.add(rest); - if ("-".equals(tag)) { - removePosition.add(old_ln - 1 + removeNum); - } - } - if (" ".equals(tag) || "+".equals(tag)) { - addNum++; - newChunkLines.add(rest); - if ("+".equals(tag)) { - addPosition.add(new_ln - 1 + addNum); - } - } - } - patch.addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, oldChunkLines, removePosition), new Chunk<>( - new_ln - 1, newChunkLines, addPosition))); - rawChunk.clear(); - } - } - - /** - * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format - * text representing the Patch. Author: Bill James (tankerbay@gmail.com). - * - * @param originalFileName - Filename of the original (unrevised file) - * @param revisedFileName - Filename of the revised file - * @param originalLines - Lines of the original file - * @param patch - Patch created by the diff() function - * @param contextSize - number of lines of context output around each difference in the file. - * @return List of strings representing the Unified Diff representation of the Patch argument. - */ - public static List generateUnifiedDiff(String originalFileName, - String revisedFileName, List originalLines, Patch patch, - int contextSize) { - if (!patch.getDeltas().isEmpty()) { - List ret = new ArrayList<>(); - ret.add("--- " + Optional.ofNullable(originalFileName).orElse(NULL_FILE_INDICATOR)); - ret.add("+++ " + Optional.ofNullable(revisedFileName).orElse(NULL_FILE_INDICATOR)); - - List> patchDeltas = new ArrayList<>( - patch.getDeltas()); - - // code outside the if block also works for single-delta issues. - List> deltas = new ArrayList<>(); // current - // list - // of - // Delta's to - // process - AbstractDelta delta = patchDeltas.get(0); - deltas.add(delta); // add the first Delta to the current set - // if there's more than 1 Delta, we may need to output them together - if (patchDeltas.size() > 1) { - for (int i = 1; i < patchDeltas.size(); i++) { - int position = delta.getSource().getPosition(); // store - // the - // current - // position - // of - // the first Delta - - // Check if the next Delta is too close to the current - // position. - // And if it is, add it to the current set - AbstractDelta nextDelta = patchDeltas.get(i); - if ((position + delta.getSource().size() + contextSize) >= (nextDelta - .getSource().getPosition() - contextSize)) { - deltas.add(nextDelta); - } else { - // if it isn't, output the current set, - // then create a new set and add the current Delta to - // it. - List curBlock = processDeltas(originalLines, - deltas, contextSize, false); - ret.addAll(curBlock); - deltas.clear(); - deltas.add(nextDelta); - } - delta = nextDelta; - } - - } - // don't forget to process the last set of Deltas - List curBlock = processDeltas(originalLines, deltas, - contextSize, patchDeltas.size() == 1 && originalFileName == null); - ret.addAll(curBlock); - return ret; - } - return new ArrayList<>(); - } - - /** - * processDeltas takes a list of Deltas and outputs them together in a single block of - * Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com). - * - * @param origLines - the lines of the original file - * @param deltas - the Deltas to be output as a single block - * @param contextSize - the number of lines of context to place around block - * @return - */ - private static List processDeltas(List origLines, - List> deltas, int contextSize, boolean newFile) { - List buffer = new ArrayList<>(); - int origTotal = 0; // counter for total lines output from Original - int revTotal = 0; // counter for total lines output from Original - int line; - - AbstractDelta curDelta = deltas.get(0); - int origStart; - if (newFile) { - origStart = 0; - } else { - // NOTE: +1 to overcome the 0-offset Position - origStart = curDelta.getSource().getPosition() + 1 - contextSize; - if (origStart < 1) { - origStart = 1; - } - } - - int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; - if (revStart < 1) { - revStart = 1; - } - - // find the start of the wrapper context code - int contextStart = curDelta.getSource().getPosition() - contextSize; - if (contextStart < 0) { - contextStart = 0; // clamp to the start of the file - } - - // output the context before the first Delta - for (line = contextStart; line < curDelta.getSource().getPosition(); line++) { // - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // output the first Delta - buffer.addAll(getDeltaText(curDelta)); - origTotal += curDelta.getSource().getLines().size(); - revTotal += curDelta.getTarget().getLines().size(); - - int deltaIndex = 1; - while (deltaIndex < deltas.size()) { // for each of the other Deltas - AbstractDelta nextDelta = deltas.get(deltaIndex); - int intermediateStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = intermediateStart; line < nextDelta.getSource() - .getPosition(); line++) { - // output the code between the last Delta and this one - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - buffer.addAll(getDeltaText(nextDelta)); // output the Delta - origTotal += nextDelta.getSource().getLines().size(); - revTotal += nextDelta.getTarget().getLines().size(); - curDelta = nextDelta; - deltaIndex++; - } - - // Now output the post-Delta context code, clamping the end of the file - contextStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = contextStart; (line < (contextStart + contextSize)) - && (line < origLines.size()); line++) { - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // Create and insert the block header, conforming to the Unified Diff - // standard - StringBuilder header = new StringBuilder(); - header.append("@@ -"); - header.append(origStart); - header.append(","); - header.append(origTotal); - header.append(" +"); - header.append(revStart); - header.append(","); - header.append(revTotal); - header.append(" @@"); - buffer.add(0, header.toString()); - - return buffer; - } - - /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com). - * - * @param delta - the Delta to output - * @return list of String lines of code. - */ - private static List getDeltaText(AbstractDelta delta) { - List buffer = new ArrayList<>(); - for (String line : delta.getSource().getLines()) { - buffer.add("-" + line); - } - for (String line : delta.getTarget().getLines()) { - buffer.add("+" + line); - } - return buffer; - } - - private UnifiedDiffUtils() { - } - - /** - * Compare the differences between two files and return to the original file and diff format - * - * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. - * You can see all the differences and unmodified places from the original file. - * Also, this will be very easy and useful for making side-by-side comparison display applications, - * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) - * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) - * - * @param original Original file content - * @param revised revised file content - * - */ - public static List generateOriginalAndDiff(List original, List revised) { - return generateOriginalAndDiff(original, revised, null, null); - } - - - /** - * Compare the differences between two files and return to the original file and diff format - * - * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. - * You can see all the differences and unmodified places from the original file. - * Also, this will be very easy and useful for making side-by-side comparison display applications, - * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) - * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) - * - * @param original Original file content - * @param revised revised file content - * @param originalFileName Original file name - * @param revisedFileName revised file name - */ - public static List generateOriginalAndDiff(List original, List revised, String originalFileName, String revisedFileName) { - String originalFileNameTemp = originalFileName; - String revisedFileNameTemp = revisedFileName; - if (originalFileNameTemp == null) { - originalFileNameTemp = "original"; - } - if (revisedFileNameTemp == null) { - revisedFileNameTemp = "revised"; - } - Patch patch = DiffUtils.diff(original, revised); - List unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0); - if (unifiedDiff.isEmpty()) { - unifiedDiff.add("--- " + originalFileNameTemp); - unifiedDiff.add("+++ " + revisedFileNameTemp); - unifiedDiff.add("@@ -0,0 +0,0 @@"); - } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { - unifiedDiff.set(1, unifiedDiff.get(1)); - unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); - } - List originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList()); - return insertOrig(originalWithPrefix, unifiedDiff); - } - - //Insert the diff format to the original file - private static List insertOrig(List original, List unifiedDiff) { - List result = new ArrayList<>(); - List> diffList = new ArrayList<>(); - List diff = new ArrayList<>(); - for (int i = 0; i < unifiedDiff.size(); i++) { - String u = unifiedDiff.get(i); - if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { - List twoList = new ArrayList<>(); - twoList.addAll(diff); - diffList.add(twoList); - diff.clear(); - diff.add(u); - continue; - } - if (i == unifiedDiff.size() - 1) { - diff.add(u); - List twoList = new ArrayList<>(); - twoList.addAll(diff); - diffList.add(twoList); - diff.clear(); - break; - } - diff.add(u); - } - insertOrig(diffList, result, original); - return result; - } - - //Insert the diff format to the original file - private static void insertOrig(List> diffList, List result, List original) { - for (int i = 0; i < diffList.size(); i++) { - List diff = diffList.get(i); - List nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); - String simb = i == 0 ? diff.get(2) : diff.get(0); - String nexSimb = nexDiff == null ? null : nexDiff.get(0); - insert(result, diff); - Map map = getRowMap(simb); - if (null != nexSimb) { - Map nexMap = getRowMap(nexSimb); - int start = 0; - if (map.get("orgRow") != 0) { - start = map.get("orgRow") + map.get("orgDel") - 1; - } - int end = nexMap.get("revRow") - 2; - insert(result, getOrigList(original, start, end)); - } - int start = map.get("orgRow") + map.get("orgDel") - 1; - start = start == -1 ? 0 : start; - if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { - insert(result, getOrigList(original, start, original.size() - 1)); - } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { - insert(result, getOrigList(original, start, original.size() - 1)); - } - } - } - - //Insert the unchanged content in the source file into result - private static void insert(List result, List noChangeContent) { - for (String ins : noChangeContent) { - result.add(ins); - } - } - - //Parse the line containing @@ to get the modified line number to delete or add a few lines - private static Map getRowMap(String str) { - Map map = new HashMap<>(); - if (str.startsWith("@@")) { - String[] sp = str.split(" "); - String org = sp[1]; - String[] orgSp = org.split(","); - map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); - map.put("orgDel", Integer.valueOf(orgSp[1])); - String[] revSp = org.split(","); - map.put("revRow", Integer.valueOf(revSp[0].substring(1))); - map.put("revAdd", Integer.valueOf(revSp[1])); - } - return map; - } - - //Get the specified part of the line from the original file - private static List getOrigList(List originalWithPrefix, int start, int end) { - List list = new ArrayList<>(); - if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) { - int startTemp = start; - for (; startTemp <= end; startTemp++) { - list.add(originalWithPrefix.get(startTemp)); - } - } - return list; - } + private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = + Pattern.compile("^@@\\s+-(\\d+)(?:,(\\d+))?\\s+\\+(\\d+)(?:,(\\d+))?\\s+@@.*$"); + private static final String NULL_FILE_INDICATOR = "/dev/null"; + + /** + * Parse the given text in unified format and creates the list of deltas for it. + * + * @param diff the text in unified format + * @return the patch with deltas. + */ + public static Patch parseUnifiedDiff(List diff) { + boolean inPrelude = true; + List rawChunk = new ArrayList<>(); + Patch patch = new Patch<>(); + + int old_ln = 0; + int new_ln = 0; + String tag; + String rest; + for (String line : diff) { + // Skip leading lines until after we've seen one starting with '+++' + if (inPrelude) { + if (line.startsWith("+++")) { + inPrelude = false; + } + continue; + } + Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); + if (m.find()) { + // Process the lines in the previous chunk + processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); + // Parse the @@ header + old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); + new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); + + if (old_ln == 0) { + old_ln = 1; + } + if (new_ln == 0) { + new_ln = 1; + } + } else { + if (line.length() > 0) { + tag = line.substring(0, 1); + rest = line.substring(1); + if (" ".equals(tag) || "+".equals(tag) || "-".equals(tag)) { + rawChunk.add(new String[] {tag, rest}); + } + } else { + rawChunk.add(new String[] {" ", ""}); + } + } + } + + // Process the lines in the last chunk + processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); + + return patch; + } + + private static void processLinesInPrevChunk(List rawChunk, Patch patch, int old_ln, int new_ln) { + String tag; + String rest; + if (!rawChunk.isEmpty()) { + List oldChunkLines = new ArrayList<>(); + List newChunkLines = new ArrayList<>(); + + List removePosition = new ArrayList<>(); + List addPosition = new ArrayList<>(); + int removeNum = 0; + int addNum = 0; + for (String[] raw_line : rawChunk) { + tag = raw_line[0]; + rest = raw_line[1]; + if (" ".equals(tag) || "-".equals(tag)) { + removeNum++; + oldChunkLines.add(rest); + if ("-".equals(tag)) { + removePosition.add(old_ln - 1 + removeNum); + } + } + if (" ".equals(tag) || "+".equals(tag)) { + addNum++; + newChunkLines.add(rest); + if ("+".equals(tag)) { + addPosition.add(new_ln - 1 + addNum); + } + } + } + patch.addDelta(new ChangeDelta<>( + new Chunk<>(old_ln - 1, oldChunkLines, removePosition), + new Chunk<>(new_ln - 1, newChunkLines, addPosition))); + rawChunk.clear(); + } + } + + /** + * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format + * text representing the Patch. Author: Bill James (tankerbay@gmail.com). + * + * @param originalFileName - Filename of the original (unrevised file) + * @param revisedFileName - Filename of the revised file + * @param originalLines - Lines of the original file + * @param patch - Patch created by the diff() function + * @param contextSize - number of lines of context output around each difference in the file. + * @return List of strings representing the Unified Diff representation of the Patch argument. + */ + public static List generateUnifiedDiff( + String originalFileName, + String revisedFileName, + List originalLines, + Patch patch, + int contextSize) { + if (!patch.getDeltas().isEmpty()) { + List ret = new ArrayList<>(); + ret.add("--- " + Optional.ofNullable(originalFileName).orElse(NULL_FILE_INDICATOR)); + ret.add("+++ " + Optional.ofNullable(revisedFileName).orElse(NULL_FILE_INDICATOR)); + + List> patchDeltas = new ArrayList<>(patch.getDeltas()); + + // code outside the if block also works for single-delta issues. + List> deltas = new ArrayList<>(); // current + // list + // of + // Delta's to + // process + AbstractDelta delta = patchDeltas.get(0); + deltas.add(delta); // add the first Delta to the current set + // if there's more than 1 Delta, we may need to output them together + if (patchDeltas.size() > 1) { + for (int i = 1; i < patchDeltas.size(); i++) { + int position = delta.getSource().getPosition(); // store + // the + // current + // position + // of + // the first Delta + + // Check if the next Delta is too close to the current + // position. + // And if it is, add it to the current set + AbstractDelta nextDelta = patchDeltas.get(i); + if ((position + delta.getSource().size() + contextSize) + >= (nextDelta.getSource().getPosition() - contextSize)) { + deltas.add(nextDelta); + } else { + // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + List curBlock = processDeltas(originalLines, deltas, contextSize, false); + ret.addAll(curBlock); + deltas.clear(); + deltas.add(nextDelta); + } + delta = nextDelta; + } + } + // don't forget to process the last set of Deltas + List curBlock = processDeltas( + originalLines, deltas, contextSize, patchDeltas.size() == 1 && originalFileName == null); + ret.addAll(curBlock); + return ret; + } + return new ArrayList<>(); + } + + /** + * processDeltas takes a list of Deltas and outputs them together in a single block of + * Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com). + * + * @param origLines - the lines of the original file + * @param deltas - the Deltas to be output as a single block + * @param contextSize - the number of lines of context to place around block + * @return + */ + private static List processDeltas( + List origLines, List> deltas, int contextSize, boolean newFile) { + List buffer = new ArrayList<>(); + int origTotal = 0; // counter for total lines output from Original + int revTotal = 0; // counter for total lines output from Original + int line; + + AbstractDelta curDelta = deltas.get(0); + int origStart; + if (newFile) { + origStart = 0; + } else { + // NOTE: +1 to overcome the 0-offset Position + origStart = curDelta.getSource().getPosition() + 1 - contextSize; + if (origStart < 1) { + origStart = 1; + } + } + + int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; + if (revStart < 1) { + revStart = 1; + } + + // find the start of the wrapper context code + int contextStart = curDelta.getSource().getPosition() - contextSize; + if (contextStart < 0) { + contextStart = 0; // clamp to the start of the file + } + + // output the context before the first Delta + for (line = contextStart; line < curDelta.getSource().getPosition(); line++) { // + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // output the first Delta + buffer.addAll(getDeltaText(curDelta)); + origTotal += curDelta.getSource().getLines().size(); + revTotal += curDelta.getTarget().getLines().size(); + + int deltaIndex = 1; + while (deltaIndex < deltas.size()) { // for each of the other Deltas + AbstractDelta nextDelta = deltas.get(deltaIndex); + int intermediateStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = intermediateStart; line < nextDelta.getSource().getPosition(); line++) { + // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + buffer.addAll(getDeltaText(nextDelta)); // output the Delta + origTotal += nextDelta.getSource().getLines().size(); + revTotal += nextDelta.getTarget().getLines().size(); + curDelta = nextDelta; + deltaIndex++; + } + + // Now output the post-Delta context code, clamping the end of the file + contextStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // Create and insert the block header, conforming to the Unified Diff + // standard + StringBuilder header = new StringBuilder(); + header.append("@@ -"); + header.append(origStart); + header.append(","); + header.append(origTotal); + header.append(" +"); + header.append(revStart); + header.append(","); + header.append(revTotal); + header.append(" @@"); + buffer.add(0, header.toString()); + + return buffer; + } + + /** + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com). + * + * @param delta - the Delta to output + * @return list of String lines of code. + */ + private static List getDeltaText(AbstractDelta delta) { + List buffer = new ArrayList<>(); + for (String line : delta.getSource().getLines()) { + buffer.add("-" + line); + } + for (String line : delta.getTarget().getLines()) { + buffer.add("+" + line); + } + return buffer; + } + + private UnifiedDiffUtils() {} + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * + */ + public static List generateOriginalAndDiff(List original, List revised) { + return generateOriginalAndDiff(original, revised, null, null); + } + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * @param originalFileName Original file name + * @param revisedFileName revised file name + */ + public static List generateOriginalAndDiff( + List original, List revised, String originalFileName, String revisedFileName) { + String originalFileNameTemp = originalFileName; + String revisedFileNameTemp = revisedFileName; + if (originalFileNameTemp == null) { + originalFileNameTemp = "original"; + } + if (revisedFileNameTemp == null) { + revisedFileNameTemp = "revised"; + } + Patch patch = DiffUtils.diff(original, revised); + List unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0); + if (unifiedDiff.isEmpty()) { + unifiedDiff.add("--- " + originalFileNameTemp); + unifiedDiff.add("+++ " + revisedFileNameTemp); + unifiedDiff.add("@@ -0,0 +0,0 @@"); + } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { + unifiedDiff.set(1, unifiedDiff.get(1)); + unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); + } + List originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList()); + return insertOrig(originalWithPrefix, unifiedDiff); + } + + // Insert the diff format to the original file + private static List insertOrig(List original, List unifiedDiff) { + List result = new ArrayList<>(); + List> diffList = new ArrayList<>(); + List diff = new ArrayList<>(); + for (int i = 0; i < unifiedDiff.size(); i++) { + String u = unifiedDiff.get(i); + if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + diff.add(u); + continue; + } + if (i == unifiedDiff.size() - 1) { + diff.add(u); + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + break; + } + diff.add(u); + } + insertOrig(diffList, result, original); + return result; + } + + // Insert the diff format to the original file + private static void insertOrig(List> diffList, List result, List original) { + for (int i = 0; i < diffList.size(); i++) { + List diff = diffList.get(i); + List nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); + String simb = i == 0 ? diff.get(2) : diff.get(0); + String nexSimb = nexDiff == null ? null : nexDiff.get(0); + insert(result, diff); + Map map = getRowMap(simb); + if (null != nexSimb) { + Map nexMap = getRowMap(nexSimb); + int start = 0; + if (map.get("orgRow") != 0) { + start = map.get("orgRow") + map.get("orgDel") - 1; + } + int end = nexMap.get("revRow") - 2; + insert(result, getOrigList(original, start, end)); + } + int start = map.get("orgRow") + map.get("orgDel") - 1; + start = start == -1 ? 0 : start; + if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { + insert(result, getOrigList(original, start, original.size() - 1)); + } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { + insert(result, getOrigList(original, start, original.size() - 1)); + } + } + } + + // Insert the unchanged content in the source file into result + private static void insert(List result, List noChangeContent) { + for (String ins : noChangeContent) { + result.add(ins); + } + } + + // Parse the line containing @@ to get the modified line number to delete or add a few lines + private static Map getRowMap(String str) { + Map map = new HashMap<>(); + if (str.startsWith("@@")) { + String[] sp = str.split(" "); + String org = sp[1]; + String[] orgSp = org.split(","); + map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); + map.put("orgDel", Integer.valueOf(orgSp[1])); + String[] revSp = org.split(","); + map.put("revRow", Integer.valueOf(revSp[0].substring(1))); + map.put("revAdd", Integer.valueOf(revSp[1])); + } + return map; + } + + // Get the specified part of the line from the original file + private static List getOrigList(List originalWithPrefix, int start, int end) { + List list = new ArrayList<>(); + if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) { + int startTemp = start; + for (; startTemp <= end; startTemp++) { + list.add(originalWithPrefix.get(startTemp)); + } + } + return list; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java index 9b6f1dfe..7c2fae8c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java @@ -23,25 +23,25 @@ */ public class Change { - public final DeltaType deltaType; - public final int startOriginal; - public final int endOriginal; - public final int startRevised; - public final int endRevised; + public final DeltaType deltaType; + public final int startOriginal; + public final int endOriginal; + public final int startRevised; + public final int endRevised; - public Change(DeltaType deltaType, int startOriginal, int endOriginal, int startRevised, int endRevised) { - this.deltaType = deltaType; - this.startOriginal = startOriginal; - this.endOriginal = endOriginal; - this.startRevised = startRevised; - this.endRevised = endRevised; - } - - public Change withEndOriginal(int endOriginal) { - return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); - } - - public Change withEndRevised(int endRevised) { - return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); - } + public Change(DeltaType deltaType, int startOriginal, int endOriginal, int startRevised, int endRevised) { + this.deltaType = deltaType; + this.startOriginal = startOriginal; + this.endOriginal = endOriginal; + this.startRevised = startRevised; + this.endRevised = endRevised; + } + + public Change withEndOriginal(int endOriginal) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } + + public Change withEndRevised(int endRevised) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java index 7e5205cd..307ee7e3 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java @@ -18,12 +18,12 @@ import java.util.function.BiPredicate; /** - * Tool to create new instances of a diff algorithm. This one is only needed at the moment to + * Tool to create new instances of a diff algorithm. This one is only needed at the moment to * set DiffUtils default diff algorithm. * @author tw */ public interface DiffAlgorithmFactory { - DiffAlgorithmI create(); - - DiffAlgorithmI create(BiPredicate equalizer); + DiffAlgorithmI create(); + + DiffAlgorithmI create(BiPredicate equalizer); } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java index 117656e4..a12b499a 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java @@ -26,25 +26,25 @@ */ public interface DiffAlgorithmI { - /** - * Computes the changeset to patch the source list to the target list. - * - * @param source source data - * @param target target data - * @param progress progress listener - * @return - */ - List computeDiff(List source, List target, DiffAlgorithmListener progress); + /** + * Computes the changeset to patch the source list to the target list. + * + * @param source source data + * @param target target data + * @param progress progress listener + * @return + */ + List computeDiff(List source, List target, DiffAlgorithmListener progress); - /** - * Simple extension to compute a changeset using arrays. - * - * @param source - * @param target - * @param progress - * @return - */ - default List computeDiff(T[] source, T[] target, DiffAlgorithmListener progress) { - return computeDiff(Arrays.asList(source), Arrays.asList(target), progress); - } + /** + * Simple extension to compute a changeset using arrays. + * + * @param source + * @param target + * @param progress + * @return + */ + default List computeDiff(T[] source, T[] target, DiffAlgorithmListener progress) { + return computeDiff(Arrays.asList(source), Arrays.asList(target), progress); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java index a141d7be..21d864f5 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java @@ -20,15 +20,16 @@ * @author Tobias Warneke (t.warneke@gmx.net) */ public interface DiffAlgorithmListener { - void diffStart(); - - /** - * This is a step within the diff algorithm. Due to different implementations the value - * is not strict incrementing to the max and is not guarantee to reach the max. It could - * stop before. - * @param value - * @param max - */ - void diffStep(int value, int max); - void diffEnd(); + void diffStart(); + + /** + * This is a step within the diff algorithm. Due to different implementations the value + * is not strict incrementing to the max and is not guarantee to reach the max. It could + * stop before. + * @param value + * @param max + */ + void diffStep(int value, int max); + + void diffEnd(); } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 2517de46..5233e02b 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -31,170 +31,168 @@ */ public final class MyersDiff implements DiffAlgorithmI { - private final BiPredicate equalizer; - - public MyersDiff() { - equalizer = Object::equals; - } - - public MyersDiff(final BiPredicate equalizer) { - Objects.requireNonNull(equalizer, "equalizer must not be null"); - this.equalizer = equalizer; - } - - /** - * {@inheritDoc} - * - * Return empty diff if get the error while procession the difference. - */ - @Override - public List computeDiff(final List source, final List target, DiffAlgorithmListener progress) { - Objects.requireNonNull(source, "source list must not be null"); - Objects.requireNonNull(target, "target list must not be null"); - - if (progress != null) { - progress.diffStart(); - } - PathNode path = buildPath(source, target, progress); - List result = buildRevision(path, source, target); - if (progress != null) { - progress.diffEnd(); - } - return result; - } - - /** - * Computes the minimum diffpath that expresses de differences between the - * original and revised sequences, according to Gene Myers differencing - * algorithm. - * - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A minimum {@link PathNode Path} accross the differences graph. - * @throws DifferentiationFailedException if a diff path could not be found. - */ - private PathNode buildPath(final List orig, final List rev, DiffAlgorithmListener progress) { - Objects.requireNonNull(orig, "original sequence is null"); - Objects.requireNonNull(rev, "revised sequence is null"); - - // these are local constants - final int N = orig.size(); - final int M = rev.size(); - - 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 PathNode(0, -1, true, true, null); - for (int d = 0; d < MAX; d++) { - if (progress != null) { - progress.diffStep(d, MAX); - } - for (int k = -d; k <= d; k += 2) { - final int kmiddle = middle + k; - final int kplus = kmiddle + 1; - final int kminus = kmiddle - 1; - PathNode prev; - int i; - - if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) { - i = diagonal[kplus].i; - prev = diagonal[kplus]; - } else { - i = diagonal[kminus].i + 1; - prev = diagonal[kminus]; - } - - diagonal[kminus] = null; // no longer used - - int j = i - k; - - PathNode node = new PathNode(i, j, false, false, prev); - - while (i < N && j < M && equalizer.test(orig.get(i), rev.get(j))) { - i++; - j++; - } - - if (i != node.i) { - node = new PathNode(i, j, true, false, 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 IllegalStateException("could not find a diff path"); - } - - /** - * Constructs a {@link Patch} from a difference path. - * - * @param actualPath The path. - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be - * built from the given path. - */ - private List buildRevision(PathNode actualPath, List orig, List rev) { - Objects.requireNonNull(actualPath, "path is null"); - Objects.requireNonNull(orig, "original sequence is null"); - Objects.requireNonNull(rev, "revised sequence is null"); - - PathNode path = actualPath; - List changes = new ArrayList<>(); - if (path.isSnake()) { - path = path.prev; - } - while (path != null && path.prev != null && path.prev.j >= 0) { - if (path.isSnake()) { - 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; - - if (ianchor == i && janchor != j) { - changes.add(new Change(DeltaType.INSERT, ianchor, i, janchor, j)); - } else if (ianchor != i && janchor == j) { - changes.add(new Change(DeltaType.DELETE, ianchor, i, janchor, j)); - } else { - changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j)); - } - - if (path.isSnake()) { - path = path.prev; - } - } - return changes; - } - - /** - * Factory to create instances of this specific diff algorithm. - */ - public static DiffAlgorithmFactory factory() { - return new DiffAlgorithmFactory() { - @Override - public DiffAlgorithmI - create() { - return new MyersDiff<>(); - } - - @Override - public DiffAlgorithmI - create(BiPredicate < T, T > equalizer) { - return new MyersDiff<>(equalizer); - } - }; - } + private final BiPredicate equalizer; + + public MyersDiff() { + equalizer = Object::equals; + } + + public MyersDiff(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + /** + * {@inheritDoc} + * + * Return empty diff if get the error while procession the difference. + */ + @Override + public List computeDiff(final List source, final List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + PathNode path = buildPath(source, target, progress); + List result = buildRevision(path, source, target); + if (progress != null) { + progress.diffEnd(); + } + return result; + } + + /** + * Computes the minimum diffpath that expresses de differences between the + * original and revised sequences, according to Gene Myers differencing + * algorithm. + * + * @param orig The original sequence. + * @param rev The revised sequence. + * @return A minimum {@link PathNode Path} accross the differences graph. + * @throws DifferentiationFailedException if a diff path could not be found. + */ + private PathNode buildPath(final List orig, final List rev, DiffAlgorithmListener progress) { + Objects.requireNonNull(orig, "original sequence is null"); + Objects.requireNonNull(rev, "revised sequence is null"); + + // these are local constants + final int N = orig.size(); + final int M = rev.size(); + + 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 PathNode(0, -1, true, true, null); + for (int d = 0; d < MAX; d++) { + if (progress != null) { + progress.diffStep(d, MAX); + } + for (int k = -d; k <= d; k += 2) { + final int kmiddle = middle + k; + final int kplus = kmiddle + 1; + final int kminus = kmiddle - 1; + PathNode prev; + int i; + + if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) { + i = diagonal[kplus].i; + prev = diagonal[kplus]; + } else { + i = diagonal[kminus].i + 1; + prev = diagonal[kminus]; + } + + diagonal[kminus] = null; // no longer used + + int j = i - k; + + PathNode node = new PathNode(i, j, false, false, prev); + + while (i < N && j < M && equalizer.test(orig.get(i), rev.get(j))) { + i++; + j++; + } + + if (i != node.i) { + node = new PathNode(i, j, true, false, 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 IllegalStateException("could not find a diff path"); + } + + /** + * Constructs a {@link Patch} from a difference path. + * + * @param actualPath The path. + * @param orig The original sequence. + * @param rev The revised sequence. + * @return A {@link Patch} script corresponding to the path. + * @throws DifferentiationFailedException if a {@link Patch} could not be + * built from the given path. + */ + private List buildRevision(PathNode actualPath, List orig, List rev) { + Objects.requireNonNull(actualPath, "path is null"); + Objects.requireNonNull(orig, "original sequence is null"); + Objects.requireNonNull(rev, "revised sequence is null"); + + PathNode path = actualPath; + List changes = new ArrayList<>(); + if (path.isSnake()) { + path = path.prev; + } + while (path != null && path.prev != null && path.prev.j >= 0) { + if (path.isSnake()) { + 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; + + if (ianchor == i && janchor != j) { + changes.add(new Change(DeltaType.INSERT, ianchor, i, janchor, j)); + } else if (ianchor != i && janchor == j) { + changes.add(new Change(DeltaType.DELETE, ianchor, i, janchor, j)); + } else { + changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j)); + } + + if (path.isSnake()) { + path = path.prev; + } + } + return changes; + } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI create() { + return new MyersDiff<>(); + } + + @Override + public DiffAlgorithmI create(BiPredicate equalizer) { + return new MyersDiff<>(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java index ca4114c4..f8c734ba 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -32,213 +32,214 @@ */ public class MyersDiffWithLinearSpace implements DiffAlgorithmI { - private final BiPredicate equalizer; - - public MyersDiffWithLinearSpace() { - equalizer = Object::equals; - } - - public MyersDiffWithLinearSpace(final BiPredicate equalizer) { - Objects.requireNonNull(equalizer, "equalizer must not be null"); - this.equalizer = equalizer; - } - - @Override - public List computeDiff(List source, List target, DiffAlgorithmListener progress) { - Objects.requireNonNull(source, "source list must not be null"); - Objects.requireNonNull(target, "target list must not be null"); - - if (progress != null) { - progress.diffStart(); - } - - DiffData data = new DiffData(source, target); - - int maxIdx = source.size() + target.size(); - - buildScript(data, 0, source.size(), 0, target.size(), idx -> { - if (progress != null) { - progress.diffStep(idx, maxIdx); - } - }); - - if (progress != null) { - progress.diffEnd(); - } - return data.script; - } - - private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { - if (progress != null) { - progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); - } - final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); - if (middle == null - || middle.start == end1 && middle.diag == end1 - end2 - || middle.end == start1 && middle.diag == start1 - start2) { - int i = start1; - int j = start2; - while (i < end1 || j < end2) { - if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { - //script.append(new KeepCommand<>(left.charAt(i))); - ++i; - ++j; - } else { - //TODO: compress these commands. - if (end1 - start1 > end2 - start2) { - //script.append(new DeleteCommand<>(left.charAt(i))); - if (data.script.isEmpty() - || data.script.get(data.script.size() - 1).endOriginal != i - || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { - data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); - } else { - data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); - } - ++i; - } else { - if (data.script.isEmpty() - || data.script.get(data.script.size() - 1).endRevised != j - || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { - data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); - } else { - data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndRevised(j + 1)); - } - ++j; - } - } - } - } else { - buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); - buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); - } - } - - private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { - final int m = end1 - start1; - final int n = end2 - start2; - if (m == 0 || n == 0) { - return null; - } - - final int delta = m - n; - final int sum = n + m; - final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; - data.vDown[1 + offset] = start1; - data.vUp[1 + offset] = end1 + 1; - - for (int d = 0; d <= offset; ++d) { - // Down - for (int k = -d; k <= d; k += 2) { - // First step - - final int i = k + offset; - if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { - data.vDown[i] = data.vDown[i + 1]; - } else { - data.vDown[i] = data.vDown[i - 1] + 1; - } - - int x = data.vDown[i]; - int y = x - start1 + start2 - k; - - while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { - data.vDown[i] = ++x; - ++y; - } - // Second step - if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { - if (data.vUp[i - delta] <= data.vDown[i]) { - return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); - } - } - } - - // Up - for (int k = delta - d; k <= delta + d; k += 2) { - // First step - final int i = k + offset - delta; - if (k == delta - d - || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { - data.vUp[i] = data.vUp[i + 1] - 1; - } else { - data.vUp[i] = data.vUp[i - 1]; - } - - int x = data.vUp[i] - 1; - int y = x - start1 + start2 - k; - while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { - data.vUp[i] = x--; - y--; - } - // Second step - if (delta % 2 == 0 && -d <= k && k <= d) { - if (data.vUp[i] <= data.vDown[i + delta]) { - return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); - } - } - } - } - - // According to Myers, this cannot happen - throw new IllegalStateException("could not find a diff path"); - } - - private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { - int end = start; - while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { - ++end; - } - return new Snake(start, end, diag); - } - - private class DiffData { - - final int size; - final int[] vDown; - final int[] vUp; - final List script; - final List source; - final List target; - - public DiffData(List source, List target) { - this.source = source; - this.target = target; - size = source.size() + target.size() + 2; - vDown = new int[size]; - vUp = new int[size]; - script = new ArrayList<>(); - } - } - - private class Snake { - - final int start; - final int end; - final int diag; - - public Snake(final int start, final int end, final int diag) { - this.start = start; - this.end = end; - this.diag = diag; - } - } - - /** - * Factory to create instances of this specific diff algorithm. - */ - public static DiffAlgorithmFactory factory() { - return new DiffAlgorithmFactory() { - @Override - public DiffAlgorithmI - create() { - return new MyersDiffWithLinearSpace<>(); - } - - @Override - public DiffAlgorithmI - create(BiPredicate < T, T > equalizer) { - return new MyersDiffWithLinearSpace<>(equalizer); - } - }; - } + private final BiPredicate equalizer; + + public MyersDiffWithLinearSpace() { + equalizer = Object::equals; + } + + public MyersDiffWithLinearSpace(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + @Override + public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + + DiffData data = new DiffData(source, target); + + int maxIdx = source.size() + target.size(); + + buildScript(data, 0, source.size(), 0, target.size(), idx -> { + if (progress != null) { + progress.diffStep(idx, maxIdx); + } + }); + + if (progress != null) { + progress.diffEnd(); + } + return data.script; + } + + private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { + if (progress != null) { + progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); + } + final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); + if (middle == null + || middle.start == end1 && middle.diag == end1 - end2 + || middle.end == start1 && middle.diag == start1 - start2) { + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { + // script.append(new KeepCommand<>(left.charAt(i))); + ++i; + ++j; + } else { + // TODO: compress these commands. + if (end1 - start1 > end2 - start2) { + // script.append(new DeleteCommand<>(left.charAt(i))); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endOriginal != i + || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + } else { + data.script.set( + data.script.size() - 1, + data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); + } + ++i; + } else { + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endRevised != j + || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + } else { + data.script.set( + data.script.size() - 1, + data.script.get(data.script.size() - 1).withEndRevised(j + 1)); + } + ++j; + } + } + } + } else { + buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); + buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); + } + } + + private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + data.vDown[1 + offset] = start1; + data.vUp[1 + offset] = end1 + 1; + + for (int d = 0; d <= offset; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { + data.vDown[i] = data.vDown[i + 1]; + } else { + data.vDown[i] = data.vDown[i - 1] + 1; + } + + int x = data.vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (data.vUp[i - delta] <= data.vDown[i]) { + return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); + } + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { + data.vUp[i] = data.vUp[i + 1] - 1; + } else { + data.vUp[i] = data.vUp[i - 1]; + } + + int x = data.vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d) { + if (data.vUp[i] <= data.vDown[i + delta]) { + return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + + // According to Myers, this cannot happen + throw new IllegalStateException("could not find a diff path"); + } + + private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + private class DiffData { + + final int size; + final int[] vDown; + final int[] vUp; + final List script; + final List source; + final List target; + + public DiffData(List source, List target) { + this.source = source; + this.target = target; + size = source.size() + target.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + script = new ArrayList<>(); + } + } + + private class Snake { + + final int start; + final int end; + final int diag; + + public Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI create() { + return new MyersDiffWithLinearSpace<>(); + } + + @Override + public DiffAlgorithmI create(BiPredicate equalizer) { + return new MyersDiffWithLinearSpace<>(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java index fe8fd03a..5504e817 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java @@ -22,89 +22,89 @@ */ public final class PathNode { - /** - * Position in the original sequence. - */ - public final int i; - /** - * Position in the revised sequence. - */ - public final int j; - /** - * The previous node in the path. - */ - public final PathNode prev; + /** + * Position in the original sequence. + */ + public final int i; + /** + * Position in the revised sequence. + */ + public final int j; + /** + * The previous node in the path. + */ + public final PathNode prev; - public final boolean snake; + public final boolean snake; - public final boolean bootstrap; + public final boolean bootstrap; - /** - * Concatenates a new path node with an existing diffpath. - * - * @param i The position in the original sequence for the new node. - * @param j The position in the revised sequence for the new node. - * @param prev The previous node in the path. - */ - public PathNode(int i, int j, boolean snake, boolean bootstrap, PathNode prev) { - this.i = i; - this.j = j; - this.bootstrap = bootstrap; - if (snake) { - this.prev = prev; - } else { - this.prev = prev == null ? null : prev.previousSnake(); - } - this.snake = snake; - } + /** + * Concatenates a new path node with an existing diffpath. + * + * @param i The position in the original sequence for the new node. + * @param j The position in the revised sequence for the new node. + * @param prev The previous node in the path. + */ + public PathNode(int i, int j, boolean snake, boolean bootstrap, PathNode prev) { + this.i = i; + this.j = j; + this.bootstrap = bootstrap; + if (snake) { + this.prev = prev; + } else { + this.prev = prev == null ? null : prev.previousSnake(); + } + this.snake = snake; + } - public boolean isSnake() { - return snake; - } + public boolean isSnake() { + return snake; + } - /** - * Is this a bootstrap node? - *

- * In bottstrap nodes one of the two corrdinates is less than zero. - * - * @return tru if this is a bootstrap node. - */ - public boolean isBootstrap() { - return bootstrap; - } + /** + * Is this a bootstrap node? + *

+ * In bottstrap nodes one of the two corrdinates is less than zero. + * + * @return tru if this is a bootstrap node. + */ + public boolean isBootstrap() { + return bootstrap; + } - /** - * Skips sequences of {@link PathNode PathNodes} until a snake or bootstrap node is found, or the end of the - * path is reached. - * - * @return The next first {@link PathNode} or bootstrap node in the path, or null if none found. - */ - public final PathNode previousSnake() { - if (isBootstrap()) { - return null; - } - if (!isSnake() && prev != null) { - return prev.previousSnake(); - } - return this; - } + /** + * Skips sequences of {@link PathNode PathNodes} until a snake or bootstrap node is found, or the end of the + * path is reached. + * + * @return The next first {@link PathNode} or bootstrap node in the path, or null if none found. + */ + public final PathNode previousSnake() { + if (isBootstrap()) { + return null; + } + if (!isSnake() && prev != null) { + return prev.previousSnake(); + } + return this; + } - /** - * {@inheritDoc} - */ - @Override - public String toString() { - StringBuilder buf = new StringBuilder("["); - PathNode node = this; - while (node != null) { - buf.append("("); - buf.append(node.i); - buf.append(","); - buf.append(node.j); - buf.append(")"); - node = node.prev; - } - buf.append("]"); - return buf.toString(); - } + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder("["); + PathNode node = this; + while (node != null) { + buf.append("("); + buf.append(node.i); + buf.append(","); + buf.append(node.j); + buf.append(")"); + node = node.prev; + } + buf.append("]"); + return buf.toString(); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java index f74f62ca..acf231e1 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java @@ -20,97 +20,98 @@ import java.util.Objects; /** - * Abstract delta between a source and a target. + * Abstract delta between a source and a target. * @author Tobias Warneke (t.warneke@gmx.net) */ public abstract class AbstractDelta implements Serializable { - private final Chunk source; - private final Chunk target; - private final DeltaType type; - - public AbstractDelta(DeltaType type, Chunk source, Chunk target) { - Objects.requireNonNull(source); - Objects.requireNonNull(target); - Objects.requireNonNull(type); - this.type = type; - this.source = source; - this.target = target; - } + private final Chunk source; + private final Chunk target; + private final DeltaType type; - public Chunk getSource() { - return source; - } + public AbstractDelta(DeltaType type, Chunk source, Chunk target) { + Objects.requireNonNull(source); + Objects.requireNonNull(target); + Objects.requireNonNull(type); + this.type = type; + this.source = source; + this.target = target; + } - public Chunk getTarget() { - return target; - } + public Chunk getSource() { + return source; + } - public DeltaType getType() { - return type; - } - - /** - * Verify the chunk of this delta, to fit the target. - * @param target - * @throws PatchFailedException - */ - protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException { - return getSource().verifyChunk(target); - } - - protected VerifyChunk verifyAndApplyTo(List target) throws PatchFailedException { - final VerifyChunk verify = verifyChunkToFitTarget(target); - if (verify == VerifyChunk.OK) { - applyTo(target); - } - return verify; - } - - protected abstract void applyTo(List target) throws PatchFailedException; - - protected abstract void restore(List target); - - /** - * Apply patch fuzzy. - * - * @param target the list this patch will be applied to - * @param fuzz the number of elements to ignore before/after the patched elements - * @param position the position this patch will be applied to. ignores {@code source.getPosition()} - * @see Description of Fuzzy Patch for more information. - */ - @SuppressWarnings("RedundantThrows") - protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { - throw new UnsupportedOperationException(this.getClass().getSimpleName() + " does not supports applying patch fuzzy"); - } + public Chunk getTarget() { + return target; + } - /** - * Create a new delta of the actual instance with customized chunk data. - */ - public abstract AbstractDelta withChunks(Chunk original, Chunk revised); + public DeltaType getType() { + return type; + } - @Override - public int hashCode() { - return Objects.hash(this.source, this.target, this.type); - } + /** + * Verify the chunk of this delta, to fit the target. + * @param target + * @throws PatchFailedException + */ + protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException { + return getSource().verifyChunk(target); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AbstractDelta other = (AbstractDelta) obj; - if (!Objects.equals(this.source, other.source)) { - return false; - } - if (!Objects.equals(this.target, other.target)) { - return false; - } - return this.type == other.type; - } + protected VerifyChunk verifyAndApplyTo(List target) throws PatchFailedException { + final VerifyChunk verify = verifyChunkToFitTarget(target); + if (verify == VerifyChunk.OK) { + applyTo(target); + } + return verify; + } + + protected abstract void applyTo(List target) throws PatchFailedException; + + protected abstract void restore(List target); + + /** + * Apply patch fuzzy. + * + * @param target the list this patch will be applied to + * @param fuzz the number of elements to ignore before/after the patched elements + * @param position the position this patch will be applied to. ignores {@code source.getPosition()} + * @see Description of Fuzzy Patch for more information. + */ + @SuppressWarnings("RedundantThrows") + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + throw new UnsupportedOperationException( + this.getClass().getSimpleName() + " does not supports applying patch fuzzy"); + } + + /** + * Create a new delta of the actual instance with customized chunk data. + */ + public abstract AbstractDelta withChunks(Chunk original, Chunk revised); + + @Override + public int hashCode() { + return Objects.hash(this.source, this.target, this.type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractDelta other = (AbstractDelta) obj; + if (!Objects.equals(this.source, other.source)) { + return false; + } + if (!Objects.equals(this.target, other.target)) { + return false; + } + return this.type == other.type; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java index 376fd625..f82f13ac 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java @@ -26,67 +26,67 @@ */ public final class ChangeDelta extends AbstractDelta { - /** - * Creates a change delta with the two given chunks. - * - * @param source The source chunk. Must not be {@code null}. - * @param target The target chunk. Must not be {@code null}. - */ - public ChangeDelta(Chunk source, Chunk target) { - super(DeltaType.CHANGE, source, target); - Objects.requireNonNull(source, "source must not be null"); - Objects.requireNonNull(target, "target must not be null"); - } + /** + * Creates a change delta with the two given chunks. + * + * @param source The source chunk. Must not be {@code null}. + * @param target The target chunk. Must not be {@code null}. + */ + public ChangeDelta(Chunk source, Chunk target) { + super(DeltaType.CHANGE, source, target); + Objects.requireNonNull(source, "source must not be null"); + Objects.requireNonNull(target, "target must not be null"); + } - @Override - protected void applyTo(List target) throws PatchFailedException { - int position = getSource().getPosition(); - int size = getSource().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - int i = 0; - for (T line : getTarget().getLines()) { - target.add(position + i, line); - i++; - } - } + @Override + protected void applyTo(List target) throws PatchFailedException { + int position = getSource().getPosition(); + int size = getSource().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + int i = 0; + for (T line : getTarget().getLines()) { + target.add(position + i, line); + i++; + } + } - @Override - protected void restore(List target) { - int position = getTarget().getPosition(); - int size = getTarget().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - int i = 0; - for (T line : getSource().getLines()) { - target.add(position + i, line); - i++; - } - } + @Override + protected void restore(List target) { + int position = getTarget().getPosition(); + int size = getTarget().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + int i = 0; + for (T line : getSource().getLines()) { + target.add(position + i, line); + i++; + } + } - protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { - int size = getSource().size(); - for (int i = fuzz; i < size - fuzz; i++) { - target.remove(position + fuzz); - } + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + int size = getSource().size(); + for (int i = fuzz; i < size - fuzz; i++) { + target.remove(position + fuzz); + } - int i = fuzz; - for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) { - target.add(position + i, line); - i++; - } - } + int i = fuzz; + for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) { + target.add(position + i, line); + i++; + } + } - @Override - public String toString() { - return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " - + getSource().getLines() + " to " + getTarget().getLines() + "]"; - } + @Override + public String toString() { + return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + " to " + getTarget().getLines() + "]"; + } - @Override - public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new ChangeDelta(original, revised); - } + @Override + public AbstractDelta withChunks(Chunk original, Chunk revised) { + return new ChangeDelta(original, revised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java index 50054074..b5b2f312 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java @@ -37,159 +37,158 @@ */ public final class Chunk implements Serializable { - private final int position; - private List lines; - private final List changePosition; - - /** - * Creates a chunk and saves a copy of affected lines - * - * @param position the start position - * @param lines the affected lines - * @param changePosition the positions of changed lines - */ - public Chunk(int position, List lines, List changePosition) { - this.position = position; - this.lines = new ArrayList<>(lines); - this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null; - } - - /** - * Creates a chunk and saves a copy of affected lines - * - * @param position the start position - * @param lines the affected lines - */ - public Chunk(int position, List lines) { - this(position, lines, null); - } - - /** - * Creates a chunk and saves a copy of affected lines - * - * @param position the start position - * @param lines the affected lines - * @param changePosition the positions of changed lines - */ - public Chunk(int position, T[] lines, List changePosition) { - this.position = position; - this.lines = Arrays.asList(lines); - this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null; - } - - /** - * Creates a chunk and saves a copy of affected lines - * - * @param position the start position - * @param lines the affected lines - */ - public Chunk(int position, T[] lines) { - this(position, lines, null); - } - - /** - * Verifies that this chunk's saved text matches the corresponding text in - * the given sequence. - * - * @param target the sequence to verify against. - * @throws com.github.difflib.patch.PatchFailedException - */ - public VerifyChunk verifyChunk(List target) throws PatchFailedException { - return verifyChunk(target, 0, getPosition()); - } - - /** - * Verifies that this chunk's saved text matches the corresponding text in - * the given sequence. - * - * @param target the sequence to verify against. - * @param fuzz the count of ignored prefix/suffix - * @param position the position of target - * @throws com.github.difflib.patch.PatchFailedException - */ - public VerifyChunk verifyChunk(List target, int fuzz, int position) throws PatchFailedException { - //noinspection UnnecessaryLocalVariable - int startIndex = fuzz; - int lastIndex = size() - fuzz; - int last = position + size() - 1; - - if (position + fuzz > target.size() || last - fuzz > target.size()) { - return VerifyChunk.POSITION_OUT_OF_TARGET; - } - for (int i = startIndex; i < lastIndex; i++) { - if (!target.get(position + i).equals(lines.get(i))) { - return VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET; - } - } - return VerifyChunk.OK; - } - - /** - * @return the start position of chunk in the text - */ - public int getPosition() { - return position; - } - - public void setLines(List lines) { - this.lines = lines; - } - - /** - * @return the affected lines - */ - public List getLines() { - return lines; - } - - /** - * @return the positions of changed lines of chunk in the text - */ - public List getChangePosition() { - return changePosition; - } - - public int size() { - return lines.size(); - } - - /** - * Returns the index of the last line of the chunk. - */ - public int last() { - return getPosition() + size() - 1; - } - - @Override - public int hashCode() { - return Objects.hash(lines, position, size()); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Chunk other = (Chunk) obj; - if (lines == null) { - if (other.lines != null) { - return false; - } - } else if (!lines.equals(other.lines)) { - return false; - } - return position == other.position; - } - - @Override - public String toString() { - return "[position: " + position + ", size: " + size() + ", lines: " + lines + "]"; - } - + private final int position; + private List lines; + private final List changePosition; + + /** + * Creates a chunk and saves a copy of affected lines + * + * @param position the start position + * @param lines the affected lines + * @param changePosition the positions of changed lines + */ + public Chunk(int position, List lines, List changePosition) { + this.position = position; + this.lines = new ArrayList<>(lines); + this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null; + } + + /** + * Creates a chunk and saves a copy of affected lines + * + * @param position the start position + * @param lines the affected lines + */ + public Chunk(int position, List lines) { + this(position, lines, null); + } + + /** + * Creates a chunk and saves a copy of affected lines + * + * @param position the start position + * @param lines the affected lines + * @param changePosition the positions of changed lines + */ + public Chunk(int position, T[] lines, List changePosition) { + this.position = position; + this.lines = Arrays.asList(lines); + this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null; + } + + /** + * Creates a chunk and saves a copy of affected lines + * + * @param position the start position + * @param lines the affected lines + */ + public Chunk(int position, T[] lines) { + this(position, lines, null); + } + + /** + * Verifies that this chunk's saved text matches the corresponding text in + * the given sequence. + * + * @param target the sequence to verify against. + * @throws com.github.difflib.patch.PatchFailedException + */ + public VerifyChunk verifyChunk(List target) throws PatchFailedException { + return verifyChunk(target, 0, getPosition()); + } + + /** + * Verifies that this chunk's saved text matches the corresponding text in + * the given sequence. + * + * @param target the sequence to verify against. + * @param fuzz the count of ignored prefix/suffix + * @param position the position of target + * @throws com.github.difflib.patch.PatchFailedException + */ + public VerifyChunk verifyChunk(List target, int fuzz, int position) throws PatchFailedException { + //noinspection UnnecessaryLocalVariable + int startIndex = fuzz; + int lastIndex = size() - fuzz; + int last = position + size() - 1; + + if (position + fuzz > target.size() || last - fuzz > target.size()) { + return VerifyChunk.POSITION_OUT_OF_TARGET; + } + for (int i = startIndex; i < lastIndex; i++) { + if (!target.get(position + i).equals(lines.get(i))) { + return VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET; + } + } + return VerifyChunk.OK; + } + + /** + * @return the start position of chunk in the text + */ + public int getPosition() { + return position; + } + + public void setLines(List lines) { + this.lines = lines; + } + + /** + * @return the affected lines + */ + public List getLines() { + return lines; + } + + /** + * @return the positions of changed lines of chunk in the text + */ + public List getChangePosition() { + return changePosition; + } + + public int size() { + return lines.size(); + } + + /** + * Returns the index of the last line of the chunk. + */ + public int last() { + return getPosition() + size() - 1; + } + + @Override + public int hashCode() { + return Objects.hash(lines, position, size()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Chunk other = (Chunk) obj; + if (lines == null) { + if (other.lines != null) { + return false; + } + } else if (!lines.equals(other.lines)) { + return false; + } + return position == other.position; + } + + @Override + public String toString() { + return "[position: " + position + ", size: " + size() + ", lines: " + lines + "]"; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java index 2dfff6a5..3f32ed2d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ConflictOutput.java @@ -8,7 +8,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -29,5 +29,6 @@ @FunctionalInterface public interface ConflictOutput extends Serializable { - public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) throws PatchFailedException; + public void processConflict(VerifyChunk verifyChunk, AbstractDelta delta, List result) + throws PatchFailedException; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java index 890b8575..8a6cda37 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/DeleteDelta.java @@ -25,42 +25,42 @@ */ public final class DeleteDelta extends AbstractDelta { - /** - * Creates a change delta with the two given chunks. - * - * @param original The original chunk. Must not be {@code null}. - * @param revised The original chunk. Must not be {@code null}. - */ - public DeleteDelta(Chunk original, Chunk revised) { - super(DeltaType.DELETE, original, revised); - } + /** + * Creates a change delta with the two given chunks. + * + * @param original The original chunk. Must not be {@code null}. + * @param revised The original chunk. Must not be {@code null}. + */ + public DeleteDelta(Chunk original, Chunk revised) { + super(DeltaType.DELETE, original, revised); + } - @Override - protected void applyTo(List target) throws PatchFailedException { - int position = getSource().getPosition(); - int size = getSource().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - } + @Override + protected void applyTo(List target) throws PatchFailedException { + int position = getSource().getPosition(); + int size = getSource().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + } - @Override - protected void restore(List target) { - int position = this.getTarget().getPosition(); - List lines = this.getSource().getLines(); - for (int i = 0; i < lines.size(); i++) { - target.add(position + i, lines.get(i)); - } - } + @Override + protected void restore(List target) { + int position = this.getTarget().getPosition(); + List lines = this.getSource().getLines(); + for (int i = 0; i < lines.size(); i++) { + target.add(position + i, lines.get(i)); + } + } - @Override - public String toString() { - return "[DeleteDelta, position: " + getSource().getPosition() + ", lines: " - + getSource().getLines() + "]"; - } - - @Override - public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new DeleteDelta(original, revised); - } + @Override + public String toString() { + return "[DeleteDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + "]"; + } + + @Override + public AbstractDelta withChunks(Chunk original, Chunk revised) { + return new DeleteDelta(original, revised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/DeltaType.java b/java-diff-utils/src/main/java/com/github/difflib/patch/DeltaType.java index 666e803a..51405c41 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/DeltaType.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/DeltaType.java @@ -16,35 +16,35 @@ package com.github.difflib.patch; /** - * Specifies the type of the delta. There are three types of modifications from - * the original to get the revised text. - * + * Specifies the type of the delta. There are three types of modifications from + * the original to get the revised text. + * * CHANGE: a block of data of the original is replaced by another block of data. * DELETE: a block of data of the original is removed * INSERT: at a position of the original a block of data is inserted - * - * to be complete there is also - * + * + * to be complete there is also + * * EQUAL: a block of data of original and the revised text is equal - * + * * which is no change at all. * */ public enum DeltaType { - /** - * A change in the original. - */ - CHANGE, - /** - * A delete from the original. - */ - DELETE, - /** - * An insert into the original. - */ - INSERT, - /** - * An do nothing. - */ - EQUAL + /** + * A change in the original. + */ + CHANGE, + /** + * A delete from the original. + */ + DELETE, + /** + * An insert into the original. + */ + INSERT, + /** + * An do nothing. + */ + EQUAL } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/DiffException.java b/java-diff-utils/src/main/java/com/github/difflib/patch/DiffException.java index da01d621..005d7a5b 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/DiffException.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/DiffException.java @@ -22,12 +22,11 @@ */ public class DiffException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public DiffException() { - } + public DiffException() {} - public DiffException(String msg) { - super(msg); - } + public DiffException(String msg) { + super(msg); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java index 17fdadc6..64f05685 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java @@ -23,34 +23,32 @@ */ public class EqualDelta extends AbstractDelta { - public EqualDelta(Chunk source, Chunk target) { - super(DeltaType.EQUAL, source, target); - } - - @Override - protected void applyTo(List target) throws PatchFailedException { - } - - @Override - protected void restore(List target) { - } - - /** - * {@inheritDoc} - */ - @Override - protected void applyFuzzyToAt(List target, int fuzz, int delta) { - // equals so no operations - } - - @Override - public String toString() { - return "[EqualDelta, position: " + getSource().getPosition() + ", lines: " - + getSource().getLines() + "]"; - } - - @Override - public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new EqualDelta(original, revised); - } + public EqualDelta(Chunk source, Chunk target) { + super(DeltaType.EQUAL, source, target); + } + + @Override + protected void applyTo(List target) throws PatchFailedException {} + + @Override + protected void restore(List target) {} + + /** + * {@inheritDoc} + */ + @Override + protected void applyFuzzyToAt(List target, int fuzz, int delta) { + // equals so no operations + } + + @Override + public String toString() { + return "[EqualDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + "]"; + } + + @Override + public AbstractDelta withChunks(Chunk original, Chunk revised) { + return new EqualDelta(original, revised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java index 6cff9103..65d447a8 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/InsertDelta.java @@ -25,42 +25,42 @@ */ public final class InsertDelta extends AbstractDelta { - /** - * Creates an insert delta with the two given chunks. - * - * @param original The original chunk. Must not be {@code null}. - * @param revised The original chunk. Must not be {@code null}. - */ - public InsertDelta(Chunk original, Chunk revised) { - super(DeltaType.INSERT, original, revised); - } + /** + * Creates an insert delta with the two given chunks. + * + * @param original The original chunk. Must not be {@code null}. + * @param revised The original chunk. Must not be {@code null}. + */ + public InsertDelta(Chunk original, Chunk revised) { + super(DeltaType.INSERT, original, revised); + } - @Override - protected void applyTo(List target) throws PatchFailedException { - int position = this.getSource().getPosition(); - List lines = this.getTarget().getLines(); - for (int i = 0; i < lines.size(); i++) { - target.add(position + i, lines.get(i)); - } - } + @Override + protected void applyTo(List target) throws PatchFailedException { + int position = this.getSource().getPosition(); + List lines = this.getTarget().getLines(); + for (int i = 0; i < lines.size(); i++) { + target.add(position + i, lines.get(i)); + } + } - @Override - protected void restore(List target) { - int position = getTarget().getPosition(); - int size = getTarget().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - } + @Override + protected void restore(List target) { + int position = getTarget().getPosition(); + int size = getTarget().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + } - @Override - public String toString() { - return "[InsertDelta, position: " + getSource().getPosition() - + ", lines: " + getTarget().getLines() + "]"; - } - - @Override - public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new InsertDelta(original, revised); - } + @Override + public String toString() { + return "[InsertDelta, position: " + getSource().getPosition() + ", lines: " + + getTarget().getLines() + "]"; + } + + @Override + public AbstractDelta withChunks(Chunk original, Chunk revised) { + return new InsertDelta(original, revised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java index 305f2de7..6a54d820 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -8,7 +8,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -20,8 +20,8 @@ package com.github.difflib.patch; import static java.util.Comparator.comparing; -import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.Change; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -37,308 +37,313 @@ */ public final class Patch implements Serializable { - private final List> deltas; - - public Patch() { - this(10); - } - - public Patch(int estimatedPatchSize) { - deltas = new ArrayList<>(estimatedPatchSize); - } - - /** - * Creates a new list, the patch is being applied to. - * - * @param target The list to apply the changes to. - * @return A new list containing the applied patch. - * @throws PatchFailedException if the patch cannot be applied - */ - public List applyTo(List target) throws PatchFailedException { - List result = new ArrayList<>(target); - applyToExisting(result); - return result; - } - - /** - * Applies the patch to the supplied list. - * - * @param target The list to apply the changes to. This list has to be modifiable, - * otherwise exceptions may be thrown, depending on the used type of list. - * @throws PatchFailedException if the patch cannot be applied - * @throws RuntimeException (or similar) if the list is not modifiable. - */ - public void applyToExisting(List target) throws PatchFailedException { - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - VerifyChunk valid = delta.verifyAndApplyTo(target); - if (valid != VerifyChunk.OK) { - conflictOutput.processConflict(valid, delta, target); - } - } - } - - private static class PatchApplyingContext { - public final List result; - public final int maxFuzz; - - // the position last patch applied to. - public int lastPatchEnd = -1; - - ///// passing values from find to apply - public int currentFuzz = 0; - - public int defaultPosition; - public boolean beforeOutRange = false; - public boolean afterOutRange = false; - - private PatchApplyingContext(List result, int maxFuzz) { - this.result = result; - this.maxFuzz = maxFuzz; - } - } - - public List applyFuzzy(List target, int maxFuzz) throws PatchFailedException { - PatchApplyingContext ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz); - - // the difference between patch's position and actually applied position - int lastPatchDelta = 0; - - for (AbstractDelta delta : getDeltas()) { - ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta; - int patchPosition = findPositionFuzzy(ctx, delta); - if (0 <= patchPosition) { - delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition); - lastPatchDelta = patchPosition - delta.getSource().getPosition(); - ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta; - } else { - conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result); - } - } - - return ctx.result; - } - - // negative for not found - private int findPositionFuzzy(PatchApplyingContext ctx, AbstractDelta delta) throws PatchFailedException { - for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) { - ctx.currentFuzz = fuzz; - int foundPosition = findPositionWithFuzz(ctx, delta, fuzz); - if (foundPosition >= 0) { - return foundPosition; - } - } - return -1; - } - - // negative for not found - private int findPositionWithFuzz(PatchApplyingContext ctx, AbstractDelta delta, int fuzz) throws PatchFailedException { - if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) { - return ctx.defaultPosition; - } - - ctx.beforeOutRange = false; - ctx.afterOutRange = false; - - // moreDelta >= 0: just for overflow guard, not a normal condition - //noinspection OverflowingLoopIndex - for (int moreDelta = 0; moreDelta >= 0; moreDelta++) { - int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta); - if (pos >= 0) { - return pos; - } - if (ctx.beforeOutRange && ctx.afterOutRange) { - break; - } - } - - return -1; - } - - // negative for not found - private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext ctx, AbstractDelta delta, int fuzz, int moreDelta) throws PatchFailedException { - // range check: can't apply before end of last patch - if (!ctx.beforeOutRange) { - int beginAt = ctx.defaultPosition - moreDelta + fuzz; - // We can't apply patch before end of last patch. - if (beginAt <= ctx.lastPatchEnd) { - ctx.beforeOutRange = true; - } - } - // range check: can't apply after end of result - if (!ctx.afterOutRange) { - int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz; - // We can't apply patch before end of last patch. - if (ctx.result.size() < beginAt) { - ctx.afterOutRange = true; - } - } - - if (!ctx.beforeOutRange) { - VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta); - if (before == VerifyChunk.OK) { - return ctx.defaultPosition - moreDelta; - } - } - if (!ctx.afterOutRange) { - VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta); - if (after == VerifyChunk.OK) { - return ctx.defaultPosition + moreDelta; - } - } - return -1; - } - - /** - * Standard Patch behaviour to throw an exception for pathching conflicts. - */ - public final ConflictOutput CONFLICT_PRODUCES_EXCEPTION = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { - throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString()); - }; - - /** - * Git like merge conflict output. - */ - public static final ConflictOutput CONFLICT_PRODUCES_MERGE_CONFLICT = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { - if (result.size() > delta.getSource().getPosition()) { - List orgData = new ArrayList<>(); - - for (int i = 0; i < delta.getSource().size(); i++) { - orgData.add(result.get(delta.getSource().getPosition())); - result.remove(delta.getSource().getPosition()); - } - - orgData.add(0, "<<<<<< HEAD"); - orgData.add("======"); - orgData.addAll(delta.getSource().getLines()); - orgData.add(">>>>>>> PATCH"); - - result.addAll(delta.getSource().getPosition(), orgData); - - } else { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - }; - - private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; - - /** - * Alter normal conflict output behaviour to e.g. include some conflict - * statements in the result, like git does it. - */ - public Patch withConflictOutput(ConflictOutput conflictOutput) { - this.conflictOutput = conflictOutput; - return this; - } - - /** - * Creates a new list, containing the restored state of the given list. - * Opposite to {@link #applyTo(List)} method. - * - * @param target The list to copy and apply changes to. - * @return A new list, containing the restored state. - */ - public List restore(List target) { - List result = new ArrayList<>(target); - restoreToExisting(result); - return result; - } - - - /** - * Restores all changes within the given list. - * Opposite to {@link #applyToExisting(List)} method. - * - * @param target The list to restore changes in. This list has to be modifiable, - * otherwise exceptions may be thrown, depending on the used type of list. - * @throws RuntimeException (or similar) if the list is not modifiable. - */ - public void restoreToExisting(List target) { - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - delta.restore(target); - } - } - - /** - * Add the given delta to this patch - * - * @param delta the given delta - */ - public void addDelta(AbstractDelta delta) { - deltas.add(delta); - } - - /** - * Get the list of computed deltas - * - * @return the deltas - */ - public List> getDeltas() { - deltas.sort(comparing(d -> d.getSource().getPosition())); - return deltas; - } - - @Override - public String toString() { - return "Patch{" + "deltas=" + deltas + '}'; - } - - public static Patch generate(List original, List revised, List changes) { - return generate(original, revised, changes, false); - } - - private static Chunk buildChunk(int start, int end, List data) { - return new Chunk<>(start, new ArrayList<>(data.subList(start, end))); - } - - public static Patch generate(List original, List revised, List _changes, boolean includeEquals) { - Patch patch = new Patch<>(_changes.size()); - int startOriginal = 0; - int startRevised = 0; - - List changes = _changes; - - if (includeEquals) { - changes = new ArrayList(_changes); - Collections.sort(changes, comparing(d -> d.startOriginal)); - } - - for (Change change : changes) { - - if (includeEquals && startOriginal < change.startOriginal) { - patch.addDelta(new EqualDelta( - buildChunk(startOriginal, change.startOriginal, original), - buildChunk(startRevised, change.startRevised, revised))); - } - - Chunk orgChunk = buildChunk(change.startOriginal, change.endOriginal, original); - Chunk revChunk = buildChunk(change.startRevised, change.endRevised, revised); - switch (change.deltaType) { - case DELETE: - patch.addDelta(new DeleteDelta<>(orgChunk, revChunk)); - break; - case INSERT: - patch.addDelta(new InsertDelta<>(orgChunk, revChunk)); - break; - case CHANGE: - patch.addDelta(new ChangeDelta<>(orgChunk, revChunk)); - break; - default: - } - - startOriginal = change.endOriginal; - startRevised = change.endRevised; - } - - if (includeEquals && startOriginal < original.size()) { - patch.addDelta(new EqualDelta( - buildChunk(startOriginal, original.size(), original), - buildChunk(startRevised, revised.size(), revised))); - } - - return patch; - } + private final List> deltas; + + public Patch() { + this(10); + } + + public Patch(int estimatedPatchSize) { + deltas = new ArrayList<>(estimatedPatchSize); + } + + /** + * Creates a new list, the patch is being applied to. + * + * @param target The list to apply the changes to. + * @return A new list containing the applied patch. + * @throws PatchFailedException if the patch cannot be applied + */ + public List applyTo(List target) throws PatchFailedException { + List result = new ArrayList<>(target); + applyToExisting(result); + return result; + } + + /** + * Applies the patch to the supplied list. + * + * @param target The list to apply the changes to. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws PatchFailedException if the patch cannot be applied + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void applyToExisting(List target) throws PatchFailedException { + ListIterator> it = getDeltas().listIterator(deltas.size()); + while (it.hasPrevious()) { + AbstractDelta delta = it.previous(); + VerifyChunk valid = delta.verifyAndApplyTo(target); + if (valid != VerifyChunk.OK) { + conflictOutput.processConflict(valid, delta, target); + } + } + } + + private static class PatchApplyingContext { + public final List result; + public final int maxFuzz; + + // the position last patch applied to. + public int lastPatchEnd = -1; + + ///// passing values from find to apply + public int currentFuzz = 0; + + public int defaultPosition; + public boolean beforeOutRange = false; + public boolean afterOutRange = false; + + private PatchApplyingContext(List result, int maxFuzz) { + this.result = result; + this.maxFuzz = maxFuzz; + } + } + + public List applyFuzzy(List target, int maxFuzz) throws PatchFailedException { + PatchApplyingContext ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz); + + // the difference between patch's position and actually applied position + int lastPatchDelta = 0; + + for (AbstractDelta delta : getDeltas()) { + ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta; + int patchPosition = findPositionFuzzy(ctx, delta); + if (0 <= patchPosition) { + delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition); + lastPatchDelta = patchPosition - delta.getSource().getPosition(); + ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta; + } else { + conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result); + } + } + + return ctx.result; + } + + // negative for not found + private int findPositionFuzzy(PatchApplyingContext ctx, AbstractDelta delta) throws PatchFailedException { + for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) { + ctx.currentFuzz = fuzz; + int foundPosition = findPositionWithFuzz(ctx, delta, fuzz); + if (foundPosition >= 0) { + return foundPosition; + } + } + return -1; + } + + // negative for not found + private int findPositionWithFuzz(PatchApplyingContext ctx, AbstractDelta delta, int fuzz) + throws PatchFailedException { + if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) { + return ctx.defaultPosition; + } + + ctx.beforeOutRange = false; + ctx.afterOutRange = false; + + // moreDelta >= 0: just for overflow guard, not a normal condition + //noinspection OverflowingLoopIndex + for (int moreDelta = 0; moreDelta >= 0; moreDelta++) { + int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta); + if (pos >= 0) { + return pos; + } + if (ctx.beforeOutRange && ctx.afterOutRange) { + break; + } + } + + return -1; + } + + // negative for not found + private int findPositionWithFuzzAndMoreDelta( + PatchApplyingContext ctx, AbstractDelta delta, int fuzz, int moreDelta) throws PatchFailedException { + // range check: can't apply before end of last patch + if (!ctx.beforeOutRange) { + int beginAt = ctx.defaultPosition - moreDelta + fuzz; + // We can't apply patch before end of last patch. + if (beginAt <= ctx.lastPatchEnd) { + ctx.beforeOutRange = true; + } + } + // range check: can't apply after end of result + if (!ctx.afterOutRange) { + int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz; + // We can't apply patch before end of last patch. + if (ctx.result.size() < beginAt) { + ctx.afterOutRange = true; + } + } + + if (!ctx.beforeOutRange) { + VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta); + if (before == VerifyChunk.OK) { + return ctx.defaultPosition - moreDelta; + } + } + if (!ctx.afterOutRange) { + VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta); + if (after == VerifyChunk.OK) { + return ctx.defaultPosition + moreDelta; + } + } + return -1; + } + + /** + * Standard Patch behaviour to throw an exception for pathching conflicts. + */ + public final ConflictOutput CONFLICT_PRODUCES_EXCEPTION = + (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { + throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString()); + }; + + /** + * Git like merge conflict output. + */ + public static final ConflictOutput CONFLICT_PRODUCES_MERGE_CONFLICT = + (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { + if (result.size() > delta.getSource().getPosition()) { + List orgData = new ArrayList<>(); + + for (int i = 0; i < delta.getSource().size(); i++) { + orgData.add(result.get(delta.getSource().getPosition())); + result.remove(delta.getSource().getPosition()); + } + + orgData.add(0, "<<<<<< HEAD"); + orgData.add("======"); + orgData.addAll(delta.getSource().getLines()); + orgData.add(">>>>>>> PATCH"); + + result.addAll(delta.getSource().getPosition(), orgData); + + } else { + throw new UnsupportedOperationException( + "Not supported yet."); // To change body of generated methods, choose Tools | Templates. + } + }; + + private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; + + /** + * Alter normal conflict output behaviour to e.g. include some conflict + * statements in the result, like git does it. + */ + public Patch withConflictOutput(ConflictOutput conflictOutput) { + this.conflictOutput = conflictOutput; + return this; + } + + /** + * Creates a new list, containing the restored state of the given list. + * Opposite to {@link #applyTo(List)} method. + * + * @param target The list to copy and apply changes to. + * @return A new list, containing the restored state. + */ + public List restore(List target) { + List result = new ArrayList<>(target); + restoreToExisting(result); + return result; + } + + /** + * Restores all changes within the given list. + * Opposite to {@link #applyToExisting(List)} method. + * + * @param target The list to restore changes in. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void restoreToExisting(List target) { + ListIterator> it = getDeltas().listIterator(deltas.size()); + while (it.hasPrevious()) { + AbstractDelta delta = it.previous(); + delta.restore(target); + } + } + + /** + * Add the given delta to this patch + * + * @param delta the given delta + */ + public void addDelta(AbstractDelta delta) { + deltas.add(delta); + } + + /** + * Get the list of computed deltas + * + * @return the deltas + */ + public List> getDeltas() { + deltas.sort(comparing(d -> d.getSource().getPosition())); + return deltas; + } + + @Override + public String toString() { + return "Patch{" + "deltas=" + deltas + '}'; + } + + public static Patch generate(List original, List revised, List changes) { + return generate(original, revised, changes, false); + } + + private static Chunk buildChunk(int start, int end, List data) { + return new Chunk<>(start, new ArrayList<>(data.subList(start, end))); + } + + public static Patch generate( + List original, List revised, List _changes, boolean includeEquals) { + Patch patch = new Patch<>(_changes.size()); + int startOriginal = 0; + int startRevised = 0; + + List changes = _changes; + + if (includeEquals) { + changes = new ArrayList(_changes); + Collections.sort(changes, comparing(d -> d.startOriginal)); + } + + for (Change change : changes) { + + if (includeEquals && startOriginal < change.startOriginal) { + patch.addDelta(new EqualDelta( + buildChunk(startOriginal, change.startOriginal, original), + buildChunk(startRevised, change.startRevised, revised))); + } + + Chunk orgChunk = buildChunk(change.startOriginal, change.endOriginal, original); + Chunk revChunk = buildChunk(change.startRevised, change.endRevised, revised); + switch (change.deltaType) { + case DELETE: + patch.addDelta(new DeleteDelta<>(orgChunk, revChunk)); + break; + case INSERT: + patch.addDelta(new InsertDelta<>(orgChunk, revChunk)); + break; + case CHANGE: + patch.addDelta(new ChangeDelta<>(orgChunk, revChunk)); + break; + default: + } + + startOriginal = change.endOriginal; + startRevised = change.endRevised; + } + + if (includeEquals && startOriginal < original.size()) { + patch.addDelta(new EqualDelta( + buildChunk(startOriginal, original.size(), original), + buildChunk(startRevised, revised.size(), revised))); + } + + return patch; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java b/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java index 7521c892..c6673573 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java @@ -22,12 +22,11 @@ */ public class PatchFailedException extends DiffException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public PatchFailedException() { - } + public PatchFailedException() {} - public PatchFailedException(String msg) { - super(msg); - } + public PatchFailedException(String msg) { + super(msg); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java index 076f633a..c8839b91 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java @@ -20,7 +20,7 @@ * @author tw */ public enum VerifyChunk { - OK, - POSITION_OUT_OF_TARGET, - CONTENT_DOES_NOT_MATCH_TARGET + OK, + POSITION_OUT_OF_TARGET, + CONTENT_DOES_NOT_MATCH_TARGET } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java index 95908393..6ba79beb 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java @@ -25,91 +25,94 @@ */ public final class DiffRow implements Serializable { - private Tag tag; - private final String oldLine; - private final String newLine; + private Tag tag; + private final String oldLine; + private final String newLine; - public DiffRow(Tag tag, String oldLine, String newLine) { - this.tag = tag; - this.oldLine = oldLine; - this.newLine = newLine; - } + public DiffRow(Tag tag, String oldLine, String newLine) { + this.tag = tag; + this.oldLine = oldLine; + this.newLine = newLine; + } - public enum Tag { - INSERT, DELETE, CHANGE, EQUAL - } + public enum Tag { + INSERT, + DELETE, + CHANGE, + EQUAL + } - /** - * @return the tag - */ - public Tag getTag() { - return tag; - } + /** + * @return the tag + */ + public Tag getTag() { + return tag; + } - /** - * @param tag the tag to set - */ - public void setTag(Tag tag) { - this.tag = tag; - } + /** + * @param tag the tag to set + */ + public void setTag(Tag tag) { + this.tag = tag; + } - /** - * @return the oldLine - */ - public String getOldLine() { - return oldLine; - } + /** + * @return the oldLine + */ + public String getOldLine() { + return oldLine; + } - /** - * @return the newLine - */ - public String getNewLine() { - return newLine; - } + /** + * @return the newLine + */ + public String getNewLine() { + return newLine; + } - @Override - public int hashCode() { - return Objects.hash(newLine, oldLine, tag); - } + @Override + public int hashCode() { + return Objects.hash(newLine, oldLine, tag); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - DiffRow other = (DiffRow) obj; - if (newLine == null) { - if (other.newLine != null) { - return false; - } - } else if (!newLine.equals(other.newLine)) { - return false; - } - if (oldLine == null) { - if (other.oldLine != null) { - return false; - } - } else if (!oldLine.equals(other.oldLine)) { - return false; - } - if (tag == null) { - if (other.tag != null) { - return false; - } - } else if (!tag.equals(other.tag)) { - return false; - } - return true; - } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DiffRow other = (DiffRow) obj; + if (newLine == null) { + if (other.newLine != null) { + return false; + } + } else if (!newLine.equals(other.newLine)) { + return false; + } + if (oldLine == null) { + if (other.oldLine != null) { + return false; + } + } else if (!oldLine.equals(other.oldLine)) { + return false; + } + if (tag == null) { + if (other.tag != null) { + return false; + } + } else if (!tag.equals(other.tag)) { + return false; + } + return true; + } - @Override - public String toString() { - return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]"; - } + @Override + public String toString() { + return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]"; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 9ec50a84..e7e09ac5 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -15,6 +15,8 @@ */ package com.github.difflib.text; +import static java.util.stream.Collectors.toList; + import com.github.difflib.DiffUtils; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; @@ -32,7 +34,6 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.util.stream.Collectors.toList; /** * This class for generating DiffRows for side-by-sidy view. You can customize @@ -51,656 +52,693 @@ */ public final class DiffRowGenerator { - public static final BiPredicate DEFAULT_EQUALIZER = Object::equals; - - public static final BiPredicate IGNORE_WHITESPACE_EQUALIZER = (original, revised) - -> adjustWhitespace(original).equals(adjustWhitespace(revised)); - - public static final Function LINE_NORMALIZER_FOR_HTML = StringUtils::normalize; - - /** - * Splitting lines by character to achieve char by char diff checking. - */ - public static final Function> SPLITTER_BY_CHARACTER = line -> { - List list = new ArrayList<>(line.length()); - for (Character character : line.toCharArray()) { - list.add(character.toString()); - } - return list; - }; - - public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#<>;:&\\']+"); - - /** - * Splitting lines by word to achieve word by word diff checking. - */ - public static final Function> SPLITTER_BY_WORD = line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN); - public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - - public static final Function>> DEFAULT_INLINE_DELTA_MERGER = InlineDeltaMergeInfo::getDeltas; - - /** - * Merge diffs which are separated by equalities consisting of whitespace only. - */ - public static final Function>> WHITESPACE_EQUALITIES_MERGER = deltaMergeInfo -> DeltaMergeUtils - .mergeInlineDeltas(deltaMergeInfo, equalities -> equalities.stream().allMatch(s -> s==null || s.replaceAll("\\s+", "").equals(""))); - - public static Builder create() { - return new Builder(); - } - - private static String adjustWhitespace(String raw) { - return WHITESPACE_PATTERN.matcher(raw.trim()).replaceAll(" "); - } - - protected final static List splitStringPreserveDelimiter(String str, Pattern SPLIT_PATTERN) { - List list = new ArrayList<>(); - if (str != null) { - Matcher matcher = SPLIT_PATTERN.matcher(str); - int pos = 0; - while (matcher.find()) { - if (pos < matcher.start()) { - list.add(str.substring(pos, matcher.start())); - } - list.add(matcher.group()); - pos = matcher.end(); - } - if (pos < str.length()) { - list.add(str.substring(pos)); - } - } - return list; - } - - /** - * 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 tagGenerator the tag generator - */ - static void wrapInTag(List sequence, int startPosition, - int endPosition, Tag tag, BiFunction tagGenerator, - Function processDiffs, boolean replaceLinefeedWithSpace) { - int endPos = endPosition; - - while (endPos >= startPosition) { - - //search position for end tag - while (endPos > startPosition) { - if (!"\n".equals(sequence.get(endPos - 1))) { - break; - } else if (replaceLinefeedWithSpace) { - sequence.set(endPos - 1, " "); - break; - } - endPos--; - } - - if (endPos == startPosition) { - break; - } - - sequence.add(endPos, tagGenerator.apply(tag, false)); - if (processDiffs != null) { - sequence.set(endPos - 1, - processDiffs.apply(sequence.get(endPos - 1))); - } - endPos--; - - //search position for end tag - while (endPos > startPosition) { - if ("\n".equals(sequence.get(endPos - 1))) { - if (replaceLinefeedWithSpace) { - sequence.set(endPos - 1, " "); - } else { - break; - } - } - if (processDiffs != null) { - sequence.set(endPos - 1, - processDiffs.apply(sequence.get(endPos - 1))); - } - endPos--; - } - - sequence.add(endPos, tagGenerator.apply(tag, true)); - endPos--; - } - } - - private final int columnWidth; - private final BiPredicate equalizer; - private final boolean ignoreWhiteSpaces; - private final Function> inlineDiffSplitter; - private final boolean mergeOriginalRevised; - private final BiFunction newTag; - private final BiFunction oldTag; - private final boolean reportLinesUnchanged; - private final Function lineNormalizer; - private final Function processDiffs; - private final Function>> inlineDeltaMerger; - - private final boolean showInlineDiffs; - private final boolean replaceOriginalLinefeedInChangesWithSpaces; - private final boolean decompressDeltas; - - private DiffRowGenerator(Builder builder) { - showInlineDiffs = builder.showInlineDiffs; - ignoreWhiteSpaces = builder.ignoreWhiteSpaces; - oldTag = builder.oldTag; - newTag = builder.newTag; - columnWidth = builder.columnWidth; - mergeOriginalRevised = builder.mergeOriginalRevised; - inlineDiffSplitter = builder.inlineDiffSplitter; - decompressDeltas = builder.decompressDeltas; - - if (builder.equalizer != null) { - equalizer = builder.equalizer; - } else { - equalizer = ignoreWhiteSpaces ? IGNORE_WHITESPACE_EQUALIZER : DEFAULT_EQUALIZER; - } - - reportLinesUnchanged = builder.reportLinesUnchanged; - lineNormalizer = builder.lineNormalizer; - processDiffs = builder.processDiffs; - inlineDeltaMerger = builder.inlineDeltaMerger; - - replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; - - Objects.requireNonNull(inlineDiffSplitter); - Objects.requireNonNull(lineNormalizer); - Objects.requireNonNull(inlineDeltaMerger); - } - - /** - * 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 - * @return the DiffRows between original and revised texts - */ - public List generateDiffRows(List original, List revised) { - return generateDiffRows(original, DiffUtils.diff(original, revised, equalizer)); - } - - /** - * 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 patch the given patch - * @return the DiffRows between original and revised texts - */ - public List generateDiffRows(final List original, Patch patch) { - List diffRows = new ArrayList<>(); - int endPos = 0; - final List> deltaList = patch.getDeltas(); - - if (decompressDeltas) { - for (AbstractDelta originalDelta : deltaList) { - for (AbstractDelta delta : decompressDeltas(originalDelta)) { - endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); - } - } - } else { - for (AbstractDelta delta : deltaList) { - endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); - } - } - - // Copy the final matching chunk if any. - for (String line : original.subList(endPos, original.size())) { - diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); - } - return diffRows; - } - - /** - * Transforms one patch delta into a DiffRow object. - */ - private int transformDeltaIntoDiffRow(final List original, int endPos, List diffRows, AbstractDelta delta) { - Chunk orig = delta.getSource(); - Chunk rev = delta.getTarget(); - - for (String line : original.subList(endPos, orig.getPosition())) { - diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); - } - - switch (delta.getType()) { - case INSERT: - for (String line : rev.getLines()) { - diffRows.add(buildDiffRow(Tag.INSERT, "", line)); - } - break; - case DELETE: - for (String line : orig.getLines()) { - diffRows.add(buildDiffRow(Tag.DELETE, line, "")); - } - break; - default: - if (showInlineDiffs) { - diffRows.addAll(generateInlineDiffs(delta)); - } else { - for (int j = 0; j < Math.max(orig.size(), rev.size()); j++) { - diffRows.add(buildDiffRow(Tag.CHANGE, - orig.getLines().size() > j ? orig.getLines().get(j) : "", - rev.getLines().size() > j ? rev.getLines().get(j) : "")); - } - } - } - - return orig.last() + 1; - } - - /** - * Decompresses ChangeDeltas with different source and target size to a - * ChangeDelta with same size and a following InsertDelta or DeleteDelta. - * With this problems of building DiffRows getting smaller. - * - * @param deltaList - */ - private List> decompressDeltas(AbstractDelta delta) { - if (delta.getType() == DeltaType.CHANGE && delta.getSource().size() != delta.getTarget().size()) { - List> deltas = new ArrayList<>(); - //System.out.println("decompress this " + delta); - - int minSize = Math.min(delta.getSource().size(), delta.getTarget().size()); - Chunk orig = delta.getSource(); - Chunk rev = delta.getTarget(); - - deltas.add(new ChangeDelta( - new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)), - new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize)))); - - if (orig.getLines().size() < rev.getLines().size()) { - deltas.add(new InsertDelta( - new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()), - new Chunk<>(rev.getPosition() + minSize, rev.getLines().subList(minSize, rev.getLines().size())))); - } else { - deltas.add(new DeleteDelta( - new Chunk<>(orig.getPosition() + minSize, orig.getLines().subList(minSize, orig.getLines().size())), - new Chunk<>(rev.getPosition() + minSize, Collections.emptyList()))); - } - return deltas; - } - - return Collections.singletonList(delta); - } - - private DiffRow buildDiffRow(Tag type, String orgline, String newline) { - if (reportLinesUnchanged) { - return new DiffRow(type, orgline, newline); - } else { - String wrapOrg = preprocessLine(orgline); - if (Tag.DELETE == type) { - if (mergeOriginalRevised || showInlineDiffs) { - wrapOrg = oldTag.apply(type, true) + wrapOrg + oldTag.apply(type, false); - } - } - String wrapNew = preprocessLine(newline); - if (Tag.INSERT == type) { - if (mergeOriginalRevised) { - wrapOrg = newTag.apply(type, true) + wrapNew + newTag.apply(type, false); - } else if (showInlineDiffs) { - wrapNew = newTag.apply(type, true) + wrapNew + newTag.apply(type, false); - } - } - return new DiffRow(type, wrapOrg, wrapNew); - } - } - - private DiffRow buildDiffRowWithoutNormalizing(Tag type, String orgline, String newline) { - return new DiffRow(type, - StringUtils.wrapText(orgline, columnWidth), - StringUtils.wrapText(newline, columnWidth)); - } - - List normalizeLines(List list) { - return reportLinesUnchanged - ? list - : list.stream() - .map(lineNormalizer::apply) - .collect(toList()); - } - - /** - * Add the inline diffs for given delta - * - * @param delta the given delta - */ - private List generateInlineDiffs(AbstractDelta delta) { - List orig = normalizeLines(delta.getSource().getLines()); - List rev = normalizeLines(delta.getTarget().getLines()); - List origList; - List revList; - String joinedOrig = String.join("\n", orig); - String joinedRev = String.join("\n", rev); - - origList = inlineDiffSplitter.apply(joinedOrig); - revList = inlineDiffSplitter.apply(joinedRev); - - List> originalInlineDeltas = DiffUtils.diff(origList, revList, equalizer) - .getDeltas(); - List> inlineDeltas = inlineDeltaMerger - .apply(new InlineDeltaMergeInfo(originalInlineDeltas, origList, revList)); - - Collections.reverse(inlineDeltas); - for (AbstractDelta inlineDelta : inlineDeltas) { - Chunk inlineOrig = inlineDelta.getSource(); - Chunk inlineRev = inlineDelta.getTarget(); - if (inlineDelta.getType() == DeltaType.DELETE) { - wrapInTag(origList, inlineOrig.getPosition(), inlineOrig - .getPosition() - + inlineOrig.size(), Tag.DELETE, oldTag, processDiffs, replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised); - } else if (inlineDelta.getType() == DeltaType.INSERT) { - if (mergeOriginalRevised) { - origList.addAll(inlineOrig.getPosition(), - revList.subList(inlineRev.getPosition(), - inlineRev.getPosition() + inlineRev.size())); - wrapInTag(origList, inlineOrig.getPosition(), - inlineOrig.getPosition() + inlineRev.size(), - Tag.INSERT, newTag, processDiffs, false); - } else { - wrapInTag(revList, inlineRev.getPosition(), - inlineRev.getPosition() + inlineRev.size(), - Tag.INSERT, newTag, processDiffs, false); - } - } else if (inlineDelta.getType() == DeltaType.CHANGE) { - if (mergeOriginalRevised) { - origList.addAll(inlineOrig.getPosition() + inlineOrig.size(), - revList.subList(inlineRev.getPosition(), - inlineRev.getPosition() + inlineRev.size())); - wrapInTag(origList, inlineOrig.getPosition() + inlineOrig.size(), - inlineOrig.getPosition() + inlineOrig.size() + inlineRev.size(), - Tag.CHANGE, newTag, processDiffs, false); - } else { - wrapInTag(revList, inlineRev.getPosition(), - inlineRev.getPosition() + inlineRev.size(), - Tag.CHANGE, newTag, processDiffs, false); - } - wrapInTag(origList, inlineOrig.getPosition(), - inlineOrig.getPosition() + inlineOrig.size(), - Tag.CHANGE, oldTag, processDiffs, replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised); - } - } - StringBuilder origResult = new StringBuilder(); - StringBuilder revResult = new StringBuilder(); - for (String character : origList) { - origResult.append(character); - } - for (String character : revList) { - revResult.append(character); - } - - List original = Arrays.asList(origResult.toString().split("\n")); - List revised = Arrays.asList(revResult.toString().split("\n")); - List diffRows = new ArrayList<>(); - for (int j = 0; j < Math.max(original.size(), revised.size()); j++) { - diffRows. - add(buildDiffRowWithoutNormalizing(Tag.CHANGE, - original.size() > j ? original.get(j) : "", - revised.size() > j ? revised.get(j) : "")); - } - return diffRows; - } - - private String preprocessLine(String line) { - if (columnWidth == 0) { - return lineNormalizer.apply(line); - } else { - return StringUtils.wrapText(lineNormalizer.apply(line), columnWidth); - } - } - - /** - * This class used for building the DiffRowGenerator. - * - * @author dmitry - * - */ - public static class Builder { - - private boolean showInlineDiffs = false; - private boolean ignoreWhiteSpaces = false; - private boolean decompressDeltas = true; - - private BiFunction oldTag - = (tag, f) -> f ? "" : ""; - private BiFunction newTag - = (tag, f) -> f ? "" : ""; - - private int columnWidth = 0; - private boolean mergeOriginalRevised = false; - private boolean reportLinesUnchanged = false; - private Function> inlineDiffSplitter = SPLITTER_BY_CHARACTER; - private Function lineNormalizer = LINE_NORMALIZER_FOR_HTML; - private Function processDiffs = null; - private BiPredicate equalizer = null; - private boolean replaceOriginalLinefeedInChangesWithSpaces = false; - private Function>> inlineDeltaMerger = DEFAULT_INLINE_DELTA_MERGER; - - private Builder() { - } - - /** - * Show inline diffs in generating diff rows or not. - * - * @param val the value to set. Default: false. - * @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. - * @return builder with configured ignoreWhiteSpaces parameter - */ - public Builder ignoreWhiteSpaces(boolean val) { - ignoreWhiteSpaces = val; - return this; - } - - /** - * Report all lines without markup on the old or new text. - * - * @param val the value to set. Default: false. - * @return builder with configured reportLinesUnchanged parameter - */ - public Builder reportLinesUnchanged(final boolean val) { - reportLinesUnchanged = val; - return this; - } - - /** - * Generator for Old-Text-Tags. - * - * @param generator the tag generator - * @return builder with configured ignoreBlankLines parameter - */ - public Builder oldTag(BiFunction generator) { - this.oldTag = generator; - return this; - } - - /** - * Generator for Old-Text-Tags. - * - * @param generator the tag generator - * @return builder with configured ignoreBlankLines parameter - */ - public Builder oldTag(Function generator) { - this.oldTag = (tag, f) -> generator.apply(f); - return this; - } - - /** - * Generator for New-Text-Tags. - * - * @param generator - * @return - */ - public Builder newTag(BiFunction generator) { - this.newTag = generator; - return this; - } - - /** - * Generator for New-Text-Tags. - * - * @param generator - * @return - */ - public Builder newTag(Function generator) { - this.newTag = (tag, f) -> generator.apply(f); - return this; - } - - /** - * Processor for diffed text parts. Here e.g. whitecharacters could be - * replaced by something visible. - * - * @param processDiffs - * @return - */ - public Builder processDiffs(Function processDiffs) { - this.processDiffs = processDiffs; - return this; - } - - /** - * Set the column width of generated lines of original and revised - * texts. - * - * @param width the width to set. Making it < 0 doesn't make any - * sense. Default 80. - * @return builder with config of column width - */ - public Builder columnWidth(int width) { - if (width >= 0) { - columnWidth = width; - } - return this; - } - - /** - * Build the DiffRowGenerator. If some parameters is not set, the - * default values are used. - * - * @return the customized DiffRowGenerator - */ - public DiffRowGenerator build() { - return new DiffRowGenerator(this); - } - - /** - * Merge the complete result within the original text. This makes sense - * for one line display. - * - * @param mergeOriginalRevised - * @return - */ - public Builder mergeOriginalRevised(boolean mergeOriginalRevised) { - this.mergeOriginalRevised = mergeOriginalRevised; - return this; - } - - /** - * Deltas could be in a state, that would produce some unreasonable - * results within an inline diff. So the deltas are decompressed into - * smaller parts and rebuild. But this could result in more differences. - * - * @param decompressDeltas - * @return - */ - public Builder decompressDeltas(boolean decompressDeltas) { - this.decompressDeltas = decompressDeltas; - return this; - } - - /** - * Per default each character is separatly processed. This variant - * introduces processing by word, which does not deliver in word - * changes. Therefore the whole word will be tagged as changed: - * - *

-         * false:    (aBa : aba) --  changed: a(B)a : a(b)a
-         * true:     (aBa : aba) --  changed: (aBa) : (aba)
-         * 
- */ - public Builder inlineDiffByWord(boolean inlineDiffByWord) { - inlineDiffSplitter = inlineDiffByWord ? SPLITTER_BY_WORD : SPLITTER_BY_CHARACTER; - return this; - } - - /** - * To provide some customized splitting a splitter can be provided. Here - * someone could think about sentence splitter, comma splitter or stuff - * like that. - * - * @param inlineDiffSplitter - * @return - */ - public Builder inlineDiffBySplitter(Function> inlineDiffSplitter) { - this.inlineDiffSplitter = inlineDiffSplitter; - return this; - } - - /** - * By default DiffRowGenerator preprocesses lines for HTML output. Tabs - * and special HTML characters like "<" are replaced with its encoded - * value. To change this you can provide a customized line normalizer - * here. - * - * @param lineNormalizer - * @return - */ - public Builder lineNormalizer(Function lineNormalizer) { - this.lineNormalizer = lineNormalizer; - return this; - } - - /** - * Provide an equalizer for diff processing. - * - * @param equalizer equalizer for diff processing. - * @return builder with configured equalizer parameter - */ - public Builder equalizer(BiPredicate equalizer) { - this.equalizer = equalizer; - return this; - } - - /** - * Sometimes it happens that a change contains multiple lines. If there - * is no correspondence in old and new. To keep the merged line more - * readable the linefeeds could be replaced by spaces. - * - * @param replace - * @return - */ - public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) { - this.replaceOriginalLinefeedInChangesWithSpaces = replace; - return this; - } - - /** - * Provide an inline delta merger for use case specific delta optimizations. - * - * @param inlineDeltaMerger - * @return - */ - public Builder inlineDeltaMerger( - Function>> inlineDeltaMerger) { - this.inlineDeltaMerger = inlineDeltaMerger; - return this; - } - } + public static final BiPredicate DEFAULT_EQUALIZER = Object::equals; + + public static final BiPredicate IGNORE_WHITESPACE_EQUALIZER = + (original, revised) -> adjustWhitespace(original).equals(adjustWhitespace(revised)); + + public static final Function LINE_NORMALIZER_FOR_HTML = StringUtils::normalize; + + /** + * Splitting lines by character to achieve char by char diff checking. + */ + public static final Function> SPLITTER_BY_CHARACTER = line -> { + List list = new ArrayList<>(line.length()); + for (Character character : line.toCharArray()) { + list.add(character.toString()); + } + return list; + }; + + public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#<>;:&\\']+"); + + /** + * Splitting lines by word to achieve word by word diff checking. + */ + public static final Function> SPLITTER_BY_WORD = + line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN); + + public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); + + public static final Function>> DEFAULT_INLINE_DELTA_MERGER = + InlineDeltaMergeInfo::getDeltas; + + /** + * Merge diffs which are separated by equalities consisting of whitespace only. + */ + public static final Function>> WHITESPACE_EQUALITIES_MERGER = + deltaMergeInfo -> DeltaMergeUtils.mergeInlineDeltas(deltaMergeInfo, equalities -> equalities.stream() + .allMatch(s -> s == null || s.replaceAll("\\s+", "").equals(""))); + + public static Builder create() { + return new Builder(); + } + + private static String adjustWhitespace(String raw) { + return WHITESPACE_PATTERN.matcher(raw.trim()).replaceAll(" "); + } + + protected static final List splitStringPreserveDelimiter(String str, Pattern SPLIT_PATTERN) { + List list = new ArrayList<>(); + if (str != null) { + Matcher matcher = SPLIT_PATTERN.matcher(str); + int pos = 0; + while (matcher.find()) { + if (pos < matcher.start()) { + list.add(str.substring(pos, matcher.start())); + } + list.add(matcher.group()); + pos = matcher.end(); + } + if (pos < str.length()) { + list.add(str.substring(pos)); + } + } + return list; + } + + /** + * 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 tagGenerator the tag generator + */ + static void wrapInTag( + List sequence, + int startPosition, + int endPosition, + Tag tag, + BiFunction tagGenerator, + Function processDiffs, + boolean replaceLinefeedWithSpace) { + int endPos = endPosition; + + while (endPos >= startPosition) { + + // search position for end tag + while (endPos > startPosition) { + if (!"\n".equals(sequence.get(endPos - 1))) { + break; + } else if (replaceLinefeedWithSpace) { + sequence.set(endPos - 1, " "); + break; + } + endPos--; + } + + if (endPos == startPosition) { + break; + } + + sequence.add(endPos, tagGenerator.apply(tag, false)); + if (processDiffs != null) { + sequence.set(endPos - 1, processDiffs.apply(sequence.get(endPos - 1))); + } + endPos--; + + // search position for end tag + while (endPos > startPosition) { + if ("\n".equals(sequence.get(endPos - 1))) { + if (replaceLinefeedWithSpace) { + sequence.set(endPos - 1, " "); + } else { + break; + } + } + if (processDiffs != null) { + sequence.set(endPos - 1, processDiffs.apply(sequence.get(endPos - 1))); + } + endPos--; + } + + sequence.add(endPos, tagGenerator.apply(tag, true)); + endPos--; + } + } + + private final int columnWidth; + private final BiPredicate equalizer; + private final boolean ignoreWhiteSpaces; + private final Function> inlineDiffSplitter; + private final boolean mergeOriginalRevised; + private final BiFunction newTag; + private final BiFunction oldTag; + private final boolean reportLinesUnchanged; + private final Function lineNormalizer; + private final Function processDiffs; + private final Function>> inlineDeltaMerger; + + private final boolean showInlineDiffs; + private final boolean replaceOriginalLinefeedInChangesWithSpaces; + private final boolean decompressDeltas; + + private DiffRowGenerator(Builder builder) { + showInlineDiffs = builder.showInlineDiffs; + ignoreWhiteSpaces = builder.ignoreWhiteSpaces; + oldTag = builder.oldTag; + newTag = builder.newTag; + columnWidth = builder.columnWidth; + mergeOriginalRevised = builder.mergeOriginalRevised; + inlineDiffSplitter = builder.inlineDiffSplitter; + decompressDeltas = builder.decompressDeltas; + + if (builder.equalizer != null) { + equalizer = builder.equalizer; + } else { + equalizer = ignoreWhiteSpaces ? IGNORE_WHITESPACE_EQUALIZER : DEFAULT_EQUALIZER; + } + + reportLinesUnchanged = builder.reportLinesUnchanged; + lineNormalizer = builder.lineNormalizer; + processDiffs = builder.processDiffs; + inlineDeltaMerger = builder.inlineDeltaMerger; + + replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; + + Objects.requireNonNull(inlineDiffSplitter); + Objects.requireNonNull(lineNormalizer); + Objects.requireNonNull(inlineDeltaMerger); + } + + /** + * 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 + * @return the DiffRows between original and revised texts + */ + public List generateDiffRows(List original, List revised) { + return generateDiffRows(original, DiffUtils.diff(original, revised, equalizer)); + } + + /** + * 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 patch the given patch + * @return the DiffRows between original and revised texts + */ + public List generateDiffRows(final List original, Patch patch) { + List diffRows = new ArrayList<>(); + int endPos = 0; + final List> deltaList = patch.getDeltas(); + + if (decompressDeltas) { + for (AbstractDelta originalDelta : deltaList) { + for (AbstractDelta delta : decompressDeltas(originalDelta)) { + endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); + } + } + } else { + for (AbstractDelta delta : deltaList) { + endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta); + } + } + + // Copy the final matching chunk if any. + for (String line : original.subList(endPos, original.size())) { + diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); + } + return diffRows; + } + + /** + * Transforms one patch delta into a DiffRow object. + */ + private int transformDeltaIntoDiffRow( + final List original, int endPos, List diffRows, AbstractDelta delta) { + Chunk orig = delta.getSource(); + Chunk rev = delta.getTarget(); + + for (String line : original.subList(endPos, orig.getPosition())) { + diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); + } + + switch (delta.getType()) { + case INSERT: + for (String line : rev.getLines()) { + diffRows.add(buildDiffRow(Tag.INSERT, "", line)); + } + break; + case DELETE: + for (String line : orig.getLines()) { + diffRows.add(buildDiffRow(Tag.DELETE, line, "")); + } + break; + default: + if (showInlineDiffs) { + diffRows.addAll(generateInlineDiffs(delta)); + } else { + for (int j = 0; j < Math.max(orig.size(), rev.size()); j++) { + diffRows.add(buildDiffRow( + Tag.CHANGE, + orig.getLines().size() > j ? orig.getLines().get(j) : "", + rev.getLines().size() > j ? rev.getLines().get(j) : "")); + } + } + } + + return orig.last() + 1; + } + + /** + * Decompresses ChangeDeltas with different source and target size to a + * ChangeDelta with same size and a following InsertDelta or DeleteDelta. + * With this problems of building DiffRows getting smaller. + * + * @param deltaList + */ + private List> decompressDeltas(AbstractDelta delta) { + if (delta.getType() == DeltaType.CHANGE + && delta.getSource().size() != delta.getTarget().size()) { + List> deltas = new ArrayList<>(); + // System.out.println("decompress this " + delta); + + int minSize = Math.min(delta.getSource().size(), delta.getTarget().size()); + Chunk orig = delta.getSource(); + Chunk rev = delta.getTarget(); + + deltas.add(new ChangeDelta( + new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)), + new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize)))); + + if (orig.getLines().size() < rev.getLines().size()) { + deltas.add(new InsertDelta( + new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()), + new Chunk<>( + rev.getPosition() + minSize, + rev.getLines().subList(minSize, rev.getLines().size())))); + } else { + deltas.add(new DeleteDelta( + new Chunk<>( + orig.getPosition() + minSize, + orig.getLines().subList(minSize, orig.getLines().size())), + new Chunk<>(rev.getPosition() + minSize, Collections.emptyList()))); + } + return deltas; + } + + return Collections.singletonList(delta); + } + + private DiffRow buildDiffRow(Tag type, String orgline, String newline) { + if (reportLinesUnchanged) { + return new DiffRow(type, orgline, newline); + } else { + String wrapOrg = preprocessLine(orgline); + if (Tag.DELETE == type) { + if (mergeOriginalRevised || showInlineDiffs) { + wrapOrg = oldTag.apply(type, true) + wrapOrg + oldTag.apply(type, false); + } + } + String wrapNew = preprocessLine(newline); + if (Tag.INSERT == type) { + if (mergeOriginalRevised) { + wrapOrg = newTag.apply(type, true) + wrapNew + newTag.apply(type, false); + } else if (showInlineDiffs) { + wrapNew = newTag.apply(type, true) + wrapNew + newTag.apply(type, false); + } + } + return new DiffRow(type, wrapOrg, wrapNew); + } + } + + private DiffRow buildDiffRowWithoutNormalizing(Tag type, String orgline, String newline) { + return new DiffRow( + type, StringUtils.wrapText(orgline, columnWidth), StringUtils.wrapText(newline, columnWidth)); + } + + List normalizeLines(List list) { + return reportLinesUnchanged + ? list + : list.stream().map(lineNormalizer::apply).collect(toList()); + } + + /** + * Add the inline diffs for given delta + * + * @param delta the given delta + */ + private List generateInlineDiffs(AbstractDelta delta) { + List orig = normalizeLines(delta.getSource().getLines()); + List rev = normalizeLines(delta.getTarget().getLines()); + List origList; + List revList; + String joinedOrig = String.join("\n", orig); + String joinedRev = String.join("\n", rev); + + origList = inlineDiffSplitter.apply(joinedOrig); + revList = inlineDiffSplitter.apply(joinedRev); + + List> originalInlineDeltas = + DiffUtils.diff(origList, revList, equalizer).getDeltas(); + List> inlineDeltas = + inlineDeltaMerger.apply(new InlineDeltaMergeInfo(originalInlineDeltas, origList, revList)); + + Collections.reverse(inlineDeltas); + for (AbstractDelta inlineDelta : inlineDeltas) { + Chunk inlineOrig = inlineDelta.getSource(); + Chunk inlineRev = inlineDelta.getTarget(); + if (inlineDelta.getType() == DeltaType.DELETE) { + wrapInTag( + origList, + inlineOrig.getPosition(), + inlineOrig.getPosition() + inlineOrig.size(), + Tag.DELETE, + oldTag, + processDiffs, + replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised); + } else if (inlineDelta.getType() == DeltaType.INSERT) { + if (mergeOriginalRevised) { + origList.addAll( + inlineOrig.getPosition(), + revList.subList(inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size())); + wrapInTag( + origList, + inlineOrig.getPosition(), + inlineOrig.getPosition() + inlineRev.size(), + Tag.INSERT, + newTag, + processDiffs, + false); + } else { + wrapInTag( + revList, + inlineRev.getPosition(), + inlineRev.getPosition() + inlineRev.size(), + Tag.INSERT, + newTag, + processDiffs, + false); + } + } else if (inlineDelta.getType() == DeltaType.CHANGE) { + if (mergeOriginalRevised) { + origList.addAll( + inlineOrig.getPosition() + inlineOrig.size(), + revList.subList(inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size())); + wrapInTag( + origList, + inlineOrig.getPosition() + inlineOrig.size(), + inlineOrig.getPosition() + inlineOrig.size() + inlineRev.size(), + Tag.CHANGE, + newTag, + processDiffs, + false); + } else { + wrapInTag( + revList, + inlineRev.getPosition(), + inlineRev.getPosition() + inlineRev.size(), + Tag.CHANGE, + newTag, + processDiffs, + false); + } + wrapInTag( + origList, + inlineOrig.getPosition(), + inlineOrig.getPosition() + inlineOrig.size(), + Tag.CHANGE, + oldTag, + processDiffs, + replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised); + } + } + StringBuilder origResult = new StringBuilder(); + StringBuilder revResult = new StringBuilder(); + for (String character : origList) { + origResult.append(character); + } + for (String character : revList) { + revResult.append(character); + } + + List original = Arrays.asList(origResult.toString().split("\n")); + List revised = Arrays.asList(revResult.toString().split("\n")); + List diffRows = new ArrayList<>(); + for (int j = 0; j < Math.max(original.size(), revised.size()); j++) { + diffRows.add(buildDiffRowWithoutNormalizing( + Tag.CHANGE, original.size() > j ? original.get(j) : "", revised.size() > j ? revised.get(j) : "")); + } + return diffRows; + } + + private String preprocessLine(String line) { + if (columnWidth == 0) { + return lineNormalizer.apply(line); + } else { + return StringUtils.wrapText(lineNormalizer.apply(line), columnWidth); + } + } + + /** + * This class used for building the DiffRowGenerator. + * + * @author dmitry + * + */ + public static class Builder { + + private boolean showInlineDiffs = false; + private boolean ignoreWhiteSpaces = false; + private boolean decompressDeltas = true; + + private BiFunction oldTag = (tag, f) -> f ? "" : ""; + private BiFunction newTag = (tag, f) -> f ? "" : ""; + + private int columnWidth = 0; + private boolean mergeOriginalRevised = false; + private boolean reportLinesUnchanged = false; + private Function> inlineDiffSplitter = SPLITTER_BY_CHARACTER; + private Function lineNormalizer = LINE_NORMALIZER_FOR_HTML; + private Function processDiffs = null; + private BiPredicate equalizer = null; + private boolean replaceOriginalLinefeedInChangesWithSpaces = false; + private Function>> inlineDeltaMerger = + DEFAULT_INLINE_DELTA_MERGER; + + private Builder() {} + + /** + * Show inline diffs in generating diff rows or not. + * + * @param val the value to set. Default: false. + * @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. + * @return builder with configured ignoreWhiteSpaces parameter + */ + public Builder ignoreWhiteSpaces(boolean val) { + ignoreWhiteSpaces = val; + return this; + } + + /** + * Report all lines without markup on the old or new text. + * + * @param val the value to set. Default: false. + * @return builder with configured reportLinesUnchanged parameter + */ + public Builder reportLinesUnchanged(final boolean val) { + reportLinesUnchanged = val; + return this; + } + + /** + * Generator for Old-Text-Tags. + * + * @param generator the tag generator + * @return builder with configured ignoreBlankLines parameter + */ + public Builder oldTag(BiFunction generator) { + this.oldTag = generator; + return this; + } + + /** + * Generator for Old-Text-Tags. + * + * @param generator the tag generator + * @return builder with configured ignoreBlankLines parameter + */ + public Builder oldTag(Function generator) { + this.oldTag = (tag, f) -> generator.apply(f); + return this; + } + + /** + * Generator for New-Text-Tags. + * + * @param generator + * @return + */ + public Builder newTag(BiFunction generator) { + this.newTag = generator; + return this; + } + + /** + * Generator for New-Text-Tags. + * + * @param generator + * @return + */ + public Builder newTag(Function generator) { + this.newTag = (tag, f) -> generator.apply(f); + return this; + } + + /** + * Processor for diffed text parts. Here e.g. whitecharacters could be + * replaced by something visible. + * + * @param processDiffs + * @return + */ + public Builder processDiffs(Function processDiffs) { + this.processDiffs = processDiffs; + return this; + } + + /** + * Set the column width of generated lines of original and revised + * texts. + * + * @param width the width to set. Making it < 0 doesn't make any + * sense. Default 80. + * @return builder with config of column width + */ + public Builder columnWidth(int width) { + if (width >= 0) { + columnWidth = width; + } + return this; + } + + /** + * Build the DiffRowGenerator. If some parameters is not set, the + * default values are used. + * + * @return the customized DiffRowGenerator + */ + public DiffRowGenerator build() { + return new DiffRowGenerator(this); + } + + /** + * Merge the complete result within the original text. This makes sense + * for one line display. + * + * @param mergeOriginalRevised + * @return + */ + public Builder mergeOriginalRevised(boolean mergeOriginalRevised) { + this.mergeOriginalRevised = mergeOriginalRevised; + return this; + } + + /** + * Deltas could be in a state, that would produce some unreasonable + * results within an inline diff. So the deltas are decompressed into + * smaller parts and rebuild. But this could result in more differences. + * + * @param decompressDeltas + * @return + */ + public Builder decompressDeltas(boolean decompressDeltas) { + this.decompressDeltas = decompressDeltas; + return this; + } + + /** + * Per default each character is separatly processed. This variant + * introduces processing by word, which does not deliver in word + * changes. Therefore the whole word will be tagged as changed: + * + *
+				 * false:    (aBa : aba) --  changed: a(B)a : a(b)a
+				 * true:     (aBa : aba) --  changed: (aBa) : (aba)
+				 * 
+ */ + public Builder inlineDiffByWord(boolean inlineDiffByWord) { + inlineDiffSplitter = inlineDiffByWord ? SPLITTER_BY_WORD : SPLITTER_BY_CHARACTER; + return this; + } + + /** + * To provide some customized splitting a splitter can be provided. Here + * someone could think about sentence splitter, comma splitter or stuff + * like that. + * + * @param inlineDiffSplitter + * @return + */ + public Builder inlineDiffBySplitter(Function> inlineDiffSplitter) { + this.inlineDiffSplitter = inlineDiffSplitter; + return this; + } + + /** + * By default DiffRowGenerator preprocesses lines for HTML output. Tabs + * and special HTML characters like "<" are replaced with its encoded + * value. To change this you can provide a customized line normalizer + * here. + * + * @param lineNormalizer + * @return + */ + public Builder lineNormalizer(Function lineNormalizer) { + this.lineNormalizer = lineNormalizer; + return this; + } + + /** + * Provide an equalizer for diff processing. + * + * @param equalizer equalizer for diff processing. + * @return builder with configured equalizer parameter + */ + public Builder equalizer(BiPredicate equalizer) { + this.equalizer = equalizer; + return this; + } + + /** + * Sometimes it happens that a change contains multiple lines. If there + * is no correspondence in old and new. To keep the merged line more + * readable the linefeeds could be replaced by spaces. + * + * @param replace + * @return + */ + public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) { + this.replaceOriginalLinefeedInChangesWithSpaces = replace; + return this; + } + + /** + * Provide an inline delta merger for use case specific delta optimizations. + * + * @param inlineDeltaMerger + * @return + */ + public Builder inlineDeltaMerger( + Function>> inlineDeltaMerger) { + this.inlineDeltaMerger = inlineDeltaMerger; + return this; + } + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java index b7e35495..2a629b17 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/StringUtils.java @@ -15,69 +15,66 @@ */ package com.github.difflib.text; -import java.util.List; import static java.util.stream.Collectors.toList; +import java.util.List; + final class StringUtils { - /** - * Replaces all opening and closing tags with < or >. - * - * @param str - * @return str with some HTML meta characters escaped. - */ - public static String htmlEntites(String str) { - return str.replace("<", "<").replace(">", ">"); - } + /** + * Replaces all opening and closing tags with < or >. + * + * @param str + * @return str with some HTML meta characters escaped. + */ + public static String htmlEntites(String str) { + return str.replace("<", "<").replace(">", ">"); + } - public static String normalize(String str) { - return htmlEntites(str).replace("\t", " "); - } + public static String normalize(String str) { + return htmlEntites(str).replace("\t", " "); + } - public static List wrapText(List list, int columnWidth) { - return list.stream() - .map(line -> wrapText(line, columnWidth)) - .collect(toList()); - } + public static List wrapText(List list, int columnWidth) { + return list.stream().map(line -> wrapText(line, columnWidth)).collect(toList()); + } - /** - * Wrap the text with the given column width - * - * @param line the text - * @param columnWidth the given column - * @return the wrapped text - */ - public static String wrapText(String line, int columnWidth) { - if (columnWidth < 0) { - throw new IllegalArgumentException("columnWidth may not be less 0"); - } - if (columnWidth == 0) { - return line; - } - int length = line.length(); - int delimiter = "
".length(); - int widthIndex = columnWidth; + /** + * Wrap the text with the given column width + * + * @param line the text + * @param columnWidth the given column + * @return the wrapped text + */ + public static String wrapText(String line, int columnWidth) { + if (columnWidth < 0) { + throw new IllegalArgumentException("columnWidth may not be less 0"); + } + if (columnWidth == 0) { + return line; + } + int length = line.length(); + int delimiter = "
".length(); + int widthIndex = columnWidth; - StringBuilder b = new StringBuilder(line); + StringBuilder b = new StringBuilder(line); - for (int count = 0; length > widthIndex; count++) { - int breakPoint = widthIndex + delimiter * count; - if (Character.isHighSurrogate(b.charAt(breakPoint - 1)) && - Character.isLowSurrogate(b.charAt(breakPoint))) { - // Shift a breakpoint that would split a supplemental code-point. - breakPoint += 1; - if (breakPoint == b.length()) { - // Break before instead of after if this is the last code-point. - breakPoint -= 2; - } - } - b.insert(breakPoint, "
"); - widthIndex += columnWidth; - } + for (int count = 0; length > widthIndex; count++) { + int breakPoint = widthIndex + delimiter * count; + if (Character.isHighSurrogate(b.charAt(breakPoint - 1)) && Character.isLowSurrogate(b.charAt(breakPoint))) { + // Shift a breakpoint that would split a supplemental code-point. + breakPoint += 1; + if (breakPoint == b.length()) { + // Break before instead of after if this is the last code-point. + breakPoint -= 2; + } + } + b.insert(breakPoint, "
"); + widthIndex += columnWidth; + } - return b.toString(); - } + return b.toString(); + } - private StringUtils() { - } + private StringUtils() {} } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java index b2580957..1e68a637 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java @@ -15,65 +15,66 @@ */ package com.github.difflib.text.deltamerge; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; /** * Provides utility features for merge inline deltas * * @author Christian Meier */ -final public class DeltaMergeUtils { +public final class DeltaMergeUtils { - public static List> mergeInlineDeltas(InlineDeltaMergeInfo deltaMergeInfo, - Predicate> replaceEquality) { - final List> originalDeltas = deltaMergeInfo.getDeltas(); - if (originalDeltas.size() < 2) { - return originalDeltas; - } + public static List> mergeInlineDeltas( + InlineDeltaMergeInfo deltaMergeInfo, Predicate> replaceEquality) { + final List> originalDeltas = deltaMergeInfo.getDeltas(); + if (originalDeltas.size() < 2) { + return originalDeltas; + } - final List> newDeltas = new ArrayList<>(); - newDeltas.add(originalDeltas.get(0)); - for (int i = 1; i < originalDeltas.size(); i++) { - final AbstractDelta previousDelta = newDeltas.get(newDeltas.size()-1); - final AbstractDelta currentDelta = originalDeltas.get(i); + final List> newDeltas = new ArrayList<>(); + newDeltas.add(originalDeltas.get(0)); + for (int i = 1; i < originalDeltas.size(); i++) { + final AbstractDelta previousDelta = newDeltas.get(newDeltas.size() - 1); + final AbstractDelta currentDelta = originalDeltas.get(i); - final List equalities = deltaMergeInfo.getOrigList().subList( - previousDelta.getSource().getPosition() + previousDelta.getSource().size(), - currentDelta.getSource().getPosition()); + final List equalities = deltaMergeInfo + .getOrigList() + .subList( + previousDelta.getSource().getPosition() + + previousDelta.getSource().size(), + currentDelta.getSource().getPosition()); - if (replaceEquality.test(equalities)) { - // Merge the previous delta, the equality and the current delta into one - // ChangeDelta and replace the previous delta by this new ChangeDelta. - final List allSourceLines = new ArrayList<>(); - allSourceLines.addAll(previousDelta.getSource().getLines()); - allSourceLines.addAll(equalities); - allSourceLines.addAll(currentDelta.getSource().getLines()); + if (replaceEquality.test(equalities)) { + // Merge the previous delta, the equality and the current delta into one + // ChangeDelta and replace the previous delta by this new ChangeDelta. + final List allSourceLines = new ArrayList<>(); + allSourceLines.addAll(previousDelta.getSource().getLines()); + allSourceLines.addAll(equalities); + allSourceLines.addAll(currentDelta.getSource().getLines()); - final List allTargetLines = new ArrayList<>(); - allTargetLines.addAll(previousDelta.getTarget().getLines()); - allTargetLines.addAll(equalities); - allTargetLines.addAll(currentDelta.getTarget().getLines()); + final List allTargetLines = new ArrayList<>(); + allTargetLines.addAll(previousDelta.getTarget().getLines()); + allTargetLines.addAll(equalities); + allTargetLines.addAll(currentDelta.getTarget().getLines()); - final ChangeDelta replacement = new ChangeDelta<>( - new Chunk<>(previousDelta.getSource().getPosition(), allSourceLines), - new Chunk<>(previousDelta.getTarget().getPosition(), allTargetLines)); + final ChangeDelta replacement = new ChangeDelta<>( + new Chunk<>(previousDelta.getSource().getPosition(), allSourceLines), + new Chunk<>(previousDelta.getTarget().getPosition(), allTargetLines)); - newDeltas.remove(newDeltas.size()-1); - newDeltas.add(replacement); - } else { - newDeltas.add(currentDelta); - } - } + newDeltas.remove(newDeltas.size() - 1); + newDeltas.add(replacement); + } else { + newDeltas.add(currentDelta); + } + } - return newDeltas; - } + return newDeltas; + } - private DeltaMergeUtils() { - } + private DeltaMergeUtils() {} } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java index cc6b399a..1c9027f1 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java @@ -15,9 +15,8 @@ */ package com.github.difflib.text.deltamerge; -import java.util.List; - import com.github.difflib.patch.AbstractDelta; +import java.util.List; /** * Holds the information required to merge deltas originating from an inline @@ -27,25 +26,25 @@ */ public final class InlineDeltaMergeInfo { - private final List> deltas; - private final List origList; - private final List revList; + private final List> deltas; + private final List origList; + private final List revList; - public InlineDeltaMergeInfo(List> deltas, List origList, List revList) { - this.deltas = deltas; - this.origList = origList; - this.revList = revList; - } + public InlineDeltaMergeInfo(List> deltas, List origList, List revList) { + this.deltas = deltas; + this.origList = origList; + this.revList = revList; + } - public List> getDeltas() { - return deltas; - } + public List> getDeltas() { + return deltas; + } - public List getOrigList() { - return origList; - } + public List getOrigList() { + return origList; + } - public List getRevList() { - return revList; - } + public List getRevList() { + return revList; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java index f2bb231d..bc572ae3 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiff.java @@ -27,52 +27,54 @@ */ public final class UnifiedDiff { - private String header; - private String tail; - private final List files = new ArrayList<>(); + private String header; + private String tail; + private final List files = new ArrayList<>(); - public String getHeader() { - return header; - } + public String getHeader() { + return header; + } - public void setHeader(String header) { - this.header = header; - } + public void setHeader(String header) { + this.header = header; + } - void addFile(UnifiedDiffFile file) { - files.add(file); - } + void addFile(UnifiedDiffFile file) { + files.add(file); + } - public List getFiles() { - return Collections.unmodifiableList(files); - } + public List getFiles() { + return Collections.unmodifiableList(files); + } - void setTailTxt(String tailTxt) { - this.tail = tailTxt; - } + void setTailTxt(String tailTxt) { + this.tail = tailTxt; + } - public String getTail() { - return tail; - } + public String getTail() { + return tail; + } - public List applyPatchTo(Predicate findFile, List originalLines) throws PatchFailedException { - UnifiedDiffFile file = files.stream() - .filter(diff -> findFile.test(diff.getFromFile())) - .findFirst().orElse(null); - if (file != null) { - return file.getPatch().applyTo(originalLines); - } else { - return originalLines; - } - } + public List applyPatchTo(Predicate findFile, List originalLines) + throws PatchFailedException { + UnifiedDiffFile file = files.stream() + .filter(diff -> findFile.test(diff.getFromFile())) + .findFirst() + .orElse(null); + if (file != null) { + return file.getPatch().applyTo(originalLines); + } else { + return originalLines; + } + } - public static UnifiedDiff from(String header, String tail, UnifiedDiffFile... files) { - UnifiedDiff diff = new UnifiedDiff(); - diff.setHeader(header); - diff.setTailTxt(tail); - for (UnifiedDiffFile file : files) { - diff.addFile(file); - } - return diff; - } + public static UnifiedDiff from(String header, String tail, UnifiedDiffFile... files) { + UnifiedDiff diff = new UnifiedDiff(); + diff.setHeader(header); + diff.setTailTxt(tail); + for (UnifiedDiffFile file : files) { + diff.addFile(file); + } + return diff; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java index 1ae3b7ca..4fc54186 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java @@ -18,194 +18,194 @@ import com.github.difflib.patch.Patch; /** - * Data structure for one patched file from a unified diff file. - * + * Data structure for one patched file from a unified diff file. + * * @author Tobias Warneke (t.warneke@gmx.net) */ public final class UnifiedDiffFile { - private String diffCommand; - private String fromFile; - private String fromTimestamp; - private String toFile; - private String renameFrom; - private String renameTo; - private String copyFrom; - private String copyTo; - private String toTimestamp; - private String index; - private String newFileMode; - private String oldMode; - private String newMode; - private String deletedFileMode; - private String binaryAdded; - private String binaryDeleted; - private String binaryEdited; - private Patch patch = new Patch<>(); - private boolean noNewLineAtTheEndOfTheFile = false; - private Integer similarityIndex; - - public String getDiffCommand() { - return diffCommand; - } - - public void setDiffCommand(String diffCommand) { - this.diffCommand = diffCommand; - } - - public String getFromFile() { - return fromFile; - } - - public void setFromFile(String fromFile) { - this.fromFile = fromFile; - } - - public String getToFile() { - return toFile; - } - - public void setToFile(String toFile) { - this.toFile = toFile; - } - - public void setIndex(String index) { - this.index = index; - } - - public String getIndex() { - return index; - } - - public Patch getPatch() { - return patch; - } - - public String getFromTimestamp() { - return fromTimestamp; - } - - public void setFromTimestamp(String fromTimestamp) { - this.fromTimestamp = fromTimestamp; - } - - public String getToTimestamp() { - return toTimestamp; - } - - public void setToTimestamp(String toTimestamp) { - this.toTimestamp = toTimestamp; - } - - public Integer getSimilarityIndex() { - return similarityIndex; - } - - public void setSimilarityIndex(Integer similarityIndex) { - this.similarityIndex = similarityIndex; - } - - public String getRenameFrom() { - return renameFrom; - } - - public void setRenameFrom(String renameFrom) { - this.renameFrom = renameFrom; - } - - public String getRenameTo() { - return renameTo; - } - - public void setRenameTo(String renameTo) { - this.renameTo = renameTo; - } - - public String getCopyFrom() { - return copyFrom; - } - - public void setCopyFrom(String copyFrom) { - this.copyFrom = copyFrom; - } - - public String getCopyTo() { - return copyTo; - } - - public void setCopyTo(String copyTo) { - this.copyTo = copyTo; - } - - public static UnifiedDiffFile from(String fromFile, String toFile, Patch patch) { - UnifiedDiffFile file = new UnifiedDiffFile(); - file.setFromFile(fromFile); - file.setToFile(toFile); - file.patch = patch; - return file; - } - - public void setNewFileMode(String newFileMode) { - this.newFileMode = newFileMode; - } - - public String getNewFileMode() { - return newFileMode; - } - - public String getDeletedFileMode() { - return deletedFileMode; - } - - public void setDeletedFileMode(String deletedFileMode) { - this.deletedFileMode = deletedFileMode; - } - - public String getOldMode() { - return oldMode; - } - - public void setOldMode(String oldMode) { - this.oldMode = oldMode; - } - - public String getNewMode() { - return newMode; - } - - public void setNewMode(String newMode) { - this.newMode = newMode; - } - - public String getBinaryAdded() { - return binaryAdded; - } - - public void setBinaryAdded(String binaryAdded) { - this.binaryAdded = binaryAdded; - } - - public String getBinaryDeleted() { - return binaryDeleted; - } - - public void setBinaryDeleted(String binaryDeleted) { - this.binaryDeleted = binaryDeleted; - } - - public String getBinaryEdited() { - return binaryEdited; - } - - public void setBinaryEdited(String binaryEdited) { - this.binaryEdited = binaryEdited; - } - - public boolean isNoNewLineAtTheEndOfTheFile() { - return noNewLineAtTheEndOfTheFile; - } - - public void setNoNewLineAtTheEndOfTheFile(boolean noNewLineAtTheEndOfTheFile) { - this.noNewLineAtTheEndOfTheFile = noNewLineAtTheEndOfTheFile; - } + private String diffCommand; + private String fromFile; + private String fromTimestamp; + private String toFile; + private String renameFrom; + private String renameTo; + private String copyFrom; + private String copyTo; + private String toTimestamp; + private String index; + private String newFileMode; + private String oldMode; + private String newMode; + private String deletedFileMode; + private String binaryAdded; + private String binaryDeleted; + private String binaryEdited; + private Patch patch = new Patch<>(); + private boolean noNewLineAtTheEndOfTheFile = false; + private Integer similarityIndex; + + public String getDiffCommand() { + return diffCommand; + } + + public void setDiffCommand(String diffCommand) { + this.diffCommand = diffCommand; + } + + public String getFromFile() { + return fromFile; + } + + public void setFromFile(String fromFile) { + this.fromFile = fromFile; + } + + public String getToFile() { + return toFile; + } + + public void setToFile(String toFile) { + this.toFile = toFile; + } + + public void setIndex(String index) { + this.index = index; + } + + public String getIndex() { + return index; + } + + public Patch getPatch() { + return patch; + } + + public String getFromTimestamp() { + return fromTimestamp; + } + + public void setFromTimestamp(String fromTimestamp) { + this.fromTimestamp = fromTimestamp; + } + + public String getToTimestamp() { + return toTimestamp; + } + + public void setToTimestamp(String toTimestamp) { + this.toTimestamp = toTimestamp; + } + + public Integer getSimilarityIndex() { + return similarityIndex; + } + + public void setSimilarityIndex(Integer similarityIndex) { + this.similarityIndex = similarityIndex; + } + + public String getRenameFrom() { + return renameFrom; + } + + public void setRenameFrom(String renameFrom) { + this.renameFrom = renameFrom; + } + + public String getRenameTo() { + return renameTo; + } + + public void setRenameTo(String renameTo) { + this.renameTo = renameTo; + } + + public String getCopyFrom() { + return copyFrom; + } + + public void setCopyFrom(String copyFrom) { + this.copyFrom = copyFrom; + } + + public String getCopyTo() { + return copyTo; + } + + public void setCopyTo(String copyTo) { + this.copyTo = copyTo; + } + + public static UnifiedDiffFile from(String fromFile, String toFile, Patch patch) { + UnifiedDiffFile file = new UnifiedDiffFile(); + file.setFromFile(fromFile); + file.setToFile(toFile); + file.patch = patch; + return file; + } + + public void setNewFileMode(String newFileMode) { + this.newFileMode = newFileMode; + } + + public String getNewFileMode() { + return newFileMode; + } + + public String getDeletedFileMode() { + return deletedFileMode; + } + + public void setDeletedFileMode(String deletedFileMode) { + this.deletedFileMode = deletedFileMode; + } + + public String getOldMode() { + return oldMode; + } + + public void setOldMode(String oldMode) { + this.oldMode = oldMode; + } + + public String getNewMode() { + return newMode; + } + + public void setNewMode(String newMode) { + this.newMode = newMode; + } + + public String getBinaryAdded() { + return binaryAdded; + } + + public void setBinaryAdded(String binaryAdded) { + this.binaryAdded = binaryAdded; + } + + public String getBinaryDeleted() { + return binaryDeleted; + } + + public void setBinaryDeleted(String binaryDeleted) { + this.binaryDeleted = binaryDeleted; + } + + public String getBinaryEdited() { + return binaryEdited; + } + + public void setBinaryEdited(String binaryEdited) { + this.binaryEdited = binaryEdited; + } + + public boolean isNoNewLineAtTheEndOfTheFile() { + return noNewLineAtTheEndOfTheFile; + } + + public void setNoNewLineAtTheEndOfTheFile(boolean noNewLineAtTheEndOfTheFile) { + this.noNewLineAtTheEndOfTheFile = noNewLineAtTheEndOfTheFile; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java index ab7114db..0e991c77 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java @@ -21,23 +21,22 @@ */ public class UnifiedDiffParserException extends RuntimeException { - public UnifiedDiffParserException() { - } + public UnifiedDiffParserException() {} - public UnifiedDiffParserException(String message) { - super(message); - } + public UnifiedDiffParserException(String message) { + super(message); + } - public UnifiedDiffParserException(String message, Throwable cause) { - super(message, cause); - } + public UnifiedDiffParserException(String message, Throwable cause) { + super(message, cause); + } - public UnifiedDiffParserException(Throwable cause) { - super(cause); - } - - public UnifiedDiffParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } + public UnifiedDiffParserException(Throwable cause) { + super(cause); + } + public UnifiedDiffParserException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index b3a66ab2..61e1aa4d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -38,438 +38,469 @@ */ public final class UnifiedDiffReader { - static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern.compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@"); - static final Pattern TIMESTAMP_REGEXP = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d{3,})(?: [+-]\\d+)?"); - - private final InternalUnifiedDiffReader READER; - private final UnifiedDiff data = new UnifiedDiff(); - - private final UnifiedDiffLine DIFF_COMMAND = new UnifiedDiffLine(true, "^diff\\s", this::processDiff); - private final UnifiedDiffLine SIMILARITY_INDEX = new UnifiedDiffLine(true, "^similarity index (\\d+)%$", this::processSimilarityIndex); - private final UnifiedDiffLine INDEX = new UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$", this::processIndex); - private final UnifiedDiffLine FROM_FILE = new UnifiedDiffLine(true, "^---\\s", this::processFromFile); - private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile); - private final UnifiedDiffLine RENAME_FROM = new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom); - private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); - - private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom); - private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo); - - private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); - - private final UnifiedDiffLine DELETED_FILE_MODE = new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); - private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode); - private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode); - private final UnifiedDiffLine BINARY_ADDED = new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded); - private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted); - private final UnifiedDiffLine BINARY_EDITED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited); - private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); - private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine); - private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine); - private final UnifiedDiffLine LINE_ADD = new UnifiedDiffLine("^\\+", this::processAddLine); - - private UnifiedDiffFile actualFile; - - UnifiedDiffReader(Reader reader) { - this.READER = new InternalUnifiedDiffReader(reader); - } - - // schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file], - // [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], - // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], - // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; - private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { -// String headerTxt = ""; -// LOG.log(Level.FINE, "header parsing"); -// String line = null; -// while (READER.ready()) { -// line = READER.readLine(); -// LOG.log(Level.FINE, "parsing line {0}", line); -// if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) -// || FROM_FILE.validLine(line) || TO_FILE.validLine(line) -// || NEW_FILE_MODE.validLine(line)) { -// break; -// } else { -// headerTxt += line + "\n"; -// } -// } -// if (!"".equals(headerTxt)) { -// data.setHeader(headerTxt); -// } - - String line = READER.readLine(); - while (line != null) { - String headerTxt = ""; - LOG.log(Level.FINE, "header parsing"); - while (line != null) { - LOG.log(Level.FINE, "parsing line {0}", line); - if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, - COPY_FROM, COPY_TO, - NEW_FILE_MODE, DELETED_FILE_MODE, - OLD_MODE, NEW_MODE, - BINARY_ADDED, BINARY_DELETED, - BINARY_EDITED, CHUNK)) { - break; - } else { - headerTxt += line + "\n"; - } - line = READER.readLine(); - } - if (!"".equals(headerTxt)) { - data.setHeader(headerTxt); - } - if (line != null && !CHUNK.validLine(line)) { - initFileIfNecessary(); - while (line != null && !CHUNK.validLine(line)) { - if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, - COPY_FROM, COPY_TO, - NEW_FILE_MODE, DELETED_FILE_MODE, - OLD_MODE, NEW_MODE, - BINARY_ADDED , BINARY_DELETED, - BINARY_EDITED)) { - throw new UnifiedDiffParserException("expected file start line not found"); - } - line = READER.readLine(); - } - } - if (line != null) { - processLine(line, CHUNK); - while ((line = READER.readLine()) != null) { - line = checkForNoNewLineAtTheEndOfTheFile(line); - - if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { - throw new UnifiedDiffParserException("expected data line not found"); - } - if ((originalTxt.size() == old_size && revisedTxt.size() == new_size) - || (old_size == 0 && new_size == 0 && originalTxt.size() == this.old_ln - && revisedTxt.size() == this.new_ln)) { - finalizeChunk(); - break; - } - } - line = READER.readLine(); - - line = checkForNoNewLineAtTheEndOfTheFile(line); - } - if (line == null || (line.startsWith("--") && !line.startsWith("---"))) { - break; - } - } - - if (READER.ready()) { - String tailTxt = ""; - while (READER.ready()) { - if (tailTxt.length() > 0) { - tailTxt += "\n"; - } - tailTxt += READER.readLine(); - } - data.setTailTxt(tailTxt); - } - - return data; - } - - private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException { - if ("\\ No newline at end of file".equals(line)) { - actualFile.setNoNewLineAtTheEndOfTheFile(true); - return READER.readLine(); - } - return line; - } - - static String[] parseFileNames(String line) { - String[] split = line.split(" "); - return new String[]{ - split[2].replaceAll("^a/", ""), - split[3].replaceAll("^b/", "") - }; - } - - private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName()); - - /** - * To parse a diff file use this method. - * - * @param stream This is the diff file data. - * @return In a UnifiedDiff structure this diff file data is returned. - * @throws IOException - * @throws UnifiedDiffParserException - */ - public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException { - UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream))); - return parser.parse(); - } - - private boolean processLine(String line, UnifiedDiffLine... rules) throws UnifiedDiffParserException { - if (line == null) { - return false; - } - for (UnifiedDiffLine rule : rules) { - if (rule.processLine(line)) { - LOG.fine(" >>> processed rule " + rule.toString()); - return true; - } - } - LOG.warning(" >>> no rule matched " + line); - return false; - //throw new UnifiedDiffParserException("parsing error at line " + line); - } - - private boolean validLine(String line, UnifiedDiffLine ... rules) { - if (line == null) { - return false; - } - for (UnifiedDiffLine rule : rules) { - if (rule.validLine(line)) { - LOG.fine(" >>> accepted rule " + rule.toString()); - return true; - } - } - return false; - } - - private void initFileIfNecessary() { - if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { - throw new IllegalStateException(); - } - actualFile = null; - if (actualFile == null) { - actualFile = new UnifiedDiffFile(); - data.addFile(actualFile); - } - } - - private void processDiff(MatchResult match, String line) { - //initFileIfNecessary(); - LOG.log(Level.FINE, "start {0}", line); - String[] fromTo = parseFileNames(READER.lastLine()); - actualFile.setFromFile(fromTo[0]); - actualFile.setToFile(fromTo[1]); - actualFile.setDiffCommand(line); - } - - private void processSimilarityIndex(MatchResult match, String line) { - actualFile.setSimilarityIndex(Integer.valueOf(match.group(1))); - } - - private List originalTxt = new ArrayList<>(); - private List revisedTxt = new ArrayList<>(); - private List addLineIdxList = new ArrayList<>(); - private List delLineIdxList = new ArrayList<>(); - private int old_ln; - private int old_size; - private int new_ln; - private int new_size; - private int delLineIdx = 0; - private int addLineIdx = 0; - - private void finalizeChunk() { - if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { - actualFile.getPatch().addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, originalTxt, delLineIdxList), new Chunk<>( - new_ln - 1, revisedTxt, addLineIdxList))); - old_ln = 0; - new_ln = 0; - originalTxt.clear(); - revisedTxt.clear(); - addLineIdxList.clear(); - delLineIdxList.clear(); - delLineIdx = 0; - addLineIdx = 0; - } - } - - private void processNormalLine(MatchResult match, String line) { - String cline = line.substring(1); - originalTxt.add(cline); - revisedTxt.add(cline); - delLineIdx++; - addLineIdx++; - } - - private void processAddLine(MatchResult match, String line) { - String cline = line.substring(1); - revisedTxt.add(cline); - addLineIdx++; - addLineIdxList.add(new_ln - 1 + addLineIdx); - } - - private void processDelLine(MatchResult match, String line) { - String cline = line.substring(1); - originalTxt.add(cline); - delLineIdx++; - delLineIdxList.add(old_ln - 1 + delLineIdx); - } - - private void processChunk(MatchResult match, String chunkStart) { - // finalizeChunk(); - old_ln = toInteger(match, 1, 1); - old_size = toInteger(match, 2, 1); - new_ln = toInteger(match, 3, 1); - new_size = toInteger(match, 4, 1); - if (old_ln == 0) { - old_ln = 1; - } - if (new_ln == 0) { - new_ln = 1; - } - } - - private static Integer toInteger(MatchResult match, int group, int defValue) throws NumberFormatException { - return Integer.valueOf(Objects.toString(match.group(group), "" + defValue)); - } - - private void processIndex(MatchResult match, String line) { - //initFileIfNecessary(); - LOG.log(Level.FINE, "index {0}", line); - actualFile.setIndex(line.substring(6)); - } - - private void processFromFile(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setFromFile(extractFileName(line)); - actualFile.setFromTimestamp(extractTimestamp(line)); - } - - private void processToFile(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setToFile(extractFileName(line)); - actualFile.setToTimestamp(extractTimestamp(line)); - } - - private void processRenameFrom(MatchResult match, String line) { - actualFile.setRenameFrom(match.group(1)); - } - - private void processRenameTo(MatchResult match, String line) { - actualFile.setRenameTo(match.group(1)); - } - - private void processCopyFrom(MatchResult match, String line) { - actualFile.setCopyFrom(match.group(1)); - } - - private void processCopyTo(MatchResult match, String line) { - actualFile.setCopyTo(match.group(1)); - } - - private void processNewFileMode(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setNewFileMode(match.group(1)); - } - - private void processDeletedFileMode(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setDeletedFileMode(match.group(1)); - } - - private void processOldMode(MatchResult match, String line) { - actualFile.setOldMode(match.group(1)); - } - - private void processNewMode(MatchResult match, String line) { - actualFile.setNewMode(match.group(1)); - } - - private void processBinaryAdded(MatchResult match, String line) { - actualFile.setBinaryAdded(match.group(1)); - } - - private void processBinaryDeleted(MatchResult match, String line) { - actualFile.setBinaryDeleted(match.group(1)); - } - - private void processBinaryEdited(MatchResult match, String line) { - actualFile.setBinaryEdited(match.group(1)); - } - - private String extractFileName(String _line) { - Matcher matcher = TIMESTAMP_REGEXP.matcher(_line); - String line = _line; - if (matcher.find()) { - line = line.substring(0, matcher.start()); - } - line = line.split("\t")[0]; - return line.substring(4).replaceFirst("^(a|b|old|new)/", "") - .trim(); - } - - private String extractTimestamp(String line) { - Matcher matcher = TIMESTAMP_REGEXP.matcher(line); - if (matcher.find()) { - return matcher.group(); - } - return null; - } - - final class UnifiedDiffLine { - - private final Pattern pattern; - private final BiConsumer command; - private final boolean stopsHeaderParsing; - - public UnifiedDiffLine(String pattern, BiConsumer command) { - this(false, pattern, command); - } - - public UnifiedDiffLine(boolean stopsHeaderParsing, String pattern, BiConsumer command) { - this.pattern = Pattern.compile(pattern); - this.command = command; - this.stopsHeaderParsing = stopsHeaderParsing; - } - - public UnifiedDiffLine(boolean stopsHeaderParsing, Pattern pattern, BiConsumer command) { - this.pattern = pattern; - this.command = command; - this.stopsHeaderParsing = stopsHeaderParsing; - } - - public boolean validLine(String line) { - Matcher m = pattern.matcher(line); - return m.find(); - } - - public boolean processLine(String line) throws UnifiedDiffParserException { - Matcher m = pattern.matcher(line); - if (m.find()) { - command.accept(m.toMatchResult(), line); - return true; - } else { - return false; - } - } - - public boolean isStopsHeaderParsing() { - return stopsHeaderParsing; - } - - @Override - public String toString() { - return "UnifiedDiffLine{" + "pattern=" + pattern + ", stopsHeaderParsing=" + stopsHeaderParsing + '}'; - } - } + static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = + Pattern.compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@"); + static final Pattern TIMESTAMP_REGEXP = + Pattern.compile("(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d{3,})(?: [+-]\\d+)?"); + + private final InternalUnifiedDiffReader READER; + private final UnifiedDiff data = new UnifiedDiff(); + + private final UnifiedDiffLine DIFF_COMMAND = new UnifiedDiffLine(true, "^diff\\s", this::processDiff); + private final UnifiedDiffLine SIMILARITY_INDEX = + new UnifiedDiffLine(true, "^similarity index (\\d+)%$", this::processSimilarityIndex); + private final UnifiedDiffLine INDEX = + new UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$", this::processIndex); + private final UnifiedDiffLine FROM_FILE = new UnifiedDiffLine(true, "^---\\s", this::processFromFile); + private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile); + private final UnifiedDiffLine RENAME_FROM = + new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom); + private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); + + private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom); + private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo); + + private final UnifiedDiffLine NEW_FILE_MODE = + new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); + + private final UnifiedDiffLine DELETED_FILE_MODE = + new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); + private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode); + private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode); + private final UnifiedDiffLine BINARY_ADDED = + new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded); + private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine( + true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted); + private final UnifiedDiffLine BINARY_EDITED = + new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited); + private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); + private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine); + private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine); + private final UnifiedDiffLine LINE_ADD = new UnifiedDiffLine("^\\+", this::processAddLine); + + private UnifiedDiffFile actualFile; + + UnifiedDiffReader(Reader reader) { + this.READER = new InternalUnifiedDiffReader(reader); + } + + // schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file], + // [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], + // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], + // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; + private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { + // String headerTxt = ""; + // LOG.log(Level.FINE, "header parsing"); + // String line = null; + // while (READER.ready()) { + // line = READER.readLine(); + // LOG.log(Level.FINE, "parsing line {0}", line); + // if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) + // || FROM_FILE.validLine(line) || TO_FILE.validLine(line) + // || NEW_FILE_MODE.validLine(line)) { + // break; + // } else { + // headerTxt += line + "\n"; + // } + // } + // if (!"".equals(headerTxt)) { + // data.setHeader(headerTxt); + // } + + String line = READER.readLine(); + while (line != null) { + String headerTxt = ""; + LOG.log(Level.FINE, "header parsing"); + while (line != null) { + LOG.log(Level.FINE, "parsing line {0}", line); + if (validLine( + line, + DIFF_COMMAND, + SIMILARITY_INDEX, + INDEX, + FROM_FILE, + TO_FILE, + RENAME_FROM, + RENAME_TO, + COPY_FROM, + COPY_TO, + NEW_FILE_MODE, + DELETED_FILE_MODE, + OLD_MODE, + NEW_MODE, + BINARY_ADDED, + BINARY_DELETED, + BINARY_EDITED, + CHUNK)) { + break; + } else { + headerTxt += line + "\n"; + } + line = READER.readLine(); + } + if (!"".equals(headerTxt)) { + data.setHeader(headerTxt); + } + if (line != null && !CHUNK.validLine(line)) { + initFileIfNecessary(); + while (line != null && !CHUNK.validLine(line)) { + if (!processLine( + line, + DIFF_COMMAND, + SIMILARITY_INDEX, + INDEX, + FROM_FILE, + TO_FILE, + RENAME_FROM, + RENAME_TO, + COPY_FROM, + COPY_TO, + NEW_FILE_MODE, + DELETED_FILE_MODE, + OLD_MODE, + NEW_MODE, + BINARY_ADDED, + BINARY_DELETED, + BINARY_EDITED)) { + throw new UnifiedDiffParserException("expected file start line not found"); + } + line = READER.readLine(); + } + } + if (line != null) { + processLine(line, CHUNK); + while ((line = READER.readLine()) != null) { + line = checkForNoNewLineAtTheEndOfTheFile(line); + + if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { + throw new UnifiedDiffParserException("expected data line not found"); + } + if ((originalTxt.size() == old_size && revisedTxt.size() == new_size) + || (old_size == 0 + && new_size == 0 + && originalTxt.size() == this.old_ln + && revisedTxt.size() == this.new_ln)) { + finalizeChunk(); + break; + } + } + line = READER.readLine(); + + line = checkForNoNewLineAtTheEndOfTheFile(line); + } + if (line == null || (line.startsWith("--") && !line.startsWith("---"))) { + break; + } + } + + if (READER.ready()) { + String tailTxt = ""; + while (READER.ready()) { + if (tailTxt.length() > 0) { + tailTxt += "\n"; + } + tailTxt += READER.readLine(); + } + data.setTailTxt(tailTxt); + } + + return data; + } + + private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException { + if ("\\ No newline at end of file".equals(line)) { + actualFile.setNoNewLineAtTheEndOfTheFile(true); + return READER.readLine(); + } + return line; + } + + static String[] parseFileNames(String line) { + String[] split = line.split(" "); + return new String[] {split[2].replaceAll("^a/", ""), split[3].replaceAll("^b/", "")}; + } + + private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName()); + + /** + * To parse a diff file use this method. + * + * @param stream This is the diff file data. + * @return In a UnifiedDiff structure this diff file data is returned. + * @throws IOException + * @throws UnifiedDiffParserException + */ + public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException { + UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream))); + return parser.parse(); + } + + private boolean processLine(String line, UnifiedDiffLine... rules) throws UnifiedDiffParserException { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.processLine(line)) { + LOG.fine(" >>> processed rule " + rule.toString()); + return true; + } + } + LOG.warning(" >>> no rule matched " + line); + return false; + // throw new UnifiedDiffParserException("parsing error at line " + line); + } + + private boolean validLine(String line, UnifiedDiffLine... rules) { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.validLine(line)) { + LOG.fine(" >>> accepted rule " + rule.toString()); + return true; + } + } + return false; + } + + private void initFileIfNecessary() { + if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { + throw new IllegalStateException(); + } + actualFile = null; + if (actualFile == null) { + actualFile = new UnifiedDiffFile(); + data.addFile(actualFile); + } + } + + private void processDiff(MatchResult match, String line) { + // initFileIfNecessary(); + LOG.log(Level.FINE, "start {0}", line); + String[] fromTo = parseFileNames(READER.lastLine()); + actualFile.setFromFile(fromTo[0]); + actualFile.setToFile(fromTo[1]); + actualFile.setDiffCommand(line); + } + + private void processSimilarityIndex(MatchResult match, String line) { + actualFile.setSimilarityIndex(Integer.valueOf(match.group(1))); + } + + private List originalTxt = new ArrayList<>(); + private List revisedTxt = new ArrayList<>(); + private List addLineIdxList = new ArrayList<>(); + private List delLineIdxList = new ArrayList<>(); + private int old_ln; + private int old_size; + private int new_ln; + private int new_size; + private int delLineIdx = 0; + private int addLineIdx = 0; + + private void finalizeChunk() { + if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { + actualFile + .getPatch() + .addDelta(new ChangeDelta<>( + new Chunk<>(old_ln - 1, originalTxt, delLineIdxList), + new Chunk<>(new_ln - 1, revisedTxt, addLineIdxList))); + old_ln = 0; + new_ln = 0; + originalTxt.clear(); + revisedTxt.clear(); + addLineIdxList.clear(); + delLineIdxList.clear(); + delLineIdx = 0; + addLineIdx = 0; + } + } + + private void processNormalLine(MatchResult match, String line) { + String cline = line.substring(1); + originalTxt.add(cline); + revisedTxt.add(cline); + delLineIdx++; + addLineIdx++; + } + + private void processAddLine(MatchResult match, String line) { + String cline = line.substring(1); + revisedTxt.add(cline); + addLineIdx++; + addLineIdxList.add(new_ln - 1 + addLineIdx); + } + + private void processDelLine(MatchResult match, String line) { + String cline = line.substring(1); + originalTxt.add(cline); + delLineIdx++; + delLineIdxList.add(old_ln - 1 + delLineIdx); + } + + private void processChunk(MatchResult match, String chunkStart) { + // finalizeChunk(); + old_ln = toInteger(match, 1, 1); + old_size = toInteger(match, 2, 1); + new_ln = toInteger(match, 3, 1); + new_size = toInteger(match, 4, 1); + if (old_ln == 0) { + old_ln = 1; + } + if (new_ln == 0) { + new_ln = 1; + } + } + + private static Integer toInteger(MatchResult match, int group, int defValue) throws NumberFormatException { + return Integer.valueOf(Objects.toString(match.group(group), "" + defValue)); + } + + private void processIndex(MatchResult match, String line) { + // initFileIfNecessary(); + LOG.log(Level.FINE, "index {0}", line); + actualFile.setIndex(line.substring(6)); + } + + private void processFromFile(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setFromFile(extractFileName(line)); + actualFile.setFromTimestamp(extractTimestamp(line)); + } + + private void processToFile(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setToFile(extractFileName(line)); + actualFile.setToTimestamp(extractTimestamp(line)); + } + + private void processRenameFrom(MatchResult match, String line) { + actualFile.setRenameFrom(match.group(1)); + } + + private void processRenameTo(MatchResult match, String line) { + actualFile.setRenameTo(match.group(1)); + } + + private void processCopyFrom(MatchResult match, String line) { + actualFile.setCopyFrom(match.group(1)); + } + + private void processCopyTo(MatchResult match, String line) { + actualFile.setCopyTo(match.group(1)); + } + + private void processNewFileMode(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setNewFileMode(match.group(1)); + } + + private void processDeletedFileMode(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setDeletedFileMode(match.group(1)); + } + + private void processOldMode(MatchResult match, String line) { + actualFile.setOldMode(match.group(1)); + } + + private void processNewMode(MatchResult match, String line) { + actualFile.setNewMode(match.group(1)); + } + + private void processBinaryAdded(MatchResult match, String line) { + actualFile.setBinaryAdded(match.group(1)); + } + + private void processBinaryDeleted(MatchResult match, String line) { + actualFile.setBinaryDeleted(match.group(1)); + } + + private void processBinaryEdited(MatchResult match, String line) { + actualFile.setBinaryEdited(match.group(1)); + } + + private String extractFileName(String _line) { + Matcher matcher = TIMESTAMP_REGEXP.matcher(_line); + String line = _line; + if (matcher.find()) { + line = line.substring(0, matcher.start()); + } + line = line.split("\t")[0]; + return line.substring(4).replaceFirst("^(a|b|old|new)/", "").trim(); + } + + private String extractTimestamp(String line) { + Matcher matcher = TIMESTAMP_REGEXP.matcher(line); + if (matcher.find()) { + return matcher.group(); + } + return null; + } + + final class UnifiedDiffLine { + + private final Pattern pattern; + private final BiConsumer command; + private final boolean stopsHeaderParsing; + + public UnifiedDiffLine(String pattern, BiConsumer command) { + this(false, pattern, command); + } + + public UnifiedDiffLine(boolean stopsHeaderParsing, String pattern, BiConsumer command) { + this.pattern = Pattern.compile(pattern); + this.command = command; + this.stopsHeaderParsing = stopsHeaderParsing; + } + + public UnifiedDiffLine(boolean stopsHeaderParsing, Pattern pattern, BiConsumer command) { + this.pattern = pattern; + this.command = command; + this.stopsHeaderParsing = stopsHeaderParsing; + } + + public boolean validLine(String line) { + Matcher m = pattern.matcher(line); + return m.find(); + } + + public boolean processLine(String line) throws UnifiedDiffParserException { + Matcher m = pattern.matcher(line); + if (m.find()) { + command.accept(m.toMatchResult(), line); + return true; + } else { + return false; + } + } + + public boolean isStopsHeaderParsing() { + return stopsHeaderParsing; + } + + @Override + public String toString() { + return "UnifiedDiffLine{" + "pattern=" + pattern + ", stopsHeaderParsing=" + stopsHeaderParsing + '}'; + } + } } class InternalUnifiedDiffReader extends BufferedReader { - private String lastLine; + private String lastLine; - public InternalUnifiedDiffReader(Reader reader) { - super(reader); - } + public InternalUnifiedDiffReader(Reader reader) { + super(reader); + } - @Override - public String readLine() throws IOException { - lastLine = super.readLine(); - return lastLine(); - } + @Override + public String readLine() throws IOException { + lastLine = super.readLine(); + return lastLine(); + } - String lastLine() { - return lastLine; - } + String lastLine() { + return lastLine; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java index 7cac8a2c..aa572683 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java @@ -32,180 +32,195 @@ */ public class UnifiedDiffWriter { - private static final Logger LOG = Logger.getLogger(UnifiedDiffWriter.class.getName()); - - public static void write(UnifiedDiff diff, Function> originalLinesProvider, Writer writer, int contextSize) throws IOException { - Objects.requireNonNull(originalLinesProvider, "original lines provider needs to be specified"); - write(diff, originalLinesProvider, line -> { - try { - writer.append(line).append("\n"); - } catch (IOException ex) { - LOG.log(Level.SEVERE, null, ex); - } - }, contextSize); - } - - public static void write(UnifiedDiff diff, Function> originalLinesProvider, Consumer writer, int contextSize) throws IOException { - if (diff.getHeader() != null) { - writer.accept(diff.getHeader()); - } - - for (UnifiedDiffFile file : diff.getFiles()) { - List> patchDeltas = new ArrayList<>( - file.getPatch().getDeltas()); - if (!patchDeltas.isEmpty()) { - writeOrNothing(writer, file.getDiffCommand()); - if (file.getIndex() != null) { - writer.accept("index " + file.getIndex()); - } - - writer.accept("--- " + (file.getFromFile() == null ? "/dev/null" : file.getFromFile())); - - if (file.getToFile() != null) { - writer.accept("+++ " + file.getToFile()); - } - - List originalLines = originalLinesProvider.apply(file.getFromFile()); - - List> deltas = new ArrayList<>(); - - AbstractDelta delta = patchDeltas.get(0); - deltas.add(delta); // add the first Delta to the current set - // if there's more than 1 Delta, we may need to output them together - if (patchDeltas.size() > 1) { - for (int i = 1; i < patchDeltas.size(); i++) { - int position = delta.getSource().getPosition(); - - // Check if the next Delta is too close to the current - // position. - // And if it is, add it to the current set - AbstractDelta nextDelta = patchDeltas.get(i); - if ((position + delta.getSource().size() + contextSize) >= (nextDelta - .getSource().getPosition() - contextSize)) { - deltas.add(nextDelta); - } else { - // if it isn't, output the current set, - // then create a new set and add the current Delta to - // it. - processDeltas(writer, originalLines, deltas, contextSize, false); - deltas.clear(); - deltas.add(nextDelta); - } - delta = nextDelta; - } - - } - // don't forget to process the last set of Deltas - processDeltas(writer, originalLines, deltas, contextSize, - patchDeltas.size() == 1 && file.getFromFile() == null); - } - - } - if (diff.getTail() != null) { - writer.accept("--"); - writer.accept(diff.getTail()); - } - } - - private static void processDeltas(Consumer writer, - List origLines, List> deltas, - int contextSize, boolean newFile) { - List buffer = new ArrayList<>(); - int origTotal = 0; // counter for total lines output from Original - int revTotal = 0; // counter for total lines output from Original - int line; - - AbstractDelta curDelta = deltas.get(0); - - int origStart; - if (newFile) { - origStart = 0; - } else { - // NOTE: +1 to overcome the 0-offset Position - origStart = curDelta.getSource().getPosition() + 1 - contextSize; - if (origStart < 1) { - origStart = 1; - } - } - - int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; - if (revStart < 1) { - revStart = 1; - } - - // find the start of the wrapper context code - int contextStart = curDelta.getSource().getPosition() - contextSize; - if (contextStart < 0) { - contextStart = 0; // clamp to the start of the file - } - - // output the context before the first Delta - for (line = contextStart; line < curDelta.getSource().getPosition() - && line < origLines.size(); line++) { // - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - // output the first Delta - getDeltaText(txt -> buffer.add(txt), curDelta); - origTotal += curDelta.getSource().getLines().size(); - revTotal += curDelta.getTarget().getLines().size(); - - int deltaIndex = 1; - while (deltaIndex < deltas.size()) { // for each of the other Deltas - AbstractDelta nextDelta = deltas.get(deltaIndex); - int intermediateStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = intermediateStart; line < nextDelta.getSource().getPosition() - && line < origLines.size(); line++) { - // output the code between the last Delta and this one - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - getDeltaText(txt -> buffer.add(txt), nextDelta); // output the Delta - origTotal += nextDelta.getSource().getLines().size(); - revTotal += nextDelta.getTarget().getLines().size(); - curDelta = nextDelta; - deltaIndex++; - } - - // Now output the post-Delta context code, clamping the end of the file - contextStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = contextStart; (line < (contextStart + contextSize)) - && (line < origLines.size()); line++) { - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // Create and insert the block header, conforming to the Unified Diff - // standard - writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@"); - buffer.forEach(txt -> { - writer.accept(txt); - }); - } - - /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. - * - * @param writer consumer for the list of String lines of code - * @param delta the Delta to output - */ - private static void getDeltaText(Consumer writer, AbstractDelta delta) { - for (String line : delta.getSource().getLines()) { - writer.accept("-" + line); - } - for (String line : delta.getTarget().getLines()) { - writer.accept("+" + line); - } - } - - private static void writeOrNothing(Consumer writer, String str) throws IOException { - if (str != null) { - writer.accept(str); - } - } + private static final Logger LOG = Logger.getLogger(UnifiedDiffWriter.class.getName()); + + public static void write( + UnifiedDiff diff, Function> originalLinesProvider, Writer writer, int contextSize) + throws IOException { + Objects.requireNonNull(originalLinesProvider, "original lines provider needs to be specified"); + write( + diff, + originalLinesProvider, + line -> { + try { + writer.append(line).append("\n"); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + } + }, + contextSize); + } + + public static void write( + UnifiedDiff diff, + Function> originalLinesProvider, + Consumer writer, + int contextSize) + throws IOException { + if (diff.getHeader() != null) { + writer.accept(diff.getHeader()); + } + + for (UnifiedDiffFile file : diff.getFiles()) { + List> patchDeltas = + new ArrayList<>(file.getPatch().getDeltas()); + if (!patchDeltas.isEmpty()) { + writeOrNothing(writer, file.getDiffCommand()); + if (file.getIndex() != null) { + writer.accept("index " + file.getIndex()); + } + + writer.accept("--- " + (file.getFromFile() == null ? "/dev/null" : file.getFromFile())); + + if (file.getToFile() != null) { + writer.accept("+++ " + file.getToFile()); + } + + List originalLines = originalLinesProvider.apply(file.getFromFile()); + + List> deltas = new ArrayList<>(); + + AbstractDelta delta = patchDeltas.get(0); + deltas.add(delta); // add the first Delta to the current set + // if there's more than 1 Delta, we may need to output them together + if (patchDeltas.size() > 1) { + for (int i = 1; i < patchDeltas.size(); i++) { + int position = delta.getSource().getPosition(); + + // Check if the next Delta is too close to the current + // position. + // And if it is, add it to the current set + AbstractDelta nextDelta = patchDeltas.get(i); + if ((position + delta.getSource().size() + contextSize) + >= (nextDelta.getSource().getPosition() - contextSize)) { + deltas.add(nextDelta); + } else { + // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + processDeltas(writer, originalLines, deltas, contextSize, false); + deltas.clear(); + deltas.add(nextDelta); + } + delta = nextDelta; + } + } + // don't forget to process the last set of Deltas + processDeltas( + writer, + originalLines, + deltas, + contextSize, + patchDeltas.size() == 1 && file.getFromFile() == null); + } + } + if (diff.getTail() != null) { + writer.accept("--"); + writer.accept(diff.getTail()); + } + } + + private static void processDeltas( + Consumer writer, + List origLines, + List> deltas, + int contextSize, + boolean newFile) { + List buffer = new ArrayList<>(); + int origTotal = 0; // counter for total lines output from Original + int revTotal = 0; // counter for total lines output from Original + int line; + + AbstractDelta curDelta = deltas.get(0); + + int origStart; + if (newFile) { + origStart = 0; + } else { + // NOTE: +1 to overcome the 0-offset Position + origStart = curDelta.getSource().getPosition() + 1 - contextSize; + if (origStart < 1) { + origStart = 1; + } + } + + int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; + if (revStart < 1) { + revStart = 1; + } + + // find the start of the wrapper context code + int contextStart = curDelta.getSource().getPosition() - contextSize; + if (contextStart < 0) { + contextStart = 0; // clamp to the start of the file + } + + // output the context before the first Delta + for (line = contextStart; line < curDelta.getSource().getPosition() && line < origLines.size(); line++) { // + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + // output the first Delta + getDeltaText(txt -> buffer.add(txt), curDelta); + origTotal += curDelta.getSource().getLines().size(); + revTotal += curDelta.getTarget().getLines().size(); + + int deltaIndex = 1; + while (deltaIndex < deltas.size()) { // for each of the other Deltas + AbstractDelta nextDelta = deltas.get(deltaIndex); + int intermediateStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = intermediateStart; + line < nextDelta.getSource().getPosition() && line < origLines.size(); + line++) { + // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + getDeltaText(txt -> buffer.add(txt), nextDelta); // output the Delta + origTotal += nextDelta.getSource().getLines().size(); + revTotal += nextDelta.getTarget().getLines().size(); + curDelta = nextDelta; + deltaIndex++; + } + + // Now output the post-Delta context code, clamping the end of the file + contextStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // Create and insert the block header, conforming to the Unified Diff + // standard + writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@"); + buffer.forEach(txt -> { + writer.accept(txt); + }); + } + + /** + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. + * + * @param writer consumer for the list of String lines of code + * @param delta the Delta to output + */ + private static void getDeltaText(Consumer writer, AbstractDelta delta) { + for (String line : delta.getSource().getLines()) { + writer.accept("-" + line); + } + for (String line : delta.getTarget().getLines()) { + writer.accept("+" + line); + } + } + + private static void writeOrNothing(Consumer writer, String str) throws IOException { + if (str != null) { + writer.accept(str); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index 3a4a6995..8d133622 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -1,9 +1,15 @@ package com.github.difflib; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; import com.github.difflib.patch.DeleteDelta; -import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.EqualDelta; import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; @@ -19,224 +25,225 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static java.util.stream.Collectors.toList; import java.util.zip.ZipFile; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class DiffUtilsTest { - @Test - public void testDiff_Insert() { - final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays. - asList("hhh", "jjj", "kkk")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof InsertDelta); - assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_Delete() { - final Patch patch = DiffUtils.diff(Arrays.asList("ddd", "fff", "ggg"), Arrays. - asList("ggg")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof DeleteDelta); - assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource()); - assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getTarget()); - } - - @Test - public void testDiff_Change() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc"); - final List changeTest_to = Arrays.asList("aaa", "zzz", "ccc"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget()); - } - - @Test - public void testDiff_EmptyList() { - final Patch patch = DiffUtils.diff(new ArrayList<>(), new ArrayList<>()); - assertNotNull(patch); - assertEquals(0, patch.getDeltas().size()); - } - - @Test - public void testDiff_EmptyListWithNonEmpty() { - final Patch patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof InsertDelta); - } - - @Test - public void testDiffInline() { - final Patch patch = DiffUtils.diffInline("", "test"); - assertEquals(1, patch.getDeltas().size()); - assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); - assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); - assertEquals("test", patch.getDeltas().get(0).getTarget().getLines().get(0)); - } - - @Test - public void testDiffInline2() { - final Patch patch = DiffUtils.diffInline("es", "fest"); - assertEquals(2, patch.getDeltas().size()); - assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); - assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); - assertEquals(2, patch.getDeltas().get(1).getSource().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); - assertEquals(0, patch.getDeltas().get(1).getSource().getLines().size()); - assertEquals("f", patch.getDeltas().get(0).getTarget().getLines().get(0)); - assertEquals("t", patch.getDeltas().get(1).getTarget().getLines().get(0)); - } - - @Test - public void testDiffIntegerList() { - List original = Arrays.asList(1, 2, 3, 4, 5); - List revised = Arrays.asList(2, 3, 4, 6); - - final Patch patch = DiffUtils.diff(original, revised); - - for (AbstractDelta delta : patch.getDeltas()) { - System.out.println(delta); - } - - assertEquals(2, patch.getDeltas().size()); - assertEquals("[DeleteDelta, position: 0, lines: [1]]", patch.getDeltas().get(0).toString()); - assertEquals("[ChangeDelta, position: 4, lines: [5] to [6]]", patch.getDeltas().get(1).toString()); - } - - @Test - public void testDiffMissesChangeForkDnaumenkoIssue31() { - List original = Arrays.asList("line1", "line2", "line3"); - List revised = Arrays.asList("line1", "line2-2", "line4"); - - Patch patch = DiffUtils.diff(original, revised); - assertEquals(1, patch.getDeltas().size()); - assertEquals("[ChangeDelta, position: 1, lines: [line2, line3] to [line2-2, line4]]", patch.getDeltas().get(0).toString()); - } - - /** - * To test this, the greedy Myer algorithm is not suitable. - */ - @Test - @Disabled - public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException { - ZipFile zip = new ZipFile(TestConstants.MOCK_FOLDER + "/large_dataset1.zip"); - - Patch patch = DiffUtils.diff( - readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))), - readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb")))); - - assertEquals(1, patch.getDeltas().size()); - } - - public static List readStringListFromInputStream(InputStream is) throws IOException { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { - - return reader.lines().collect(toList()); - } - } - - @Test - public void testDiffMyersExample1() { - final Patch patch = DiffUtils.diff(Arrays.asList("A", "B", "C", "A", "B", "B", "A"), Arrays.asList("C", "B", "A", "B", "A", "C")); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiff_Equal() { - final Patch patch = DiffUtils.diff( - Arrays.asList("hhh", "jjj", "kkk"), - Arrays.asList("hhh", "jjj", "kkk"), true); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_InsertWithEqual() { - final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays. - asList("hhh", "jjj", "kkk"), true); - assertNotNull(patch); - assertEquals(2, patch.getDeltas().size()); - - AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getTarget()); - - delta = patch.getDeltas().get(1); - assertTrue(delta instanceof InsertDelta); - assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_ProblemIssue42() { - final Patch patch = DiffUtils.diff( - Arrays.asList("The", "dog", "is", "brown"), - Arrays.asList("The", "fox", "is", "down"), true); - - System.out.println(patch); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - - - assertThat(patch.getDeltas()).extracting(d -> d.getType().name()) - .containsExactly("EQUAL", "CHANGE", "EQUAL", "CHANGE"); - - AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getTarget()); - - delta = patch.getDeltas().get(1); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(1, Arrays.asList("dog")), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("fox")), delta.getTarget()); - - delta = patch.getDeltas().get(2); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getSource()); - assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getTarget()); - - delta = patch.getDeltas().get(3); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); - assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); - } - - @Test - public void testDiffPatchIssue189Problem() throws IOException { - String original = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt"))); - String revised = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt"))); - - Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); - - assertEquals(1, patch.getDeltas().size()); - } + @Test + public void testDiff_Insert() { + final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof InsertDelta); + assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_Delete() { + final Patch patch = DiffUtils.diff(Arrays.asList("ddd", "fff", "ggg"), Arrays.asList("ggg")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof DeleteDelta); + assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource()); + assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getTarget()); + } + + @Test + public void testDiff_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc"); + final List changeTest_to = Arrays.asList("aaa", "zzz", "ccc"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget()); + } + + @Test + public void testDiff_EmptyList() { + final Patch patch = DiffUtils.diff(new ArrayList<>(), new ArrayList<>()); + assertNotNull(patch); + assertEquals(0, patch.getDeltas().size()); + } + + @Test + public void testDiff_EmptyListWithNonEmpty() { + final Patch patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof InsertDelta); + } + + @Test + public void testDiffInline() { + final Patch patch = DiffUtils.diffInline("", "test"); + assertEquals(1, patch.getDeltas().size()); + assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); + assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); + assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); + assertEquals("test", patch.getDeltas().get(0).getTarget().getLines().get(0)); + } + + @Test + public void testDiffInline2() { + final Patch patch = DiffUtils.diffInline("es", "fest"); + assertEquals(2, patch.getDeltas().size()); + assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); + assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); + assertEquals(2, patch.getDeltas().get(1).getSource().getPosition()); + assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); + assertEquals(0, patch.getDeltas().get(1).getSource().getLines().size()); + assertEquals("f", patch.getDeltas().get(0).getTarget().getLines().get(0)); + assertEquals("t", patch.getDeltas().get(1).getTarget().getLines().get(0)); + } + + @Test + public void testDiffIntegerList() { + List original = Arrays.asList(1, 2, 3, 4, 5); + List revised = Arrays.asList(2, 3, 4, 6); + + final Patch patch = DiffUtils.diff(original, revised); + + for (AbstractDelta delta : patch.getDeltas()) { + System.out.println(delta); + } + + assertEquals(2, patch.getDeltas().size()); + assertEquals( + "[DeleteDelta, position: 0, lines: [1]]", + patch.getDeltas().get(0).toString()); + assertEquals( + "[ChangeDelta, position: 4, lines: [5] to [6]]", + patch.getDeltas().get(1).toString()); + } + + @Test + public void testDiffMissesChangeForkDnaumenkoIssue31() { + List original = Arrays.asList("line1", "line2", "line3"); + List revised = Arrays.asList("line1", "line2-2", "line4"); + + Patch patch = DiffUtils.diff(original, revised); + assertEquals(1, patch.getDeltas().size()); + assertEquals( + "[ChangeDelta, position: 1, lines: [line2, line3] to [line2-2, line4]]", + patch.getDeltas().get(0).toString()); + } + + /** + * To test this, the greedy Myer algorithm is not suitable. + */ + @Test + @Disabled + public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException { + ZipFile zip = new ZipFile(TestConstants.MOCK_FOLDER + "/large_dataset1.zip"); + + Patch patch = DiffUtils.diff( + readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))), + readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb")))); + + assertEquals(1, patch.getDeltas().size()); + } + + public static List readStringListFromInputStream(InputStream is) throws IOException { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { + + return reader.lines().collect(toList()); + } + } + + @Test + public void testDiffMyersExample1() { + final Patch patch = DiffUtils.diff( + Arrays.asList("A", "B", "C", "A", "B", "B", "A"), Arrays.asList("C", "B", "A", "B", "A", "C")); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiff_Equal() { + final Patch patch = + DiffUtils.diff(Arrays.asList("hhh", "jjj", "kkk"), Arrays.asList("hhh", "jjj", "kkk"), true); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_InsertWithEqual() { + final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk"), true); + assertNotNull(patch); + assertEquals(2, patch.getDeltas().size()); + + AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getTarget()); + + delta = patch.getDeltas().get(1); + assertTrue(delta instanceof InsertDelta); + assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_ProblemIssue42() { + final Patch patch = DiffUtils.diff( + Arrays.asList("The", "dog", "is", "brown"), Arrays.asList("The", "fox", "is", "down"), true); + + System.out.println(patch); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + + assertThat(patch.getDeltas()) + .extracting(d -> d.getType().name()) + .containsExactly("EQUAL", "CHANGE", "EQUAL", "CHANGE"); + + AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getTarget()); + + delta = patch.getDeltas().get(1); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(1, Arrays.asList("dog")), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("fox")), delta.getTarget()); + + delta = patch.getDeltas().get(2); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getSource()); + assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getTarget()); + + delta = patch.getDeltas().get(3); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); + assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); + } + + @Test + public void testDiffPatchIssue189Problem() throws IOException { + String original = new String(Files.readAllBytes( + Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt"))); + String revised = new String(Files.readAllBytes( + Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt"))); + + Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + assertEquals(1, patch.getDeltas().size()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java index b8667818..3e2357da 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java @@ -1,5 +1,12 @@ package com.github.difflib; +import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.patch.Chunk; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; @@ -11,219 +18,211 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static java.util.stream.Collectors.joining; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Test; public class GenerateUnifiedDiffTest { - public static List fileToLines(String filename) throws FileNotFoundException, IOException { - List lines = new ArrayList<>(); - String line = ""; - try (BufferedReader in = new BufferedReader(new FileReader(filename))) { - while ((line = in.readLine()) != null) { - lines.add(line); - } - } - return lines; - } - - @Test - public void testGenerateUnified() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); - - verify(origLines, revLines, "original.txt", "revised.txt"); - } - - @Test - public void testGenerateUnifiedWithOneDelta() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); - - verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); - } - - @Test - public void testGenerateUnifiedDiffWithoutAnyDeltas() { - List test = Arrays.asList("abc"); - List testRevised = Arrays.asList("abc2"); - Patch patch = DiffUtils.diff(test, testRevised); - String unifiedDiffTxt = String.join("\n", UnifiedDiffUtils.generateUnifiedDiff("abc1", "abc2", test, patch, 0)); - System.out.println(unifiedDiffTxt); - - assertThat(unifiedDiffTxt) - .as("original filename should be abc1").contains("--- abc1") - .as("revised filename should be abc2").contains("+++ abc2"); - } - - @Test - public void testDiff_Issue10() throws IOException { - final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); - final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); - final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines); - try { - DiffUtils.patch(baseLines, p); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - /** - * Issue 12 - */ - @Test - public void testPatchWithNoDeltas() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); - verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); - } - - @Test - public void testDiff5() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); - verify(lines1, lines2, "5A.txt", "5B.txt"); - } - - /** - * Issue 19 - */ - @Test - public void testDiffWithHeaderLineInText() { - List original = new ArrayList<>(); - List revised = new ArrayList<>(); - - original.add("test line1"); - original.add("test line2"); - original.add("test line 4"); - original.add("test line 5"); - - revised.add("test line1"); - revised.add("test line2"); - revised.add("@@ -2,6 +2,7 @@"); - revised.add("test line 4"); - revised.add("test line 5"); - - Patch patch = DiffUtils.diff(original, revised); - List udiff = UnifiedDiffUtils.generateUnifiedDiff("original", "revised", - original, patch, 10); - UnifiedDiffUtils.parseUnifiedDiff(udiff); - } - - /** - * Issue 47 - */ - @Test - public void testNewFileCreation() { - List original = new ArrayList<>(); - List revised = new ArrayList<>(); - - revised.add("line1"); - revised.add("line2"); - - Patch patch = DiffUtils.diff(original, revised); - List udiff = UnifiedDiffUtils.generateUnifiedDiff(null, "revised", - original, patch, 10); - - assertEquals("--- /dev/null", udiff.get(0)); - assertEquals("+++ revised", udiff.get(1)); - assertEquals("@@ -0,0 +1,2 @@", udiff.get(2)); - - UnifiedDiffUtils.parseUnifiedDiff(udiff); - } - - /** - * Issue 89 - */ - @Test - public void testChangePosition() throws IOException { - final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue89_patch.txt"); - final Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patchLines); - List realRemoveListOne = Collections.singletonList(3); - List realAddListOne = Arrays.asList(3, 7, 8, 9, 10, 11, 12, 13, 14); - validateChangePosition(patch, 0, realRemoveListOne, realAddListOne); - List realRemoveListTwo = new ArrayList<>(); - List realAddListTwo = Arrays.asList(27, 28); - validateChangePosition(patch, 1, realRemoveListTwo, realAddListTwo); - - } - - private void validateChangePosition(Patch patch, int index, List realRemoveList, - List realAddList ) { - final Chunk originChunk = patch.getDeltas().get(index).getSource(); - List removeList = originChunk.getChangePosition(); - assertEquals(realRemoveList.size(), removeList.size()); - for (Integer ele: realRemoveList) { - assertTrue(realRemoveList.contains(ele)); - } - for (Integer ele: removeList) { - assertTrue(realAddList.contains(ele)); - } - final Chunk targetChunk = patch.getDeltas().get(index).getTarget(); - List addList = targetChunk.getChangePosition(); - assertEquals(realAddList.size(), addList.size()); - for (Integer ele: realAddList) { - assertTrue(addList.contains(ele)); - } - for (Integer ele: addList) { - assertTrue(realAddList.contains(ele)); - } - } - - private void verify(List origLines, List revLines, - String originalFile, String revisedFile) { - Patch patch = DiffUtils.diff(origLines, revLines); - List unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff(originalFile, revisedFile, - origLines, patch, 10); - - System.out.println(unifiedDiff.stream().collect(joining("\n"))); - - Patch fromUnifiedPatch = UnifiedDiffUtils.parseUnifiedDiff(unifiedDiff); - List patchedLines; - try { - patchedLines = fromUnifiedPatch.applyTo(origLines); - assertEquals(revLines.size(), patchedLines.size()); - for (int i = 0; i < revLines.size(); i++) { - String l1 = revLines.get(i); - String l2 = patchedLines.get(i); - if (!l1.equals(l2)) { - fail("Line " + (i + 1) + " of the patched file did not match the revised original"); - } - } - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - - @Test - public void testFailingPatchByException() throws IOException { - final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); - final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); - final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines); - - //make original not fitting - baseLines.set(40, baseLines.get(40) + " corrupted "); - - assertThrows(PatchFailedException.class, () -> DiffUtils.patch(baseLines, p)); - } - - @Test - public void testWrongContextLength() throws IOException { - List original = fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_original.txt"); - List revised = fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_revised.txt"); - - Patch patch = DiffUtils.diff(original, revised); - List udiff = UnifiedDiffUtils.generateUnifiedDiff("a/$filename", "b/$filename", - original, patch, 3); - - //System.out.println(udiff.stream().collect(joining("\n"))); - - assertThat(udiff).contains("@@ -1,4 +1,4 @@"); - } + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } + + @Test + public void testGenerateUnified() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + + verify(origLines, revLines, "original.txt", "revised.txt"); + } + + @Test + public void testGenerateUnifiedWithOneDelta() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); + + verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); + } + + @Test + public void testGenerateUnifiedDiffWithoutAnyDeltas() { + List test = Arrays.asList("abc"); + List testRevised = Arrays.asList("abc2"); + Patch patch = DiffUtils.diff(test, testRevised); + String unifiedDiffTxt = String.join("\n", UnifiedDiffUtils.generateUnifiedDiff("abc1", "abc2", test, patch, 0)); + System.out.println(unifiedDiffTxt); + + assertThat(unifiedDiffTxt) + .as("original filename should be abc1") + .contains("--- abc1") + .as("revised filename should be abc2") + .contains("+++ abc2"); + } + + @Test + public void testDiff_Issue10() throws IOException { + final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); + final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); + final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines); + try { + DiffUtils.patch(baseLines, p); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + /** + * Issue 12 + */ + @Test + public void testPatchWithNoDeltas() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); + verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); + } + + @Test + public void testDiff5() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); + verify(lines1, lines2, "5A.txt", "5B.txt"); + } + + /** + * Issue 19 + */ + @Test + public void testDiffWithHeaderLineInText() { + List original = new ArrayList<>(); + List revised = new ArrayList<>(); + + original.add("test line1"); + original.add("test line2"); + original.add("test line 4"); + original.add("test line 5"); + + revised.add("test line1"); + revised.add("test line2"); + revised.add("@@ -2,6 +2,7 @@"); + revised.add("test line 4"); + revised.add("test line 5"); + + Patch patch = DiffUtils.diff(original, revised); + List udiff = UnifiedDiffUtils.generateUnifiedDiff("original", "revised", original, patch, 10); + UnifiedDiffUtils.parseUnifiedDiff(udiff); + } + + /** + * Issue 47 + */ + @Test + public void testNewFileCreation() { + List original = new ArrayList<>(); + List revised = new ArrayList<>(); + + revised.add("line1"); + revised.add("line2"); + + Patch patch = DiffUtils.diff(original, revised); + List udiff = UnifiedDiffUtils.generateUnifiedDiff(null, "revised", original, patch, 10); + + assertEquals("--- /dev/null", udiff.get(0)); + assertEquals("+++ revised", udiff.get(1)); + assertEquals("@@ -0,0 +1,2 @@", udiff.get(2)); + + UnifiedDiffUtils.parseUnifiedDiff(udiff); + } + + /** + * Issue 89 + */ + @Test + public void testChangePosition() throws IOException { + final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue89_patch.txt"); + final Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patchLines); + List realRemoveListOne = Collections.singletonList(3); + List realAddListOne = Arrays.asList(3, 7, 8, 9, 10, 11, 12, 13, 14); + validateChangePosition(patch, 0, realRemoveListOne, realAddListOne); + List realRemoveListTwo = new ArrayList<>(); + List realAddListTwo = Arrays.asList(27, 28); + validateChangePosition(patch, 1, realRemoveListTwo, realAddListTwo); + } + + private void validateChangePosition( + Patch patch, int index, List realRemoveList, List realAddList) { + final Chunk originChunk = patch.getDeltas().get(index).getSource(); + List removeList = originChunk.getChangePosition(); + assertEquals(realRemoveList.size(), removeList.size()); + for (Integer ele : realRemoveList) { + assertTrue(realRemoveList.contains(ele)); + } + for (Integer ele : removeList) { + assertTrue(realAddList.contains(ele)); + } + final Chunk targetChunk = patch.getDeltas().get(index).getTarget(); + List addList = targetChunk.getChangePosition(); + assertEquals(realAddList.size(), addList.size()); + for (Integer ele : realAddList) { + assertTrue(addList.contains(ele)); + } + for (Integer ele : addList) { + assertTrue(realAddList.contains(ele)); + } + } + + private void verify(List origLines, List revLines, String originalFile, String revisedFile) { + Patch patch = DiffUtils.diff(origLines, revLines); + List unifiedDiff = + UnifiedDiffUtils.generateUnifiedDiff(originalFile, revisedFile, origLines, patch, 10); + + System.out.println(unifiedDiff.stream().collect(joining("\n"))); + + Patch fromUnifiedPatch = UnifiedDiffUtils.parseUnifiedDiff(unifiedDiff); + List patchedLines; + try { + patchedLines = fromUnifiedPatch.applyTo(origLines); + assertEquals(revLines.size(), patchedLines.size()); + for (int i = 0; i < revLines.size(); i++) { + String l1 = revLines.get(i); + String l2 = patchedLines.get(i); + if (!l1.equals(l2)) { + fail("Line " + (i + 1) + " of the patched file did not match the revised original"); + } + } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testFailingPatchByException() throws IOException { + final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); + final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); + final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines); + + // make original not fitting + baseLines.set(40, baseLines.get(40) + " corrupted "); + + assertThrows(PatchFailedException.class, () -> DiffUtils.patch(baseLines, p)); + } + + @Test + public void testWrongContextLength() throws IOException { + List original = + fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_original.txt"); + List revised = + fileToLines(TestConstants.BASE_FOLDER_RESOURCES + "com/github/difflib/text/issue_119_revised.txt"); + + Patch patch = DiffUtils.diff(original, revised); + List udiff = UnifiedDiffUtils.generateUnifiedDiff("a/$filename", "b/$filename", original, patch, 3); + + // System.out.println(udiff.stream().collect(joining("\n"))); + + assertThat(udiff).contains("@@ -1,4 +1,4 @@"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/TestConstants.java b/java-diff-utils/src/test/java/com/github/difflib/TestConstants.java index ba6d754e..f7035864 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/TestConstants.java +++ b/java-diff-utils/src/test/java/com/github/difflib/TestConstants.java @@ -8,12 +8,11 @@ */ public final class TestConstants { - public static final String BASE_FOLDER_RESOURCES = "target/test-classes/"; - /** - * The base folder containing the test files. - */ - public static final String MOCK_FOLDER = BASE_FOLDER_RESOURCES + "/mocks/"; + public static final String BASE_FOLDER_RESOURCES = "target/test-classes/"; + /** + * The base folder containing the test files. + */ + public static final String MOCK_FOLDER = BASE_FOLDER_RESOURCES + "/mocks/"; - private TestConstants() { - } + private TestConstants() {} } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 1e233a8c..f315f2b9 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -15,13 +15,14 @@ */ package com.github.difflib.algorithm.myers; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; /** @@ -30,43 +31,48 @@ */ public class MyersDiffTest { - @Test - public void testDiffMyersExample1Forward() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiffMyersExample1ForwardWithListener() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - - List logdata = new ArrayList<>(); - final Patch patch = Patch.generate(original, revised, - new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = + Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate( + original, revised, new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } - @Override - public void diffEnd() { - logdata.add("end"); - } - })); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - System.out.println(logdata); - assertEquals(8, logdata.size()); - } + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + System.out.println(logdata); + assertEquals(8, logdata.size()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java index b63876ac..87f80134 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java @@ -15,77 +15,83 @@ */ package com.github.difflib.algorithm.myers; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.*; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.stream.Collectors.toList; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; /** * * @author tw */ public class MyersDiffWithLinearSpaceTest { - - @Test - public void testDiffMyersExample1Forward() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiffWithLinearSpace().computeDiff(original, revised, null)); - assertNotNull(patch); - System.out.println(patch); - assertEquals(5, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiffMyersExample1ForwardWithListener() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - - List logdata = new ArrayList<>(); - final Patch patch = Patch.generate(original, revised, - new MyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = Patch.generate( + original, revised, new MyersDiffWithLinearSpace().computeDiff(original, revised, null)); + assertNotNull(patch); + System.out.println(patch); + assertEquals(5, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate( + original, + revised, + new MyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } + + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } + + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + System.out.println(patch); + assertEquals(5, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + System.out.println(logdata); + assertEquals(11, logdata.size()); + } + + @Test + public void testPerformanceProblemsIssue124() { + List old = Arrays.asList("abcd"); + List newl = + IntStream.range(0, 90000).boxed().map(i -> i.toString()).collect(toList()); - @Override - public void diffEnd() { - logdata.add("end"); - } - })); - assertNotNull(patch); - System.out.println(patch); - assertEquals(5, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - System.out.println(logdata); - assertEquals(11, logdata.size()); - } - - - @Test - public void testPerformanceProblemsIssue124() { - List old = Arrays.asList("abcd"); - List newl = IntStream.range(0, 90000) - .boxed() - .map(i -> i.toString()) - .collect(toList()); - - long start = System.currentTimeMillis(); - Patch diff = DiffUtils.diff(old, newl, new MyersDiffWithLinearSpace()); - long end = System.currentTimeMillis(); - System.out.println("Finished in " + (end - start) + "ms and resulted " + diff.getDeltas().size() + " deltas"); - } + long start = System.currentTimeMillis(); + Patch diff = DiffUtils.diff(old, newl, new MyersDiffWithLinearSpace()); + long end = System.currentTimeMillis(); + System.out.println("Finished in " + (end - start) + "ms and resulted " + + diff.getDeltas().size() + " deltas"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java index 522eff58..92668ce8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java @@ -1,10 +1,11 @@ package com.github.difflib.algorithm.myers; -import com.github.difflib.patch.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -17,378 +18,378 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; - import org.junit.jupiter.api.Test; -import com.github.difflib.DiffUtils; - public class WithMyersDiffWithLinearSpacePatchTest { - @Test - public void testPatch_Insert() { - final List insertTest_from = Arrays.asList("hhh"); - final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); - - final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @Test - public void testPatch_Delete() { - final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); - final List deleteTest_to = Arrays.asList("ggg"); - - final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @Test - public void testPatch_Change() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - // region testPatch_fuzzyApply utils - - private List intRange(int count) { - return IntStream.range(0, count) - .mapToObj(Integer::toString) - .collect(Collectors.toList()); - } - - @SafeVarargs - private final List join(List... lists) { - return Arrays.stream(lists).flatMap(Collection::stream).collect(Collectors.toList()); - } - - private static class FuzzyApplyTestPair { - public final List from; - public final List to; - public final int requiredFuzz; - - private FuzzyApplyTestPair(List from, List to, int requiredFuzz) { - this.from = from; - this.to = to; - this.requiredFuzz = requiredFuzz; - } - } - - // endregion - - @Test - public void fuzzyApply() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(6, deltaFrom), - new Chunk<>(6, deltaTo))); - - //noinspection unchecked - List[] moves = new List[] { - intRange(6), // no patch move - intRange(3), // forward patch move - intRange(9), // backward patch move - intRange(0), // apply to the first - }; - - for (FuzzyApplyTestPair pair : FUZZY_APPLY_TEST_PAIRS) { - for (List move : moves) { - List from = join(move, pair.from); - List to = join(move, pair.to); - - for (int i = 0; i < pair.requiredFuzz; i++) { - int maxFuzz = i; - assertThrows(PatchFailedException.class, () -> - patch.applyFuzzy(from, maxFuzz), - () -> "fail for " + from + " -> " + to + " for fuzz " + maxFuzz + " required " + pair.requiredFuzz); - } - for (int i = pair.requiredFuzz; i < 4; i++) { - int maxFuzz = i; - assertEquals(to, patch.applyFuzzy(from, maxFuzz), - () -> "with " + maxFuzz); - } - } - } - } - - @Test - public void fuzzyApplyTwoSideBySidePatches() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(0, deltaFrom), - new Chunk<>(0, deltaTo))); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(6, deltaFrom), - new Chunk<>(6, deltaTo))); - - - assertEquals(join(deltaTo, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom), 0)); - } - - @Test - public void fuzzyApplyToNearest() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(0, deltaFrom), - new Chunk<>(0, deltaTo))); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(10, deltaFrom), - new Chunk<>(10, deltaTo))); - - assertEquals(join(deltaTo, deltaFrom, deltaTo), - patch.applyFuzzy(join(deltaFrom, deltaFrom, deltaFrom), 0)); - assertEquals(join(intRange(1), deltaTo, deltaFrom, deltaTo), - patch.applyFuzzy(join(intRange(1), deltaFrom, deltaFrom, deltaFrom), 0)); - } - - @Test - public void testPatch_Serializable() throws IOException, ClassNotFoundException { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(baos); - out.writeObject(patch); - out.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bais); - Patch result = (Patch) in.readObject(); - in.close(); - - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - - } - - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - - changeTest_from.set(2, "CDC"); - - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(11, data.size()); - - assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - static class FuzzyApplyTestDataGenerator { - private static String createList(List values) { - return values.stream() - .map(x -> '"' + x + '"') - .collect(Collectors.joining(", ", "Arrays.asList(", ")")); - } - - public static void main(String[] args) { - String[] deltaFrom = new String[] { "aaa", "bbb", "ccc", "ddd", "eee", "fff" }; - String[] deltaTo = new String[] { "aaa", "bbb", "cxc", "dxd", "eee", "fff" }; - - List pairs = new ArrayList<>(); - - // create test data. - // Brute-force search - String[] changedValue = new String[]{"axa", "bxb", "czc", "dzd", "exe", "fxf"}; - for (int i = 0; i < 1 << 6; i++) { - if ((i & 0b001100) != 0 && (i & 0b001100) != 0b001100) { - continue; - } - - String[] from = deltaFrom.clone(); - String[] to = deltaTo.clone(); - for (int j = 0; j < 6; j++) { - if ((i & (1 << j)) != 0) { - from[j] = changedValue[j]; - to[j] = changedValue[j]; - } - } - - int requiredFuzz; - if ((i & 0b001100) != 0) { - requiredFuzz = 3; - } else if ((i & 0b010010) != 0) { - requiredFuzz = 2; - } else if ((i & 0b100001) != 0) { - requiredFuzz = 1; - } else { - requiredFuzz = 0; - } - - pairs.add(new FuzzyApplyTestPair(Arrays.asList(from), Arrays.asList(to), requiredFuzz)); - } - pairs.sort(Comparator.comparingInt(a -> a.requiredFuzz)); - System.out.println("FuzzyApplyTestPair[] pairs = new FuzzyApplyTestPair[] {"); - for (FuzzyApplyTestPair pair : pairs) { - System.out.println(" new FuzzyApplyTestPair("); - System.out.println(" " + createList(pair.from) + ","); - System.out.println(" " + createList(pair.to) + ","); - System.out.println(" " + pair.requiredFuzz + "),"); - } - System.out.println("};"); - } - } - - private static final FuzzyApplyTestPair[] FUZZY_APPLY_TEST_PAIRS = new FuzzyApplyTestPair[] { - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"), - 0), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fff"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fxf"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fxf"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), - 3), - }; + @Test + public void testPatch_Insert() { + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = + DiffUtils.diff(insertTest_from, insertTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Delete() { + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = + DiffUtils.diff(deleteTest_from, deleteTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + // region testPatch_fuzzyApply utils + + private List intRange(int count) { + return IntStream.range(0, count).mapToObj(Integer::toString).collect(Collectors.toList()); + } + + @SafeVarargs + private final List join(List... lists) { + return Arrays.stream(lists).flatMap(Collection::stream).collect(Collectors.toList()); + } + + private static class FuzzyApplyTestPair { + public final List from; + public final List to; + public final int requiredFuzz; + + private FuzzyApplyTestPair(List from, List to, int requiredFuzz) { + this.from = from; + this.to = to; + this.requiredFuzz = requiredFuzz; + } + } + + // endregion + + @Test + public void fuzzyApply() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(6, deltaFrom), new Chunk<>(6, deltaTo))); + + //noinspection unchecked + List[] moves = new List[] { + intRange(6), // no patch move + intRange(3), // forward patch move + intRange(9), // backward patch move + intRange(0), // apply to the first + }; + + for (FuzzyApplyTestPair pair : FUZZY_APPLY_TEST_PAIRS) { + for (List move : moves) { + List from = join(move, pair.from); + List to = join(move, pair.to); + + for (int i = 0; i < pair.requiredFuzz; i++) { + int maxFuzz = i; + assertThrows( + PatchFailedException.class, + () -> patch.applyFuzzy(from, maxFuzz), + () -> "fail for " + from + " -> " + to + " for fuzz " + maxFuzz + " required " + + pair.requiredFuzz); + } + for (int i = pair.requiredFuzz; i < 4; i++) { + int maxFuzz = i; + assertEquals(to, patch.applyFuzzy(from, maxFuzz), () -> "with " + maxFuzz); + } + } + } + } + + @Test + public void fuzzyApplyTwoSideBySidePatches() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(0, deltaFrom), new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>(new Chunk<>(6, deltaFrom), new Chunk<>(6, deltaTo))); + + assertEquals(join(deltaTo, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom), 0)); + } + + @Test + public void fuzzyApplyToNearest() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(0, deltaFrom), new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>(new Chunk<>(10, deltaFrom), new Chunk<>(10, deltaTo))); + + assertEquals(join(deltaTo, deltaFrom, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom, deltaFrom), 0)); + assertEquals( + join(intRange(1), deltaTo, deltaFrom, deltaTo), + patch.applyFuzzy(join(intRange(1), deltaFrom, deltaFrom, deltaFrom), 0)); + } + + @Test + public void testPatch_Serializable() throws IOException, ClassNotFoundException { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); + + assertEquals( + Arrays.asList( + "aaa", + "bxb", + "cxc", + "<<<<<< HEAD", + "bbb", + "CDC", + "======", + "bbb", + "ccc", + ">>>>>>> PATCH", + "ddd"), + data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + static class FuzzyApplyTestDataGenerator { + private static String createList(List values) { + return values.stream().map(x -> '"' + x + '"').collect(Collectors.joining(", ", "Arrays.asList(", ")")); + } + + public static void main(String[] args) { + String[] deltaFrom = new String[] {"aaa", "bbb", "ccc", "ddd", "eee", "fff"}; + String[] deltaTo = new String[] {"aaa", "bbb", "cxc", "dxd", "eee", "fff"}; + + List pairs = new ArrayList<>(); + + // create test data. + // Brute-force search + String[] changedValue = new String[] {"axa", "bxb", "czc", "dzd", "exe", "fxf"}; + for (int i = 0; i < 1 << 6; i++) { + if ((i & 0b001100) != 0 && (i & 0b001100) != 0b001100) { + continue; + } + + String[] from = deltaFrom.clone(); + String[] to = deltaTo.clone(); + for (int j = 0; j < 6; j++) { + if ((i & (1 << j)) != 0) { + from[j] = changedValue[j]; + to[j] = changedValue[j]; + } + } + + int requiredFuzz; + if ((i & 0b001100) != 0) { + requiredFuzz = 3; + } else if ((i & 0b010010) != 0) { + requiredFuzz = 2; + } else if ((i & 0b100001) != 0) { + requiredFuzz = 1; + } else { + requiredFuzz = 0; + } + + pairs.add(new FuzzyApplyTestPair(Arrays.asList(from), Arrays.asList(to), requiredFuzz)); + } + pairs.sort(Comparator.comparingInt(a -> a.requiredFuzz)); + System.out.println("FuzzyApplyTestPair[] pairs = new FuzzyApplyTestPair[] {"); + for (FuzzyApplyTestPair pair : pairs) { + System.out.println(" new FuzzyApplyTestPair("); + System.out.println(" " + createList(pair.from) + ","); + System.out.println(" " + createList(pair.to) + ","); + System.out.println(" " + pair.requiredFuzz + "),"); + } + System.out.println("};"); + } + } + + private static final FuzzyApplyTestPair[] FUZZY_APPLY_TEST_PAIRS = new FuzzyApplyTestPair[] { + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"), + 0), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fff"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + }; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java b/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java index 4eca7d36..48259260 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java @@ -12,19 +12,19 @@ public class ApplyPatch { - private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "issue10_base.txt"; - private static final String PATCH = TestConstants.MOCK_FOLDER + "issue10_patch.txt"; + private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "issue10_base.txt"; + private static final String PATCH = TestConstants.MOCK_FOLDER + "issue10_patch.txt"; - public static void main(String[] args) throws PatchFailedException, IOException { - List original = Files.readAllLines(new File(ORIGINAL).toPath()); - List patched = Files.readAllLines(new File(PATCH).toPath()); + public static void main(String[] args) throws PatchFailedException, IOException { + List original = Files.readAllLines(new File(ORIGINAL).toPath()); + List patched = Files.readAllLines(new File(PATCH).toPath()); - // At first, parse the unified diff file and get the patch - Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patched); + // At first, parse the unified diff file and get the patch + Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patched); - // Then apply the computed patch to the given text - List result = DiffUtils.patch(original, patch); - System.out.println(result); - // / Or we can call patch.applyTo(original). There is no difference. - } + // Then apply the computed patch to the given text + List result = DiffUtils.patch(original, patch); + System.out.println(result); + // / Or we can call patch.applyTo(original). There is no difference. + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java index e8b25437..ffae731b 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java @@ -11,18 +11,18 @@ public class ComputeDifference { - private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "original.txt"; - private static final String REVISED = TestConstants.MOCK_FOLDER + "revised.txt"; + private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "original.txt"; + private static final String REVISED = TestConstants.MOCK_FOLDER + "revised.txt"; - public static void main(String[] args) throws IOException { - List original = Files.readAllLines(new File(ORIGINAL).toPath()); - List revised = Files.readAllLines(new File(REVISED).toPath()); + public static void main(String[] args) throws IOException { + List original = Files.readAllLines(new File(ORIGINAL).toPath()); + List revised = Files.readAllLines(new File(REVISED).toPath()); - // Compute diff. Get the Patch object. Patch is the container for computed deltas. - Patch patch = DiffUtils.diff(original, revised); + // Compute diff. Get the Patch object. Patch is the container for computed deltas. + Patch patch = DiffUtils.diff(original, revised); - for (AbstractDelta delta : patch.getDeltas()) { - System.out.println(delta); - } - } + for (AbstractDelta delta : patch.getDeltas()) { + System.out.println(delta); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java index 17283b4d..a3f0b7b9 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java @@ -1,59 +1,58 @@ package com.github.difflib.examples; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.TestConstants; import com.github.difflib.UnifiedDiffUtils; -import org.junit.jupiter.api.Test; - import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; - -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; public class OriginalAndDiffTest { - @Test - public void testGenerateOriginalAndDiff() { - List origLines = null; - List revLines = null; - try { - origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); - revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); - } catch (IOException e) { - fail(e.getMessage()); - } - - List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); - System.out.println(originalAndDiff.stream().collect(joining("\n"))); - } - - @Test - public void testGenerateOriginalAndDiffFirstLineChange() { - List origLines = null; - List revLines = null; - try { - origLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_original.txt"); - revLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_revised.txt"); - } catch (IOException e) { - fail(e.getMessage()); - } - - List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); - System.out.println(originalAndDiff.stream().collect(joining("\n"))); - } - - public static List fileToLines(String filename) throws FileNotFoundException, IOException { - List lines = new ArrayList<>(); - String line = ""; - try (BufferedReader in = new BufferedReader(new FileReader(filename))) { - while ((line = in.readLine()) != null) { - lines.add(line); - } - } - return lines; - } + @Test + public void testGenerateOriginalAndDiff() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + + @Test + public void testGenerateOriginalAndDiffFirstLineChange() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java index 4816f221..46eb727b 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java @@ -1,43 +1,37 @@ package com.github.difflib.patch; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; class ChunkTest { - @Test - void verifyChunk() throws PatchFailedException { - Chunk chunk = new Chunk<>(7, toCharList("test")); - - // normal check - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix test suffix"))); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix es suffix"), 0, 7)); - - // position - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("short test suffix"), 0, 6)); - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("loonger test suffix"), 0, 8)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix test suffix"), 0, 6)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix test suffix"), 0, 8)); - - // fuzz - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix test suffix"), 1, 7)); - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix es suffix"), 1, 7)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix suffix"), 1, 7)); - } - - private List toCharList(String str) { - return str.chars().mapToObj(x -> (char) x).collect(Collectors.toList()); - } + @Test + void verifyChunk() throws PatchFailedException { + Chunk chunk = new Chunk<>(7, toCharList("test")); + + // normal check + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix test suffix"))); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix es suffix"), 0, 7)); + + // position + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("short test suffix"), 0, 6)); + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("loonger test suffix"), 0, 8)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix test suffix"), 0, 6)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix test suffix"), 0, 8)); + + // fuzz + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix test suffix"), 1, 7)); + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix es suffix"), 1, 7)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix suffix"), 1, 7)); + } + + private List toCharList(String str) { + return str.chars().mapToObj(x -> (char) x).collect(Collectors.toList()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index a9731514..a14ba520 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -3,6 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.DiffAlgorithmFactory; +import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -10,12 +14,6 @@ import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.List; - - -import com.github.difflib.DiffUtils; -import com.github.difflib.algorithm.DiffAlgorithmFactory; -import com.github.difflib.algorithm.myers.MyersDiff; -import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.params.ParameterizedTest; @@ -24,87 +22,85 @@ public class PatchWithAllDiffAlgorithmsTest { - private static Stream provideAlgorithms() { - return Stream.of(Arguments.of(MyersDiff.factory()), - Arguments.of(MyersDiffWithLinearSpace.factory())); - } - - @AfterAll - public static void afterAll() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Insert(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List insertTest_from = Arrays.asList("hhh"); - final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); - - final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); - try { - assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Delete(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); - final List deleteTest_to = Arrays.asList("ggg"); - - final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); - try { - assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Change(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(baos); - out.writeObject(patch); - out.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bais); - Patch result = (Patch) in.readObject(); - in.close(); - - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - - } + private static Stream provideAlgorithms() { + return Stream.of(Arguments.of(MyersDiff.factory()), Arguments.of(MyersDiffWithLinearSpace.factory())); + } + + @AfterAll + public static void afterAll() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Insert(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Delete(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java index 97dc20fb..0dab69f3 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java @@ -15,13 +15,14 @@ */ package com.github.difflib.patch; -import com.github.difflib.DiffUtils; import static com.github.difflib.patch.Patch.CONFLICT_PRODUCES_MERGE_CONFLICT; -import java.util.Arrays; -import java.util.List; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; + +import com.github.difflib.DiffUtils; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; /** @@ -30,39 +31,40 @@ */ public class PatchWithMyerDiffTest { - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - changeTest_from.set(2, "CDC"); + changeTest_from.set(2, "CDC"); - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals( + Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), + data); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } - @Test - public void testPatchThreeWayIssue138() throws PatchFailedException { - List base = Arrays.asList("Imagine there's no heaven".split("\\s+")); - List left = Arrays.asList("Imagine there's no HEAVEN".split("\\s+")); - List right = Arrays.asList("IMAGINE there's no heaven".split("\\s+")); + @Test + public void testPatchThreeWayIssue138() throws PatchFailedException { + List base = Arrays.asList("Imagine there's no heaven".split("\\s+")); + List left = Arrays.asList("Imagine there's no HEAVEN".split("\\s+")); + List right = Arrays.asList("IMAGINE there's no heaven".split("\\s+")); - Patch rightPatch = DiffUtils.diff(base, right) - .withConflictOutput(CONFLICT_PRODUCES_MERGE_CONFLICT); + Patch rightPatch = DiffUtils.diff(base, right).withConflictOutput(CONFLICT_PRODUCES_MERGE_CONFLICT); - List applied = rightPatch.applyTo(left); + List applied = rightPatch.applyTo(left); - assertEquals("IMAGINE there's no HEAVEN", applied.stream().collect(joining(" "))); - } + assertEquals("IMAGINE there's no HEAVEN", applied.stream().collect(joining(" "))); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java index 2cc334ad..f04a1c93 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java @@ -15,14 +15,15 @@ */ package com.github.difflib.patch; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.myers.MyersDiff; import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -32,35 +33,48 @@ */ public class PatchWithMyerDiffWithLinearSpaceTest { - @BeforeAll - public static void setupClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.factory()); - } + @BeforeAll + public static void setupClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.factory()); + } - @AfterAll - public static void resetClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); - } + @AfterAll + public static void resetClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); + } - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - changeTest_from.set(2, "CDC"); + changeTest_from.set(2, "CDC"); - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(11, data.size()); + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); - assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals( + Arrays.asList( + "aaa", + "bxb", + "cxc", + "<<<<<< HEAD", + "bbb", + "CDC", + "======", + "bbb", + "ccc", + ">>>>>>> PATCH", + "ddd"), + data); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index beccad8c..3b1120c4 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -1,7 +1,16 @@ package com.github.difflib.text; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.text.deltamerge.DeltaMergeUtils; +import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; @@ -14,850 +23,882 @@ import java.util.List; import java.util.function.Function; import java.util.regex.Pattern; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import com.github.difflib.patch.AbstractDelta; -import com.github.difflib.text.deltamerge.DeltaMergeUtils; -import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo; - public class DiffRowGeneratorTest { - @Test - public void testGenerator_Default() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .columnWidth(Integer.MAX_VALUE) // do not wrap - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - } - - /** - * Test of normalize method, of class StringUtils. - */ - @Test - public void testNormalize_List() { - DiffRowGenerator generator = DiffRowGenerator.create() - .build(); - assertEquals(Collections.singletonList(" test"), generator.normalizeLines(Collections.singletonList("\ttest"))); - } - - @Test - public void testGenerator_Default2() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .columnWidth(0) // do not wrap - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - } - - @Test - public void testGenerator_InlineDiff() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .columnWidth(Integer.MAX_VALUE) // do not wrap - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - assertTrue(rows.get(0).getOldLine().indexOf(" 0); - } - - @Test - public void testGenerator_IgnoreWhitespaces() { - String first = "anything \n \nother\nmore lines"; - String second = "anything\n\nother\nsome more lines"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .ignoreWhiteSpaces(true) - .columnWidth(Integer.MAX_VALUE) // do not wrap - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(4, rows.size()); - assertEquals(rows.get(0).getTag(), DiffRow.Tag.EQUAL); - assertEquals(rows.get(1).getTag(), DiffRow.Tag.EQUAL); - assertEquals(rows.get(2).getTag(), DiffRow.Tag.EQUAL); - assertEquals(rows.get(3).getTag(), DiffRow.Tag.CHANGE); - } - - private List split(String content) { - return Arrays.asList(content.split("\n")); - } - - private void print(List diffRows) { - for (DiffRow row : diffRows) { - System.out.println(row); - } - } - - @Test - public void testGeneratorWithWordWrap() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .columnWidth(5) - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - assertEquals("[CHANGE,anyth
ing ,anyth
ing]", rows.get(0).toString()); - assertEquals("[CHANGE, ,]", rows.get(1).toString()); - assertEquals("[EQUAL,other,other]", rows.get(2).toString()); - } - - @Test - public void testGeneratorWithMerge() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - assertEquals("[CHANGE,anything ,anything]", rows.get(0).toString()); - assertEquals("[CHANGE, ,]", rows.get(1).toString()); - assertEquals("[EQUAL,other,other]", rows.get(2).toString()); - } - - @Test - public void testGeneratorWithMerge2() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .build(); - List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); - print(rows); - - assertEquals(1, rows.size()); - assertEquals("[CHANGE,Tester,ester]", rows.get(0).toString()); - } - - @Test - public void testGeneratorWithMerge3() { - String first = "test\nanything \n \nother"; - String second = "anything\n\nother\ntest\ntest2"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(6, rows.size()); - assertEquals("[CHANGE,test,anything]", rows.get(0).toString()); - assertEquals("[CHANGE,anything ,]", rows.get(1).toString()); - assertEquals("[DELETE, ,]", rows.get(2).toString()); - assertEquals("[EQUAL,other,other]", rows.get(3).toString()); - assertEquals("[INSERT,test,test]", rows.get(4).toString()); - assertEquals("[INSERT,test2,test2]", rows.get(5).toString()); - } - - @Test - public void testGeneratorWithMergeByWord4() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .build(); - List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); - print(rows); - - assertEquals(1, rows.size()); - assertEquals("[CHANGE,Testester,ester]", rows.get(0).toString()); - } - - @Test - public void testGeneratorWithMergeByWord5() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .columnWidth(80) - .build(); - List rows = generator.generateDiffRows(Arrays.asList("Test feature"), Arrays.asList("ester feature best")); - print(rows); - - assertEquals(1, rows.size()); - assertEquals("[CHANGE,Testester
feature best,ester feature best]", rows.get(0).toString()); - } - - @Test - public void testSplitString() { - List list = DiffRowGenerator.splitStringPreserveDelimiter("test,test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); - assertEquals(3, list.size()); - assertEquals("[test, ,, test2]", list.toString()); - } - - @Test - public void testSplitString2() { - List list = DiffRowGenerator.splitStringPreserveDelimiter("test , test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); - System.out.println(list); - assertEquals(5, list.size()); - assertEquals("[test, , ,, , test2]", list.toString()); - } - - @Test - public void testSplitString3() { - List list = DiffRowGenerator.splitStringPreserveDelimiter("test,test2,", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); - System.out.println(list); - assertEquals(4, list.size()); - assertEquals("[test, ,, test2, ,]", list.toString()); - } - - @Test - public void testGeneratorExample1() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence."), - Arrays.asList("This is a test for diffutils.")); - - System.out.println(rows.get(0).getOldLine()); - - assertEquals(1, rows.size()); - assertEquals("This is a test ~senctence~**for diffutils**.", rows.get(0).getOldLine()); - } - - @Test - public void testGeneratorExample2() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), - Arrays.asList("This is a test for diffutils.", "This is the second line.")); - - System.out.println("|original|new|"); - System.out.println("|--------|---|"); - for (DiffRow row : rows) { - System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); - } - - assertEquals(3, rows.size()); - assertEquals("This is a test ~senctence~.", rows.get(0).getOldLine()); - assertEquals("This is a test **for diffutils**.", rows.get(0).getNewLine()); - } - - @Test - public void testGeneratorUnchanged() { - String first = "anything \n \nother"; - String second = "anything\n\nother"; - - DiffRowGenerator generator = DiffRowGenerator.create() - .columnWidth(5) - .reportLinesUnchanged(true) - .build(); - List rows = generator.generateDiffRows(split(first), split(second)); - print(rows); - - assertEquals(3, rows.size()); - assertEquals("[CHANGE,anything ,anything]", rows.get(0).toString()); - assertEquals("[CHANGE, ,]", rows.get(1).toString()); - assertEquals("[EQUAL,other,other]", rows.get(2).toString()); - } - - @Test - public void testGeneratorIssue14() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffBySplitter(line -> DiffRowGenerator.splitStringPreserveDelimiter(line, Pattern.compile(","))) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList("J. G. Feldstein, Chair"), - Arrays.asList("T. P. Pastor, Chair")); - - System.out.println(rows.get(0).getOldLine()); - - assertEquals(1, rows.size()); - assertEquals("~J. G. Feldstein~**T. P. Pastor**, Chair", rows.get(0).getOldLine()); - } - - @Test - public void testGeneratorIssue15() throws IOException { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) //show the ~ ~ and ** ** symbols on each difference - .inlineDiffByWord(true) //show the ~ ~ and ** ** around each different word instead of each letter - //.reportLinesUnchanged(true) //experiment - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - - List listOne = Files.lines(new File("target/test-classes/mocks/issue15_1.txt").toPath()) - .collect(toList()); - - List listTwo = Files.lines(new File("target/test-classes/mocks/issue15_2.txt").toPath()) - .collect(toList()); - - List rows = generator.generateDiffRows(listOne, listTwo); - - assertEquals(9, rows.size()); - - for (DiffRow row : rows) { - System.out.println("|" + row.getOldLine() + "| " + row.getNewLine() + " |"); - if (!row.getOldLine().startsWith("TABLE_NAME")) { - assertTrue(row.getNewLine().startsWith("**ACTIONS_C16913**")); - assertTrue(row.getOldLine().startsWith("~ACTIONS_C1700")); - } - } - } - - @Test - public void testGeneratorIssue22() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - String aa = "This is a test senctence."; - String bb = "This is a test for diffutils.\nThis is the second line."; - List rows = generator.generateDiffRows( - Arrays.asList(aa.split("\n")), - Arrays.asList(bb.split("\n"))); - - assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**]]", - rows.toString()); - - System.out.println("|original|new|"); - System.out.println("|--------|---|"); - for (DiffRow row : rows) { - System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); - } - } - - @Test - public void testGeneratorIssue22_2() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - String aa = "This is a test for diffutils.\nThis is the second line."; - String bb = "This is a test senctence."; - List rows = generator.generateDiffRows( - Arrays.asList(aa.split("\n")), - Arrays.asList(bb.split("\n"))); - - assertEquals("[[CHANGE,This is a test ~for diffutils~.,This is a test **senctence**.], [DELETE,~This is the second line.~,]]", - rows.toString()); - } - - @Test - public void testGeneratorIssue22_3() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - String aa = "This is a test senctence."; - String bb = "This is a test for diffutils.\nThis is the second line.\nAnd one more."; - List rows = generator.generateDiffRows( - Arrays.asList(aa.split("\n")), - Arrays.asList(bb.split("\n"))); - - assertEquals("[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**], [INSERT,,**And one more.**]]", - rows.toString()); - } - - @Test - public void testGeneratorIssue41DefaultNormalizer() { - DiffRowGenerator generator = DiffRowGenerator.create() - .build(); - List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); - assertEquals("[[EQUAL,<,<]]", rows.toString()); - } - - @Test - public void testGeneratorIssue41UserNormalizer() { - DiffRowGenerator generator = DiffRowGenerator.create() - .lineNormalizer(str -> str.replace("\t", " ")) - .build(); - List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); - assertEquals("[[EQUAL,<,<]]", rows.toString()); - rows = generator.generateDiffRows(Arrays.asList("\t<"), Arrays.asList("<")); - assertEquals("[[CHANGE, <,<]]", rows.toString()); - } - - @Test - public void testGenerationIssue44reportLinesUnchangedProblem() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .reportLinesUnchanged(true) - .oldTag(f -> "~~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows(Arrays.asList("
To do
"), Arrays.asList("
Done
")); - assertEquals("[[CHANGE,
~~T~~o~~ do~~
,
**D**o**ne**
]]", rows.toString()); - } - - @Test - public void testIgnoreWhitespaceIssue66() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .ignoreWhiteSpaces(true) - .mergeOriginalRevised(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold - .build(); - - //compute the differences for two test texts. - //CHECKSTYLE:OFF - List rows = generator.generateDiffRows( - Arrays.asList("This\tis\ta\ttest."), - Arrays.asList("This is a test")); - //CHECKSTYLE:ON - - assertEquals("This is a test~.~", rows.get(0).getOldLine()); - } - - @Test - public void testIgnoreWhitespaceIssue66_2() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .ignoreWhiteSpaces(true) - .mergeOriginalRevised(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold - .build(); - - //compute the differences for two test texts. - List rows = generator.generateDiffRows( - Arrays.asList("This is a test."), - Arrays.asList("This is a test")); - - assertEquals("This is a test~.~", rows.get(0).getOldLine()); - } - - @Test - public void testIgnoreWhitespaceIssue64() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .ignoreWhiteSpaces(true) - .mergeOriginalRevised(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold - .build(); - - //compute the differences for two test texts. - List rows = generator.generateDiffRows( - Arrays.asList("test\n\ntestline".split("\n")), - Arrays.asList("A new text line\n\nanother one".split("\n"))); - - assertThat(rows).extracting(item -> item.getOldLine()) - .containsExactly("~test~**A new text line**", - "", - "~testline~**another one**"); - } - - @Test - public void testReplaceDiffsIssue63() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .mergeOriginalRevised(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold - .processDiffs(str -> str.replace(" ", "/")) - .build(); - - //compute the differences for two test texts. - List rows = generator.generateDiffRows( - Arrays.asList("This is a test."), - Arrays.asList("This is a test")); - - assertEquals("This~//~**/**is~//~**/**a~//~**/**test~.~", rows.get(0).getOldLine()); - } - - @Test - public void testProblemTooManyDiffRowsIssue65() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .reportLinesUnchanged(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .mergeOriginalRevised(true) - .inlineDiffByWord(false) - .replaceOriginalLinefeedInChangesWithSpaces(true) - .build(); - - List diffRows = generator.generateDiffRows( - Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), - Arrays.asList("Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", "Kannst du mich zum Kundendienst weiterleiten?")); - - print(diffRows); - - assertThat(diffRows).hasSize(2); - } - - @Test - public void testProblemTooManyDiffRowsIssue65_NoMerge() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .reportLinesUnchanged(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .mergeOriginalRevised(false) - .inlineDiffByWord(false) - .build(); - - List diffRows = generator.generateDiffRows( - Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), - Arrays.asList("Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", "Kannst du mich zum Kundendienst weiterleiten?")); - - System.out.println(diffRows); - - assertThat(diffRows).hasSize(2); - } - - @Test - public void testProblemTooManyDiffRowsIssue65_DiffByWord() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .reportLinesUnchanged(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .build(); - - List diffRows = generator.generateDiffRows( - Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), - Arrays.asList("Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", "Kannst du mich zum Kundendienst weiterleiten?")); - - System.out.println(diffRows); - - assertThat(diffRows).hasSize(2); - } - - @Test - public void testProblemTooManyDiffRowsIssue65_NoInlineDiff() { - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(false) - .reportLinesUnchanged(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .mergeOriginalRevised(true) - .inlineDiffByWord(false) - .build(); - - List diffRows = generator.generateDiffRows( - Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), - Arrays.asList("Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", "Kannst du mich zum Kundendienst weiterleiten?")); - - System.out.println(diffRows); - - assertThat(diffRows).hasSize(2); - } - - @Test - public void testLinefeedInStandardTagsWithLineWidthIssue81() { - List original = Arrays.asList(("American bobtail jaguar. American bobtail bombay but turkish angora and tomcat.\n" - + "Russian blue leopard. Lion. Tabby scottish fold for russian blue, so savannah yet lynx. Tomcat singapura, cheetah.\n" - + "Bengal tiger panther but singapura but bombay munchkin for cougar.").split("\n")); - List revised = Arrays.asList(("bobtail jaguar. American bobtail turkish angora and tomcat.\n" - + "Russian blue leopard. Lion. Tabby scottish folded for russian blue, so savannah yettie? lynx. Tomcat singapura, cheetah.\n" - + "Bengal tiger panther but singapura but bombay munchkin for cougar. And more.").split("\n")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .ignoreWhiteSpaces(true) - .columnWidth(100) - .build(); - List deltas = generator.generateDiffRows(original, revised); - - System.out.println(deltas); - } - - @Test - public void testIssue86WrongInlineDiff() throws IOException { - String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_original.txt")).collect(joining("\n")); - String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_revised.txt")).collect(joining("\n")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList(original.split("\n")), - Arrays.asList(revised.split("\n"))); - - rows.stream() - .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) - .forEach(System.out::println); - } - - @Test - public void testCorrectChangeIssue114() throws IOException { - List original = Arrays.asList("A", "B", "C", "D", "E"); - List revised = Arrays.asList("a", "C", "", "E"); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(false) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows(original, revised); - - for (DiffRow diff : rows) { - System.out.println(diff); - } - - assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); - } - - @Test - public void testCorrectChangeIssue114_2() throws IOException { - List original = Arrays.asList("A", "B", "C", "D", "E"); - List revised = Arrays.asList("a", "C", "", "E"); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows(original, revised); - - for (DiffRow diff : rows) { - System.out.println(diff); - } - - assertThat(rows).extracting(item -> item.getTag().name()).containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); - assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]"); - } - - @Test - public void testIssue119WrongContextLength() throws IOException { - String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_original.txt")).collect(joining("\n")); - String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_revised.txt")).collect(joining("\n")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .oldTag(f -> "~") - .newTag(f -> "**") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList(original.split("\n")), - Arrays.asList(revised.split("\n"))); - - rows.stream() - .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) - .forEach(System.out::println); - } - - @Test - public void testIssue129WithDeltaDecompression() { - List lines1 = Arrays.asList( - "apple1", - "apple2", - "apple3", - "A man named Frankenstein abc to Switzerland for cookies!", - "banana1", - "banana2", - "banana3"); - List lines2 = Arrays.asList( - "apple1", - "apple2", - "apple3", - "A man named Frankenstein", - "xyz", - "to Switzerland for cookies!", - "banana1", - "banana2", - "banana3"); - int[] entry = {1}; - String txt = DiffRowGenerator.create() - .showInlineDiffs(true) - .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") - .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") - .build() - .generateDiffRows(lines1, lines2) - .stream() - .map(row -> row.getTag().toString()) - .collect(joining(" ")); -// .forEachOrdered(row -> { -// System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, -// row.getTag(), row.getOldLine(), row.getNewLine()); -// }); - - assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL"); - } - - @Test - public void testIssue129SkipDeltaDecompression() { - List lines1 = Arrays.asList( - "apple1", - "apple2", - "apple3", - "A man named Frankenstein abc to Switzerland for cookies!", - "banana1", - "banana2", - "banana3"); - List lines2 = Arrays.asList( - "apple1", - "apple2", - "apple3", - "A man named Frankenstein", - "xyz", - "to Switzerland for cookies!", - "banana1", - "banana2", - "banana3"); - int[] entry = {1}; - String txt - = DiffRowGenerator.create() - .showInlineDiffs(true) - .decompressDeltas(false) - .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") - .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") - .build() - .generateDiffRows(lines1, lines2) - .stream() - .map(row -> row.getTag().toString()) - .collect(joining(" ")); -// .forEachOrdered(row -> { -// System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, -// row.getTag(), row.getOldLine(), row.getNewLine()); -// }); - - assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); - } - - @Test - public void testIssue129SkipWhitespaceChanges() throws IOException { - String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")).collect(joining("\n")); - String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_2.txt")).collect(joining("\n")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .ignoreWhiteSpaces(true) - .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") - .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") - .build(); - List rows = generator.generateDiffRows( - Arrays.asList(original.split("\n")), - Arrays.asList(revised.split("\n"))); - - assertThat(rows).hasSize(13); - - rows.stream() - .filter(item -> item.getTag() != DiffRow.Tag.EQUAL) - .forEach(System.out::println); - } - - @Test - public void testGeneratorWithWhitespaceDeltaMerge() { - final DiffRowGenerator generator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true) - .inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**") // - .lineNormalizer(StringUtils::htmlEntites) // do not replace tabs - .inlineDeltaMerger(DiffRowGenerator.WHITESPACE_EQUALITIES_MERGER).build(); - - assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); - assertInlineDiffResult(generator, " x whitespace before diff", " y whitespace before diff", - " ~x~**y** whitespace before diff"); - assertInlineDiffResult(generator, "Whitespace after diff x ", "Whitespace after diff y ", - "Whitespace after diff ~x~**y** "); - assertInlineDiffResult(generator, "Diff x x between", "Diff y y between", "Diff ~x x~**y y** between"); - assertInlineDiffResult(generator, "Hello \t world", "Hi \t universe", "~Hello \t world~**Hi \t universe**"); - assertInlineDiffResult(generator, "The quick brown fox jumps over the lazy dog", "A lazy dog jumps over a fox", - "~The quick brown fox ~**A lazy dog **jumps over ~the lazy dog~**a fox**"); - } - - @Test - public void testGeneratorWithMergingDeltasForShortEqualities() { - final Function>> shortEqualitiesMerger = deltaMergeInfo -> DeltaMergeUtils - .mergeInlineDeltas(deltaMergeInfo, - equalities -> equalities.stream().mapToInt(String::length).sum() < 6); - - final DiffRowGenerator generator = DiffRowGenerator.create().showInlineDiffs(true).mergeOriginalRevised(true) - .inlineDiffByWord(true).oldTag(f -> "~").newTag(f -> "**").inlineDeltaMerger(shortEqualitiesMerger) - .build(); - - assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); - assertInlineDiffResult(generator, "aaa bbb ccc", "xxx bbb zzz", "~aaa bbb ccc~**xxx bbb zzz**"); - assertInlineDiffResult(generator, "aaa bbbb ccc", "xxx bbbb zzz", "~aaa~**xxx** bbbb ~ccc~**zzz**"); - } - - private void assertInlineDiffResult(DiffRowGenerator generator, String original, String revised, String expected) { - final List rows = generator.generateDiffRows(Arrays.asList(original), Arrays.asList(revised)); - print(rows); - - assertEquals(1, rows.size()); - assertEquals(expected, rows.get(0).getOldLine().toString()); - } - - @Test - public void testIssue188HangOnExamples() throws IOException, URISyntaxException { - try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), (ClassLoader) null);) { - List original = Files.readAllLines(zipFs.getPath("old.html")); - List revised = Files.readAllLines(zipFs.getPath("new.html")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .lineNormalizer(line -> line) - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .decompressDeltas(true) - .oldTag(f -> f ? "" : "") - .newTag(f -> f ? "" : "") - .build(); - - //List rows = generator.generateDiffRows(original, revised); - List rows = generator.generateDiffRows(original, DiffUtils.diff(original, revised, new MyersDiffWithLinearSpace<>() )); - - System.out.println(rows); - } - } + @Test + public void testGenerator_Default() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + } + + /** + * Test of normalize method, of class StringUtils. + */ + @Test + public void testNormalize_List() { + DiffRowGenerator generator = DiffRowGenerator.create().build(); + assertEquals( + Collections.singletonList(" test"), generator.normalizeLines(Collections.singletonList("\ttest"))); + } + + @Test + public void testGenerator_Default2() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(0) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + } + + @Test + public void testGenerator_InlineDiff() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertTrue(rows.get(0).getOldLine().indexOf(" 0); + } + + @Test + public void testGenerator_IgnoreWhitespaces() { + String first = "anything \n \nother\nmore lines"; + String second = "anything\n\nother\nsome more lines"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .ignoreWhiteSpaces(true) + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(4, rows.size()); + assertEquals(rows.get(0).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(1).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(2).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(3).getTag(), DiffRow.Tag.CHANGE); + } + + private List split(String content) { + return Arrays.asList(content.split("\n")); + } + + private void print(List diffRows) { + for (DiffRow row : diffRows) { + System.out.println(row); + } + } + + @Test + public void testGeneratorWithWordWrap() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create().columnWidth(5).build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals("[CHANGE,anyth
ing ,anyth
ing]", rows.get(0).toString()); + assertEquals("[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorWithMerge() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals( + "[CHANGE,anything ,anything]", + rows.get(0).toString()); + assertEquals( + "[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorWithMerge2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Tester,ester]", + rows.get(0).toString()); + } + + @Test + public void testGeneratorWithMerge3() { + String first = "test\nanything \n \nother"; + String second = "anything\n\nother\ntest\ntest2"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(6, rows.size()); + assertEquals( + "[CHANGE,test,anything]", + rows.get(0).toString()); + assertEquals( + "[CHANGE,anything ,]", + rows.get(1).toString()); + assertEquals( + "[DELETE, ,]", rows.get(2).toString()); + assertEquals("[EQUAL,other,other]", rows.get(3).toString()); + assertEquals( + "[INSERT,test,test]", + rows.get(4).toString()); + assertEquals( + "[INSERT,test2,test2]", + rows.get(5).toString()); + } + + @Test + public void testGeneratorWithMergeByWord4() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Testester,ester]", + rows.get(0).toString()); + } + + @Test + public void testGeneratorWithMergeByWord5() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .columnWidth(80) + .build(); + List rows = + generator.generateDiffRows(Arrays.asList("Test feature"), Arrays.asList("ester feature best")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Testester
feature best,ester feature best]", + rows.get(0).toString()); + } + + @Test + public void testSplitString() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test,test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + assertEquals(3, list.size()); + assertEquals("[test, ,, test2]", list.toString()); + } + + @Test + public void testSplitString2() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test , test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + System.out.println(list); + assertEquals(5, list.size()); + assertEquals("[test, , ,, , test2]", list.toString()); + } + + @Test + public void testSplitString3() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test,test2,", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + System.out.println(list); + assertEquals(4, list.size()); + assertEquals("[test, ,, test2, ,]", list.toString()); + } + + @Test + public void testGeneratorExample1() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence."), Arrays.asList("This is a test for diffutils.")); + + System.out.println(rows.get(0).getOldLine()); + + assertEquals(1, rows.size()); + assertEquals("This is a test ~senctence~**for diffutils**.", rows.get(0).getOldLine()); + } + + @Test + public void testGeneratorExample2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), + Arrays.asList("This is a test for diffutils.", "This is the second line.")); + + System.out.println("|original|new|"); + System.out.println("|--------|---|"); + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); + } + + assertEquals(3, rows.size()); + assertEquals("This is a test ~senctence~.", rows.get(0).getOldLine()); + assertEquals("This is a test **for diffutils**.", rows.get(0).getNewLine()); + } + + @Test + public void testGeneratorUnchanged() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(5) + .reportLinesUnchanged(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals("[CHANGE,anything ,anything]", rows.get(0).toString()); + assertEquals("[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorIssue14() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffBySplitter(line -> DiffRowGenerator.splitStringPreserveDelimiter(line, Pattern.compile(","))) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("J. G. Feldstein, Chair"), Arrays.asList("T. P. Pastor, Chair")); + + System.out.println(rows.get(0).getOldLine()); + + assertEquals(1, rows.size()); + assertEquals("~J. G. Feldstein~**T. P. Pastor**, Chair", rows.get(0).getOldLine()); + } + + @Test + public void testGeneratorIssue15() throws IOException { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) // show the ~ ~ and ** ** symbols on each difference + .inlineDiffByWord(true) // show the ~ ~ and ** ** around each different word instead of each letter + // .reportLinesUnchanged(true) //experiment + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + + List listOne = Files.lines(new File("target/test-classes/mocks/issue15_1.txt").toPath()) + .collect(toList()); + + List listTwo = Files.lines(new File("target/test-classes/mocks/issue15_2.txt").toPath()) + .collect(toList()); + + List rows = generator.generateDiffRows(listOne, listTwo); + + assertEquals(9, rows.size()); + + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "| " + row.getNewLine() + " |"); + if (!row.getOldLine().startsWith("TABLE_NAME")) { + assertTrue(row.getNewLine().startsWith("**ACTIONS_C16913**")); + assertTrue(row.getOldLine().startsWith("~ACTIONS_C1700")); + } + } + } + + @Test + public void testGeneratorIssue22() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test senctence."; + String bb = "This is a test for diffutils.\nThis is the second line."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**]]", + rows.toString()); + + System.out.println("|original|new|"); + System.out.println("|--------|---|"); + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); + } + } + + @Test + public void testGeneratorIssue22_2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test for diffutils.\nThis is the second line."; + String bb = "This is a test senctence."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~for diffutils~.,This is a test **senctence**.], [DELETE,~This is the second line.~,]]", + rows.toString()); + } + + @Test + public void testGeneratorIssue22_3() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test senctence."; + String bb = "This is a test for diffutils.\nThis is the second line.\nAnd one more."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**], [INSERT,,**And one more.**]]", + rows.toString()); + } + + @Test + public void testGeneratorIssue41DefaultNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create().build(); + List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); + assertEquals("[[EQUAL,<,<]]", rows.toString()); + } + + @Test + public void testGeneratorIssue41UserNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create() + .lineNormalizer(str -> str.replace("\t", " ")) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); + assertEquals("[[EQUAL,<,<]]", rows.toString()); + rows = generator.generateDiffRows(Arrays.asList("\t<"), Arrays.asList("<")); + assertEquals("[[CHANGE, <,<]]", rows.toString()); + } + + @Test + public void testGenerationIssue44reportLinesUnchangedProblem() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList("
To do
"), Arrays.asList("
Done
")); + assertEquals("[[CHANGE,
~~T~~o~~ do~~
,
**D**o**ne**
]]", rows.toString()); + } + + @Test + public void testIgnoreWhitespaceIssue66() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + // CHECKSTYLE:OFF + List rows = + generator.generateDiffRows(Arrays.asList("This\tis\ta\ttest."), Arrays.asList("This is a test")); + // CHECKSTYLE:ON + + assertEquals("This is a test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testIgnoreWhitespaceIssue66_2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + List rows = + generator.generateDiffRows(Arrays.asList("This is a test."), Arrays.asList("This is a test")); + + assertEquals("This is a test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testIgnoreWhitespaceIssue64() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + List rows = generator.generateDiffRows( + Arrays.asList("test\n\ntestline".split("\n")), + Arrays.asList("A new text line\n\nanother one".split("\n"))); + + assertThat(rows) + .extracting(item -> item.getOldLine()) + .containsExactly("~test~**A new text line**", "", "~testline~**another one**"); + } + + @Test + public void testReplaceDiffsIssue63() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .processDiffs(str -> str.replace(" ", "/")) + .build(); + + // compute the differences for two test texts. + List rows = + generator.generateDiffRows(Arrays.asList("This is a test."), Arrays.asList("This is a test")); + + assertEquals("This~//~**/**is~//~**/**a~//~**/**test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testProblemTooManyDiffRowsIssue65() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(false) + .replaceOriginalLinefeedInChangesWithSpaces(true) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + print(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_NoMerge() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(false) + .inlineDiffByWord(false) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_DiffByWord() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_NoInlineDiff() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(false) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testLinefeedInStandardTagsWithLineWidthIssue81() { + List original = + Arrays.asList(("American bobtail jaguar. American bobtail bombay but turkish angora and tomcat.\n" + + "Russian blue leopard. Lion. Tabby scottish fold for russian blue, so savannah yet lynx. Tomcat singapura, cheetah.\n" + + "Bengal tiger panther but singapura but bombay munchkin for cougar.") + .split("\n")); + List revised = Arrays.asList(("bobtail jaguar. American bobtail turkish angora and tomcat.\n" + + "Russian blue leopard. Lion. Tabby scottish folded for russian blue, so savannah yettie? lynx. Tomcat singapura, cheetah.\n" + + "Bengal tiger panther but singapura but bombay munchkin for cougar. And more.") + .split("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .ignoreWhiteSpaces(true) + .columnWidth(100) + .build(); + List deltas = generator.generateDiffRows(original, revised); + + System.out.println(deltas); + } + + @Test + public void testIssue86WrongInlineDiff() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_original.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_revised.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testCorrectChangeIssue114() throws IOException { + List original = Arrays.asList("A", "B", "C", "D", "E"); + List revised = Arrays.asList("a", "C", "", "E"); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows(original, revised); + + for (DiffRow diff : rows) { + System.out.println(diff); + } + + assertThat(rows) + .extracting(item -> item.getTag().name()) + .containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); + } + + @Test + public void testCorrectChangeIssue114_2() throws IOException { + List original = Arrays.asList("A", "B", "C", "D", "E"); + List revised = Arrays.asList("a", "C", "", "E"); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows(original, revised); + + for (DiffRow diff : rows) { + System.out.println(diff); + } + + assertThat(rows) + .extracting(item -> item.getTag().name()) + .containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); + assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]"); + } + + @Test + public void testIssue119WrongContextLength() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_original.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_revised.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testIssue129WithDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = DiffRowGenerator.create() + .showInlineDiffs(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); + // .forEachOrdered(row -> { + // System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, + // row.getTag(), row.getOldLine(), row.getNewLine()); + // }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL"); + } + + @Test + public void testIssue129SkipDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = DiffRowGenerator.create() + .showInlineDiffs(true) + .decompressDeltas(false) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); + // .forEachOrdered(row -> { + // System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, + // row.getTag(), row.getOldLine(), row.getNewLine()); + // }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); + } + + @Test + public void testIssue129SkipWhitespaceChanges() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_2.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + assertThat(rows).hasSize(13); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testGeneratorWithWhitespaceDeltaMerge() { + final DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") // + .lineNormalizer(StringUtils::htmlEntites) // do not replace tabs + .inlineDeltaMerger(DiffRowGenerator.WHITESPACE_EQUALITIES_MERGER) + .build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult( + generator, + " x whitespace before diff", + " y whitespace before diff", + " ~x~**y** whitespace before diff"); + assertInlineDiffResult( + generator, "Whitespace after diff x ", "Whitespace after diff y ", "Whitespace after diff ~x~**y** "); + assertInlineDiffResult(generator, "Diff x x between", "Diff y y between", "Diff ~x x~**y y** between"); + assertInlineDiffResult(generator, "Hello \t world", "Hi \t universe", "~Hello \t world~**Hi \t universe**"); + assertInlineDiffResult( + generator, + "The quick brown fox jumps over the lazy dog", + "A lazy dog jumps over a fox", + "~The quick brown fox ~**A lazy dog **jumps over ~the lazy dog~**a fox**"); + } + + @Test + public void testGeneratorWithMergingDeltasForShortEqualities() { + final Function>> shortEqualitiesMerger = + deltaMergeInfo -> DeltaMergeUtils.mergeInlineDeltas( + deltaMergeInfo, + equalities -> + equalities.stream().mapToInt(String::length).sum() < 6); + + final DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .inlineDeltaMerger(shortEqualitiesMerger) + .build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult(generator, "aaa bbb ccc", "xxx bbb zzz", "~aaa bbb ccc~**xxx bbb zzz**"); + assertInlineDiffResult(generator, "aaa bbbb ccc", "xxx bbbb zzz", "~aaa~**xxx** bbbb ~ccc~**zzz**"); + } + + private void assertInlineDiffResult(DiffRowGenerator generator, String original, String revised, String expected) { + final List rows = generator.generateDiffRows(Arrays.asList(original), Arrays.asList(revised)); + print(rows); + + assertEquals(1, rows.size()); + assertEquals(expected, rows.get(0).getOldLine().toString()); + } + + @Test + public void testIssue188HangOnExamples() throws IOException, URISyntaxException { + try (FileSystem zipFs = FileSystems.newFileSystem( + Paths.get("target/test-classes/com/github/difflib/text/test.zip"), (ClassLoader) null); ) { + List original = Files.readAllLines(zipFs.getPath("old.html")); + List revised = Files.readAllLines(zipFs.getPath("new.html")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .lineNormalizer(line -> line) + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .decompressDeltas(true) + .oldTag(f -> f ? "" : "") + .newTag(f -> f ? "" : "") + .build(); + + // List rows = generator.generateDiffRows(original, revised); + List rows = generator.generateDiffRows( + original, DiffUtils.diff(original, revised, new MyersDiffWithLinearSpace<>())); + + System.out.println(rows); + } + } } - diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java index 68670723..b2e06de0 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java @@ -15,8 +15,9 @@ */ package com.github.difflib.text; -import org.junit.jupiter.api.Assertions; import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -25,38 +26,36 @@ */ public class StringUtilsTest { - /** - * Test of htmlEntites method, of class StringUtils. - */ - @Test - public void testHtmlEntites() { - assertEquals("<test>", StringUtils.htmlEntites("")); - } - - /** - * Test of normalize method, of class StringUtils. - */ - @Test - public void testNormalize_String() { - assertEquals(" test", StringUtils.normalize("\ttest")); - } + /** + * Test of htmlEntites method, of class StringUtils. + */ + @Test + public void testHtmlEntites() { + assertEquals("<test>", StringUtils.htmlEntites("")); + } - /** - * Test of wrapText method, of class StringUtils. - */ - @Test - public void testWrapText_String_int() { - assertEquals("te
st", StringUtils.wrapText("test", 2)); - assertEquals("tes
t", StringUtils.wrapText("test", 3)); - assertEquals("test", StringUtils.wrapText("test", 10)); - assertEquals(".\uD800\uDC01
.", StringUtils.wrapText(".\uD800\uDC01.", 2)); - assertEquals("..
\uD800\uDC01", StringUtils.wrapText("..\uD800\uDC01", 3)); - } + /** + * Test of normalize method, of class StringUtils. + */ + @Test + public void testNormalize_String() { + assertEquals(" test", StringUtils.normalize("\ttest")); + } - @Test - public void testWrapText_String_int_zero() { - Assertions.assertThrows(IllegalArgumentException.class, - () -> StringUtils.wrapText("test", -1)); - } + /** + * Test of wrapText method, of class StringUtils. + */ + @Test + public void testWrapText_String_int() { + assertEquals("te
st", StringUtils.wrapText("test", 2)); + assertEquals("tes
t", StringUtils.wrapText("test", 3)); + assertEquals("test", StringUtils.wrapText("test", 10)); + assertEquals(".\uD800\uDC01
.", StringUtils.wrapText(".\uD800\uDC01.", 2)); + assertEquals("..
\uD800\uDC01", StringUtils.wrapText("..\uD800\uDC01", 3)); + } + @Test + public void testWrapText_String_int_zero() { + Assertions.assertThrows(IllegalArgumentException.class, () -> StringUtils.wrapText("test", -1)); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 3d851561..fc272edb 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -15,14 +15,15 @@ */ package com.github.difflib.unifieddiff; -import com.github.difflib.patch.AbstractDelta; -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.difflib.patch.AbstractDelta; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; /** @@ -31,419 +32,475 @@ */ public class UnifiedDiffReaderTest { - @Test - public void testSimpleParse() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + @Test + public void testSimpleParse() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } + + @Test + public void testParseDiffBlock() { + String[] files = UnifiedDiffReader.parseFileNames( + "diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); + assertThat(files) + .containsExactly( + "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java", + "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); + } + + @Test + public void testChunkHeaderParsing() { + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher( + "@@ -189,6 +189,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */"); + + assertTrue(matcher.find()); + assertEquals("189", matcher.group(1)); + assertEquals("189", matcher.group(3)); + } - System.out.println(diff); + @Test + public void testChunkHeaderParsing2() { + // "^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@"); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertTrue(matcher.find()); + assertEquals("189", matcher.group(1)); + assertEquals("189", matcher.group(3)); + } - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + @Test + public void testChunkHeaderParsing3() { + // "^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher("@@ -1,27 +1,27 @@"); - assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); - } + assertTrue(matcher.find()); + assertEquals("1", matcher.group(1)); + assertEquals("1", matcher.group(3)); + } - @Test - public void testParseDiffBlock() { - String[] files = UnifiedDiffReader.parseFileNames("diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); - assertThat(files).containsExactly("src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java", "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); - } + @Test + public void testSimpleParse2() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); - @Test - public void testChunkHeaderParsing() { - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */"); + System.out.println(diff); - assertTrue(matcher.find()); - assertEquals("189", matcher.group(1)); - assertEquals("189", matcher.group(3)); - } + assertThat(diff.getFiles().size()).isEqualTo(2); - @Test - public void testChunkHeaderParsing2() { - //"^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@"); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); - assertTrue(matcher.find()); - assertEquals("189", matcher.group(1)); - assertEquals("189", matcher.group(3)); - } + AbstractDelta first = file1.getPatch().getDeltas().get(0); - @Test - public void testChunkHeaderParsing3() { - //"^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -1,27 +1,27 @@"); + assertThat(first.getSource().size()).isGreaterThan(0); + assertThat(first.getTarget().size()).isGreaterThan(0); - assertTrue(matcher.find()); - assertEquals("1", matcher.group(1)); - assertEquals("1", matcher.group(3)); - } + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } - @Test - public void testSimpleParse2() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + @Test + public void testParseIssue201() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); - AbstractDelta first = file1.getPatch().getDeltas().get(0); + AbstractDelta first = file1.getPatch().getDeltas().get(0); - assertThat(first.getSource().size()).isGreaterThan(0); - assertThat(first.getTarget().size()).isGreaterThan(0); + assertThat(first.getSource().size()).isGreaterThan(0); + assertThat(first.getTarget().size()).isGreaterThan(0); - assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); - } + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } - @Test - public void testSimplePattern() { - Pattern pattern = Pattern.compile("^\\+\\+\\+\\s"); + @Test + public void testSimplePattern() { + Pattern pattern = Pattern.compile("^\\+\\+\\+\\s"); - Matcher m = pattern.matcher("+++ revised.txt"); - assertTrue(m.find()); - } + Matcher m = pattern.matcher("+++ revised.txt"); + assertTrue(m.find()); + } - @Test - public void testParseIssue46() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue46.diff")); + @Test + public void testParseIssue46() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue46.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("a.vhd"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("a.vhd"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - } + assertThat(diff.getTail()).isNull(); + } - @Test - public void testParseIssue33() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue33.diff")); + @Test + public void testParseIssue33() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue33.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Main.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Main.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - assertThat(diff.getHeader()).isNull(); - } + assertThat(diff.getTail()).isNull(); + assertThat(diff.getHeader()).isNull(); + } - @Test - public void testParseIssue51() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue51.diff")); + @Test + public void testParseIssue51() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue51.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("f1"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("f1"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - UnifiedDiffFile file2 = diff.getFiles().get(1); - assertThat(file2.getFromFile()).isEqualTo("f2"); - assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file2 = diff.getFiles().get(1); + assertThat(file2.getFromFile()).isEqualTo("f2"); + assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - } + assertThat(diff.getTail()).isNull(); + } - @Test - public void testParseIssue79() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue79.diff")); + @Test + public void testParseIssue79() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue79.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("test/Issue.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(0); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("test/Issue.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(0); - assertThat(diff.getTail()).isNull(); - assertThat(diff.getHeader()).isNull(); - } + assertThat(diff.getTail()).isNull(); + assertThat(diff.getHeader()).isNull(); + } - @Test - public void testParseIssue84() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue84.diff")); + @Test + public void testParseIssue84() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue84.diff")); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("config/ant-phase-verify.xml"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("config/ant-phase-verify.xml"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - UnifiedDiffFile file2 = diff.getFiles().get(1); - assertThat(file2.getFromFile()).isEqualTo("/dev/null"); - assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file2 = diff.getFiles().get(1); + assertThat(file2.getFromFile()).isEqualTo("/dev/null"); + assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isEqualTo("2.7.4"); - assertThat(diff.getHeader()).startsWith("From b53e612a2ab5ff15d14860e252f84c0f343fe93a Mon Sep 17 00:00:00 2001"); - } + assertThat(diff.getTail()).isEqualTo("2.7.4"); + assertThat(diff.getHeader()) + .startsWith("From b53e612a2ab5ff15d14860e252f84c0f343fe93a Mon Sep 17 00:00:00 2001"); + } - @Test - public void testParseIssue85() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue85.diff")); + @Test + public void testParseIssue85() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue85.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertEquals(1, diff.getFiles().size()); + assertEquals(1, diff.getFiles().size()); - final UnifiedDiffFile file1 = diff.getFiles().get(0); - assertEquals("diff -r 83e41b73d115 -r a4438263b228 tests/test-check-pyflakes.t", - file1.getDiffCommand()); - assertEquals("tests/test-check-pyflakes.t", file1.getFromFile()); - assertEquals("tests/test-check-pyflakes.t", file1.getToFile()); - assertEquals(1, file1.getPatch().getDeltas().size()); + final UnifiedDiffFile file1 = diff.getFiles().get(0); + assertEquals("diff -r 83e41b73d115 -r a4438263b228 tests/test-check-pyflakes.t", file1.getDiffCommand()); + assertEquals("tests/test-check-pyflakes.t", file1.getFromFile()); + assertEquals("tests/test-check-pyflakes.t", file1.getToFile()); + assertEquals(1, file1.getPatch().getDeltas().size()); - assertNull(diff.getTail()); - } + assertNull(diff.getTail()); + } - @Test - public void testTimeStampRegexp() { - assertThat("2019-04-18 13:49:39.516149751 +0200").matches(UnifiedDiffReader.TIMESTAMP_REGEXP); - } + @Test + public void testTimeStampRegexp() { + assertThat("2019-04-18 13:49:39.516149751 +0200").matches(UnifiedDiffReader.TIMESTAMP_REGEXP); + } - @Test - public void testParseIssue98() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue98.diff")); + @Test + public void testParseIssue98() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue98.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertEquals(1, diff.getFiles().size()); + assertEquals(1, diff.getFiles().size()); - final UnifiedDiffFile file1 = diff.getFiles().get(0); - assertEquals("100644", - file1.getDeletedFileMode()); - assertEquals("src/test/java/se/bjurr/violations/lib/model/ViolationTest.java", file1.getFromFile()); - assertThat(diff.getTail()).isEqualTo("2.25.1"); - } + final UnifiedDiffFile file1 = diff.getFiles().get(0); + assertEquals("100644", file1.getDeletedFileMode()); + assertEquals("src/test/java/se/bjurr/violations/lib/model/ViolationTest.java", file1.getFromFile()); + assertThat(diff.getTail()).isEqualTo("2.25.1"); + } - @Test - public void testParseIssue104() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue104.diff")); + @Test + public void testParseIssue104() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue104.diff")); - assertThat(diff.getFiles().size()).isEqualTo(6); + assertThat(diff.getFiles().size()).isEqualTo(6); - final UnifiedDiffFile file = diff.getFiles().get(2); - assertThat(file.getFromFile()).isEqualTo("/dev/null"); - assertThat(file.getToFile()).isEqualTo("doc/samba_data_tool_path.xml.in"); + final UnifiedDiffFile file = diff.getFiles().get(2); + assertThat(file.getFromFile()).isEqualTo("/dev/null"); + assertThat(file.getToFile()).isEqualTo("doc/samba_data_tool_path.xml.in"); - assertThat(file.getPatch().toString()).isEqualTo("Patch{deltas=[[ChangeDelta, position: 0, lines: [] to [@SAMBA_DATA_TOOL@]]]}"); + assertThat(file.getPatch().toString()) + .isEqualTo("Patch{deltas=[[ChangeDelta, position: 0, lines: [] to [@SAMBA_DATA_TOOL@]]]}"); - assertThat(diff.getTail()).isEqualTo("2.14.4"); - } - - @Test - public void testParseIssue107BazelDiff() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("01-bazel-strip-unused.patch_issue107.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(450); - - final UnifiedDiffFile file = diff.getFiles().get(0); - assertThat(file.getFromFile()).isEqualTo("./src/main/java/com/amazonaws/AbortedException.java"); - assertThat(file.getToFile()).isEqualTo("/home/greg/projects/bazel/third_party/aws-sdk-auth-lite/src/main/java/com/amazonaws/AbortedException.java"); - - assertThat(diff.getFiles().stream() - .filter(f -> f.isNoNewLineAtTheEndOfTheFile()) - .count()) - .isEqualTo(48); - } - - @Test - public void testParseIssue107_2() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Main.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - - } - - @Test - public void testParseIssue107_3() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_3.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(1); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Billion laughs attack.md"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - - } - - @Test - public void testParseIssue107_4() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_4.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(27); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("README.md"); - } - - @Test - public void testParseIssue107_5() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_5.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(22); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); - } - - @Test - public void testParseIssue110() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("0001-avahi-python-Use-the-agnostic-DBM-interface.patch")); - - assertThat(diff.getFiles().size()).isEqualTo(5); - - final UnifiedDiffFile file = diff.getFiles().get(4); - assertThat(file.getSimilarityIndex()).isEqualTo(87); - assertThat(file.getRenameFrom()).isEqualTo("service-type-database/build-db.in"); - assertThat(file.getRenameTo()).isEqualTo("service-type-database/build-db"); - - assertThat(file.getFromFile()).isEqualTo("service-type-database/build-db.in"); - assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); - } - - @Test - public void testParseIssue117() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue117.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getSource().getChangePosition()) - .containsExactly(24, 27); - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getTarget().getChangePosition()) - .containsExactly(24, 27); - - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getSource().getChangePosition()) - .containsExactly(64); - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getTarget().getChangePosition()) - .containsExactly(64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74); - -// diff.getFiles().forEach(f -> { -// System.out.println("File: " + f.getFromFile()); -// f.getPatch().getDeltas().forEach(delta -> { -// -// System.out.println(delta); -// System.out.println("Source: "); -// System.out.println(delta.getSource().getPosition()); -// System.out.println(delta.getSource().getChangePosition()); -// -// System.out.println("Target: "); -// System.out.println(delta.getTarget().getPosition()); -// System.out.println(delta.getTarget().getChangePosition()); -// }); -// }); - } - - @Test - public void testParseIssue122() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(1); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); - } - - @Test - public void testParseIssue123() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); - } - - @Test - public void testParseIssue141() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue141.diff")); - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getFromFile()).isEqualTo("a.txt"); - assertThat(file1.getToFile()).isEqualTo("a1.txt"); - } - - @Test - public void testParseIssue182add() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryAdded()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182delete() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryDeleted()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182edit() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryEdited()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182mode() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getOldMode()).isEqualTo("100644"); - assertThat(file1.getNewMode()).isEqualTo("100755"); - } - - @Test - public void testParseIssue193Copy() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue193.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getCopyFrom()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.pcf"); - assertThat(file1.getCopyTo()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf"); - } + assertThat(diff.getTail()).isEqualTo("2.14.4"); + } + + @Test + public void testParseIssue107BazelDiff() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("01-bazel-strip-unused.patch_issue107.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(450); + + final UnifiedDiffFile file = diff.getFiles().get(0); + assertThat(file.getFromFile()).isEqualTo("./src/main/java/com/amazonaws/AbortedException.java"); + assertThat(file.getToFile()) + .isEqualTo( + "/home/greg/projects/bazel/third_party/aws-sdk-auth-lite/src/main/java/com/amazonaws/AbortedException.java"); + + assertThat(diff.getFiles().stream() + .filter(f -> f.isNoNewLineAtTheEndOfTheFile()) + .count()) + .isEqualTo(48); + } + + @Test + public void testParseIssue107_2() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Main.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + } + + @Test + public void testParseIssue107_3() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_3.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(1); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Billion laughs attack.md"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + } + + @Test + public void testParseIssue107_4() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_4.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(27); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("README.md"); + } + + @Test + public void testParseIssue107_5() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_5.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()) + .extracting(f -> f.getFromFile()) + .contains( + "rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } + + @Test + public void testParseIssue110() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream( + "0001-avahi-python-Use-the-agnostic-DBM-interface.patch")); + + assertThat(diff.getFiles().size()).isEqualTo(5); + + final UnifiedDiffFile file = diff.getFiles().get(4); + assertThat(file.getSimilarityIndex()).isEqualTo(87); + assertThat(file.getRenameFrom()).isEqualTo("service-type-database/build-db.in"); + assertThat(file.getRenameTo()).isEqualTo("service-type-database/build-db"); + + assertThat(file.getFromFile()).isEqualTo("service-type-database/build-db.in"); + assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); + } + + @Test + public void testParseIssue117() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue117.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(0) + .getSource() + .getChangePosition()) + .containsExactly(24, 27); + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(0) + .getTarget() + .getChangePosition()) + .containsExactly(24, 27); + + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(1) + .getSource() + .getChangePosition()) + .containsExactly(64); + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(1) + .getTarget() + .getChangePosition()) + .containsExactly(64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74); + + // diff.getFiles().forEach(f -> { + // System.out.println("File: " + f.getFromFile()); + // f.getPatch().getDeltas().forEach(delta -> { + // + // System.out.println(delta); + // System.out.println("Source: "); + // System.out.println(delta.getSource().getPosition()); + // System.out.println(delta.getSource().getChangePosition()); + // + // System.out.println("Target: "); + // System.out.println(delta.getTarget().getPosition()); + // System.out.println(delta.getTarget().getChangePosition()); + // }); + // }); + } + + @Test + public void testParseIssue122() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(1); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); + } + + @Test + public void testParseIssue123() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + assertThat(diff.getFiles()) + .extracting(f -> f.getFromFile()) + .contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); + } + + @Test + public void testParseIssue141() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue141.diff")); + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getFromFile()).isEqualTo("a.txt"); + assertThat(file1.getToFile()).isEqualTo("a1.txt"); + } + + @Test + public void testParseIssue182add() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryAdded()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182delete() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryDeleted()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182edit() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryEdited()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182mode() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getOldMode()).isEqualTo("100644"); + assertThat(file1.getNewMode()).isEqualTo("100755"); + } + + @Test + public void testParseIssue193Copy() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue193.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getCopyFrom()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.pcf"); + assertThat(file1.getCopyTo()) + .isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java index 725e0e2a..22d192d8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java @@ -15,32 +15,33 @@ */ package com.github.difflib.unifieddiff; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.github.difflib.patch.PatchFailedException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled("for next release") public class UnifiedDiffRoundTripNewLineTest { - @Test - public void testIssue135MissingNoNewLineInPatched() throws IOException, PatchFailedException { - String beforeContent = "rootProject.name = \"sample-repo\""; - String afterContent = "rootProject.name = \"sample-repo\"\n"; - String patch = "diff --git a/settings.gradle b/settings.gradle\n" + - "index ef3b8e2..ab30124 100644\n" + - "--- a/settings.gradle\n" + - "+++ b/settings.gradle\n" + - "@@ -1 +1 @@\n" + - "-rootProject.name = \"sample-repo\"\n" + - "\\ No newline at end of file\n" + - "+rootProject.name = \"sample-repo\"\n"; - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(patch.getBytes())); - String unifiedAfterContent = unifiedDiff.getFiles().get(0).getPatch() - .applyTo(Arrays.asList(beforeContent.split("\n"))).stream().collect(joining("\n")); - assertEquals(afterContent, unifiedAfterContent); - } + @Test + public void testIssue135MissingNoNewLineInPatched() throws IOException, PatchFailedException { + String beforeContent = "rootProject.name = \"sample-repo\""; + String afterContent = "rootProject.name = \"sample-repo\"\n"; + String patch = "diff --git a/settings.gradle b/settings.gradle\n" + "index ef3b8e2..ab30124 100644\n" + + "--- a/settings.gradle\n" + + "+++ b/settings.gradle\n" + + "@@ -1 +1 @@\n" + + "-rootProject.name = \"sample-repo\"\n" + + "\\ No newline at end of file\n" + + "+rootProject.name = \"sample-repo\"\n"; + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(patch.getBytes())); + String unifiedAfterContent = + unifiedDiff.getFiles().get(0).getPatch().applyTo(Arrays.asList(beforeContent.split("\n"))).stream() + .collect(joining("\n")); + assertEquals(afterContent, unifiedAfterContent); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java index d1892771..bb8bdcb5 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java @@ -1,5 +1,9 @@ package com.github.difflib.unifieddiff; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.DiffUtils; import com.github.difflib.TestConstants; import com.github.difflib.patch.Patch; @@ -13,154 +17,152 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class UnifiedDiffRoundTripTest { - public static List fileToLines(String filename) throws FileNotFoundException, IOException { - List lines = new ArrayList<>(); - String line = ""; - try (BufferedReader in = new BufferedReader(new FileReader(filename))) { - while ((line = in.readLine()) != null) { - lines.add(line); - } - } - return lines; - } - - @Test - public void testGenerateUnified() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); - - verify(origLines, revLines, "original.txt", "revised.txt"); - } - - @Test - public void testGenerateUnifiedWithOneDelta() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); - - verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); - } - - @Test - public void testGenerateUnifiedDiffWithoutAnyDeltas() throws IOException { - List test = Arrays.asList("abc"); - Patch patch = DiffUtils.diff(test, test); - StringWriter writer = new StringWriter(); - - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("abc", "abc", patch)), - name -> test, - writer, 0); - - System.out.println(writer); - } - - @Test - public void testDiff_Issue10() throws IOException { - final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); - final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( - new ByteArrayInputStream(patchLines.stream().collect(joining("\n")).getBytes()) - ); - - final Patch p = unifiedDiff.getFiles().get(0).getPatch(); - try { - DiffUtils.patch(baseLines, p); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - /** - * Issue 12 - */ - @Test - @Disabled - public void testPatchWithNoDeltas() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); - verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); - } - - @Test - public void testDiff5() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); - verify(lines1, lines2, "5A.txt", "5B.txt"); - } - - /** - * Issue 19 - */ - @Test - public void testDiffWithHeaderLineInText() throws IOException { - List original = new ArrayList<>(); - List revised = new ArrayList<>(); - - original.add("test line1"); - original.add("test line2"); - original.add("test line 4"); - original.add("test line 5"); - - revised.add("test line1"); - revised.add("test line2"); - revised.add("@@ -2,6 +2,7 @@"); - revised.add("test line 4"); - revised.add("test line 5"); - - Patch patch = DiffUtils.diff(original, revised); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("original", "revised", patch)), - name -> original, - writer, 10); - - System.out.println(writer.toString()); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(writer.toString().getBytes())); - } - - private void verify(List origLines, List revLines, - String originalFile, String revisedFile) throws IOException { - Patch patch = DiffUtils.diff(origLines, revLines); - - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from(originalFile, revisedFile, patch)), - name -> origLines, - writer, 10); - - System.out.println(writer.toString()); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(writer.toString().getBytes())); - - List patchedLines; - try { -// if (unifiedDiff.getFiles().isEmpty()) { -// patchedLines = new ArrayList<>(origLines); -// } else { -// Patch fromUnifiedPatch = unifiedDiff.getFiles().get(0).getPatch(); -// patchedLines = fromUnifiedPatch.applyTo(origLines); -// } - patchedLines = unifiedDiff.applyPatchTo(file -> originalFile.equals(file), origLines); - assertEquals(revLines.size(), patchedLines.size()); - for (int i = 0; i < revLines.size(); i++) { - String l1 = revLines.get(i); - String l2 = patchedLines.get(i); - if (!l1.equals(l2)) { - fail("Line " + (i + 1) + " of the patched file did not match the revised original"); - } - } - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } + + @Test + public void testGenerateUnified() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + + verify(origLines, revLines, "original.txt", "revised.txt"); + } + + @Test + public void testGenerateUnifiedWithOneDelta() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); + + verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); + } + + @Test + public void testGenerateUnifiedDiffWithoutAnyDeltas() throws IOException { + List test = Arrays.asList("abc"); + Patch patch = DiffUtils.diff(test, test); + StringWriter writer = new StringWriter(); + + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("abc", "abc", patch)), name -> test, writer, 0); + + System.out.println(writer); + } + + @Test + public void testDiff_Issue10() throws IOException { + final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); + final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream( + patchLines.stream().collect(joining("\n")).getBytes())); + + final Patch p = unifiedDiff.getFiles().get(0).getPatch(); + try { + DiffUtils.patch(baseLines, p); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + /** + * Issue 12 + */ + @Test + @Disabled + public void testPatchWithNoDeltas() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); + verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); + } + + @Test + public void testDiff5() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); + verify(lines1, lines2, "5A.txt", "5B.txt"); + } + + /** + * Issue 19 + */ + @Test + public void testDiffWithHeaderLineInText() throws IOException { + List original = new ArrayList<>(); + List revised = new ArrayList<>(); + + original.add("test line1"); + original.add("test line2"); + original.add("test line 4"); + original.add("test line 5"); + + revised.add("test line1"); + revised.add("test line2"); + revised.add("@@ -2,6 +2,7 @@"); + revised.add("test line 4"); + revised.add("test line 5"); + + Patch patch = DiffUtils.diff(original, revised); + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("original", "revised", patch)), + name -> original, + writer, + 10); + + System.out.println(writer.toString()); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( + new ByteArrayInputStream(writer.toString().getBytes())); + } + + private void verify(List origLines, List revLines, String originalFile, String revisedFile) + throws IOException { + Patch patch = DiffUtils.diff(origLines, revLines); + + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from(originalFile, revisedFile, patch)), + name -> origLines, + writer, + 10); + + System.out.println(writer.toString()); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( + new ByteArrayInputStream(writer.toString().getBytes())); + + List patchedLines; + try { + // if (unifiedDiff.getFiles().isEmpty()) { + // patchedLines = new ArrayList<>(origLines); + // } else { + // Patch fromUnifiedPatch = unifiedDiff.getFiles().get(0).getPatch(); + // patchedLines = fromUnifiedPatch.applyTo(origLines); + // } + patchedLines = unifiedDiff.applyPatchTo(file -> originalFile.equals(file), origLines); + assertEquals(revLines.size(), patchedLines.size()); + for (int i = 0; i < revLines.size(); i++) { + String l1 = revLines.get(i); + String l2 = patchedLines.get(i); + if (!l1.equals(l2)) { + fail("Line " + (i + 1) + " of the patched file did not match the revised original"); + } + } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java index af48d485..ccbff82d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java @@ -15,6 +15,8 @@ */ package com.github.difflib.unifieddiff; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.github.difflib.DiffUtils; import com.github.difflib.patch.Patch; import java.io.ByteArrayInputStream; @@ -28,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; /** @@ -37,49 +38,51 @@ */ public class UnifiedDiffWriterTest { - public UnifiedDiffWriterTest() { - } + public UnifiedDiffWriterTest() {} + + @Test + public void testWrite() throws URISyntaxException, IOException { + String str = readFile( + UnifiedDiffReaderTest.class + .getResource("jsqlparser_patch_1.diff") + .toURI(), + Charset.defaultCharset()); + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(str.getBytes())); + + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write(diff, f -> Collections.emptyList(), writer, 5); + System.out.println(writer.toString()); + } + + /** + * Issue 47 + */ + @Test + public void testWriteWithNewFile() throws URISyntaxException, IOException { + + List original = new ArrayList<>(); + List revised = new ArrayList<>(); - @Test - public void testWrite() throws URISyntaxException, IOException { - String str = readFile(UnifiedDiffReaderTest.class.getResource("jsqlparser_patch_1.diff").toURI(), Charset.defaultCharset()); - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(str.getBytes())); + revised.add("line1"); + revised.add("line2"); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write(diff, f -> Collections.emptyList(), writer, 5); - System.out.println(writer.toString()); - } - - /** - * Issue 47 - */ - @Test - public void testWriteWithNewFile() throws URISyntaxException, IOException { - - List original = new ArrayList<>(); - List revised = new ArrayList<>(); + Patch patch = DiffUtils.diff(original, revised); + UnifiedDiff diff = new UnifiedDiff(); + diff.addFile(UnifiedDiffFile.from(null, "revised", patch)); - revised.add("line1"); - revised.add("line2"); + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write(diff, f -> original, writer, 5); + System.out.println(writer.toString()); - Patch patch = DiffUtils.diff(original, revised); - UnifiedDiff diff = new UnifiedDiff(); - diff.addFile( UnifiedDiffFile.from(null, "revised", patch) ); + String[] lines = writer.toString().split("\\n"); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write(diff, f -> original, writer, 5); - System.out.println(writer.toString()); - - String[] lines = writer.toString().split("\\n"); - - assertEquals("--- /dev/null", lines[0]); - assertEquals("+++ revised", lines[1]); - assertEquals("@@ -0,0 +1,2 @@", lines[2]); - } + assertEquals("--- /dev/null", lines[0]); + assertEquals("+++ revised", lines[1]); + assertEquals("@@ -0,0 +1,2 @@", lines[2]); + } - static String readFile(URI path, Charset encoding) - throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, encoding); - } + static String readFile(URI path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } } diff --git a/pom.xml b/pom.xml index aa1deae3..6dc5de72 100644 --- a/pom.xml +++ b/pom.xml @@ -1,246 +1,272 @@ - 4.0.0 - io.github.java-diff-utils - java-diff-utils-parent - 4.16-SNAPSHOT - java-diff-utils-parent - pom - - java-diff-utils - java-diff-utils-jgit - - The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. - https://github.com/java-diff-utils/java-diff-utils - 2009 - - - - sonatype-nexus-staging - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - sonatype-nexus-snapshots - https://oss.sonatype.org/content/repositories/snapshots - - - - - scm:git:https://github.com/java-diff-utils/java-diff-utils.git - scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git - https://github.com/java-diff-utils/java-diff-utils.git - HEAD - - - GitHub Issues - https://github.com/java-diff-utils/java-diff-utils/issues - - - - java-diff-utils - - - - - Tobias Warneke - t.warneke@gmx.net - - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - A business-friendly OSS license - - - - UTF-8 - 1.8 - 1.8 - - + 4.0.0 + io.github.java-diff-utils + java-diff-utils-parent + 4.16-SNAPSHOT + pom + java-diff-utils-parent + The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. + https://github.com/java-diff-utils/java-diff-utils + 2009 + + + java-diff-utils + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + A business-friendly OSS license + + + + + + Tobias Warneke + t.warneke@gmx.net + + + + java-diff-utils + java-diff-utils-jgit + + + + scm:git:https://github.com/java-diff-utils/java-diff-utils.git + scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git + HEAD + https://github.com/java-diff-utils/java-diff-utils.git + + + GitHub Issues + https://github.com/java-diff-utils/java-diff-utils/issues + + + + + sonatype-nexus-staging + https://oss.sonatype.org/service/local/staging/deploy/maven2 + + + sonatype-nexus-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + + + UTF-8 + 1.8 + 1.8 + + + + + org.junit.jupiter + junit-jupiter + 5.11.4 + test + + + org.assertj + assertj-core + 3.27.3 + test + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + true + false + forked-path + sign-release-artifacts + + + + org.apache.felix + maven-bundle-plugin + 3.5.1 + + + bundle-manifest + + manifest + + process-classes + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + ${javadoc.opts} + none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + true + true + ${project.build.sourceDirectory} + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - org.junit.jupiter - junit-jupiter - 5.11.4 - test - - - org.assertj - assertj-core - 3.27.3 - test - + + com.puppycrawl.tools + checkstyle + 8.29 + - - + + + verify-style + + check + + process-classes + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + **/LR*.java + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.30.0 + + + + + true + 2 + + + + + false + + + + + + + apply + + validate + + + + + + + + sign-release-artifacts + + + performRelease + true + + + - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + + sign + + verify - true - false - forked-path - sign-release-artifacts + f22e0543 - - - org.apache.felix - maven-bundle-plugin - 3.5.1 - - - bundle-manifest - process-classes - - manifest - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.1.1 - - ${javadoc.opts} - none - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.6.0 - - - verify-style - process-classes - - check - - - - - true - true - ${project.build.sourceDirectory} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.puppycrawl.tools - checkstyle - 8.29 - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.2 - - - **/LR*.java - - - + + + + + + + + doclint-java8-disable + + [1.8,) + + + -Xdoclint:none + + + + long-running-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + xxx + + + - - - - sign-release-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - f22e0543 - - - - - - - - - doclint-java8-disable - - [1.8,) - - - -Xdoclint:none - - - - long-running-tests - - - - org.apache.maven.plugins - maven-surefire-plugin - - - xxx - - - - - - - + + + From c4b752fa51a2e6dc743d410f4706b8a89c458bb9 Mon Sep 17 00:00:00 2001 From: tw Date: Mon, 7 Jul 2025 23:00:25 +0200 Subject: [PATCH 097/107] fixes #211 --- pom.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6dc5de72..61b466b3 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,7 @@ https://github.com/java-diff-utils/java-diff-utils/issues
+ UTF-8 1.8 @@ -210,6 +212,15 @@ + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-nexus + + From 637cb7b6a309d66ff5e0cec2b3ffea52f867edc7 Mon Sep 17 00:00:00 2001 From: tw Date: Tue, 8 Jul 2025 01:00:59 +0200 Subject: [PATCH 098/107] [maven-release-plugin] prepare release java-diff-utils-parent-4.16 --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 766719b6..98db8553 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.16-SNAPSHOT + 4.16 java-diff-utils-jgit jar diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index e0c1139f..fb2d1b9d 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.16-SNAPSHOT + 4.16 io.github.java-diff-utils java-diff-utils diff --git a/pom.xml b/pom.xml index 61b466b3..e5dd60af 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.16-SNAPSHOT + 4.16 pom java-diff-utils-parent The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. @@ -37,7 +37,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git - HEAD + java-diff-utils-parent-4.16 https://github.com/java-diff-utils/java-diff-utils.git From b117553de7ca8590ee77dcb207e2886b2836edc3 Mon Sep 17 00:00:00 2001 From: tw Date: Tue, 8 Jul 2025 01:01:01 +0200 Subject: [PATCH 099/107] [maven-release-plugin] prepare for next development iteration --- java-diff-utils-jgit/pom.xml | 2 +- java-diff-utils/pom.xml | 2 +- pom.xml | 42 ++++++++++++++++++------------------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index 98db8553..a5203b3f 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.16 + 4.17-SNAPSHOT java-diff-utils-jgit jar diff --git a/java-diff-utils/pom.xml b/java-diff-utils/pom.xml index fb2d1b9d..cbe4f34b 100644 --- a/java-diff-utils/pom.xml +++ b/java-diff-utils/pom.xml @@ -4,7 +4,7 @@ io.github.java-diff-utils java-diff-utils-parent - 4.16 + 4.17-SNAPSHOT io.github.java-diff-utils java-diff-utils diff --git a/pom.xml b/pom.xml index e5dd60af..e39a81d8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.github.java-diff-utils java-diff-utils-parent - 4.16 + 4.17-SNAPSHOT pom java-diff-utils-parent The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. @@ -37,7 +37,7 @@ scm:git:https://github.com/java-diff-utils/java-diff-utils.git scm:git:ssh://git@github.com:java-diff-utils/java-diff-utils.git - java-diff-utils-parent-4.16 + HEAD https://github.com/java-diff-utils/java-diff-utils.git @@ -132,28 +132,28 @@ ${project.build.sourceDirectory} - + - - - - - - - - - - - + + + + + + + + + + + - + - - - - + + + + - + @@ -191,7 +191,7 @@ 2.30.0 - + true 2 From 8b70f765919898d29568f97b7c77e9d6df93a66b Mon Sep 17 00:00:00 2001 From: Alexander Nikitin Date: Fri, 1 Aug 2025 20:47:55 +0300 Subject: [PATCH 100/107] Fix typo --- .../java/com/github/difflib/algorithm/jgit/HistogramDiff.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index 0476b269..a1cc1ce8 100644 --- a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -28,7 +28,7 @@ import org.eclipse.jgit.diff.SequenceComparator; /** - * HistorgramDiff using JGit - Library. This one is much more performant than the original Myers + * HistogramDiff using JGit - Library. This one is much more performant than the original Myers * implementation. * * @author toben From 1ef6162ec61d34dca0aec2f94e9318e4389b3ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Go=C3=9F?= <75903169+agoss94@users.noreply.github.com> Date: Fri, 5 Sep 2025 21:42:32 +0200 Subject: [PATCH 101/107] Adjust generic parameters for PECS principle. (#214) Spotless update Co-authored-by: Andreas Goss --- .../difflib/algorithm/jgit/HistogramDiff.java | 7 ++-- .../java/com/github/difflib/DiffUtils.java | 26 +++++++----- .../algorithm/DiffAlgorithmFactory.java | 2 +- .../difflib/algorithm/DiffAlgorithmI.java | 2 +- .../difflib/algorithm/myers/MyersDiff.java | 18 +++++---- .../myers/MyersDiffWithLinearSpace.java | 15 +++---- .../com/github/difflib/patch/EqualDelta.java | 4 +- .../java/com/github/difflib/patch/Patch.java | 8 ++-- pom.xml | 40 +++++++++---------- 9 files changed, 66 insertions(+), 56 deletions(-) diff --git a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index a1cc1ce8..62ac4b97 100644 --- a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -36,7 +36,8 @@ public class HistogramDiff implements DiffAlgorithmI { @Override - public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + public List computeDiff( + List source, List target, DiffAlgorithmListener progress) { Objects.requireNonNull(source, "source list must not be null"); Objects.requireNonNull(target, "target list must not be null"); if (progress != null) { @@ -92,9 +93,9 @@ public int hash(DataList s, int i) { class DataList extends Sequence { - final List data; + final List data; - public DataList(List data) { + public DataList(List data) { this.data = data; } diff --git a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java index e43be506..8448a760 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -57,7 +57,8 @@ public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ - public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { + public static Patch diff( + List original, List revised, DiffAlgorithmListener progress) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); } @@ -69,7 +70,7 @@ public static Patch diff(List original, List revised, DiffAlgorithm * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ - public static Patch diff(List original, List revised) { + public static Patch diff(List original, List revised) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); } @@ -82,7 +83,7 @@ public static Patch diff(List original, List revised) { * @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ - public static Patch diff(List original, List revised, boolean includeEqualParts) { + public static Patch diff(List original, List revised, boolean includeEqualParts) { return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); } @@ -110,7 +111,8 @@ public static Patch diff(String sourceText, String targetText, DiffAlgor * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ - public static Patch diff(List source, List target, BiPredicate equalizer) { + public static Patch diff( + List source, List target, BiPredicate equalizer) { if (equalizer != null) { return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer)); } @@ -118,7 +120,10 @@ public static Patch diff(List source, List target, BiPredicate Patch diff( - List original, List revised, DiffAlgorithmI algorithm, DiffAlgorithmListener progress) { + List original, + List revised, + DiffAlgorithmI algorithm, + DiffAlgorithmListener progress) { return diff(original, revised, algorithm, progress, false); } @@ -135,8 +140,8 @@ public static Patch diff( * revised sequences. Never {@code null}. */ public static Patch diff( - List original, - List revised, + List original, + List revised, DiffAlgorithmI algorithm, DiffAlgorithmListener progress, boolean includeEqualParts) { @@ -157,7 +162,8 @@ public static Patch diff( * @return The patch describing the difference between the original and * revised sequences. Never {@code null}. */ - public static Patch diff(List original, List revised, DiffAlgorithmI algorithm) { + public static Patch diff( + List original, List revised, DiffAlgorithmI algorithm) { return diff(original, revised, algorithm, null); } @@ -196,7 +202,7 @@ public static Patch diffInline(String original, String revised) { * @return the revised list. * @throws PatchFailedException if the patch cannot be applied. */ - public static List patch(List original, Patch patch) throws PatchFailedException { + public static List patch(List original, Patch patch) throws PatchFailedException { return patch.applyTo(original); } @@ -208,7 +214,7 @@ public static List patch(List original, Patch patch) throws PatchFa * @return the original list. * @throws PatchFailedException if the patch cannot be applied. */ - public static List unpatch(List revised, Patch patch) { + public static List unpatch(List revised, Patch patch) { return patch.restore(revised); } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java index 307ee7e3..5b9d71c9 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java @@ -25,5 +25,5 @@ public interface DiffAlgorithmFactory { DiffAlgorithmI create(); - DiffAlgorithmI create(BiPredicate equalizer); + DiffAlgorithmI create(BiPredicate equalizer); } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java index a12b499a..ee421c59 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java @@ -34,7 +34,7 @@ public interface DiffAlgorithmI { * @param progress progress listener * @return */ - List computeDiff(List source, List target, DiffAlgorithmListener progress); + List computeDiff(List source, List target, DiffAlgorithmListener progress); /** * Simple extension to compute a changeset using arrays. diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 5233e02b..c990208a 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -31,13 +31,13 @@ */ public final class MyersDiff implements DiffAlgorithmI { - private final BiPredicate equalizer; + private final BiPredicate equalizer; public MyersDiff() { equalizer = Object::equals; } - public MyersDiff(final BiPredicate equalizer) { + public MyersDiff(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @@ -48,7 +48,8 @@ public MyersDiff(final BiPredicate equalizer) { * Return empty diff if get the error while procession the difference. */ @Override - public List computeDiff(final List source, final List target, DiffAlgorithmListener progress) { + public List computeDiff( + final List source, final List target, DiffAlgorithmListener progress) { Objects.requireNonNull(source, "source list must not be null"); Objects.requireNonNull(target, "target list must not be null"); @@ -71,9 +72,10 @@ public List computeDiff(final List source, final List target, Diff * @param orig The original sequence. * @param rev The revised sequence. * @return A minimum {@link PathNode Path} accross the differences graph. - * @throws DifferentiationFailedException if a diff path could not be found. + * @throws IllegalStateException if a diff path could not be found. */ - private PathNode buildPath(final List orig, final List rev, DiffAlgorithmListener progress) { + private PathNode buildPath( + final List orig, final List rev, DiffAlgorithmListener progress) { Objects.requireNonNull(orig, "original sequence is null"); Objects.requireNonNull(rev, "revised sequence is null"); @@ -140,10 +142,10 @@ private PathNode buildPath(final List orig, final List rev, DiffAlgorithmL * @param orig The original sequence. * @param rev The revised sequence. * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be + * @throws IllegalStateException if a {@link Patch} could not be * built from the given path. */ - private List buildRevision(PathNode actualPath, List orig, List rev) { + private List buildRevision(PathNode actualPath, List orig, List rev) { Objects.requireNonNull(actualPath, "path is null"); Objects.requireNonNull(orig, "original sequence is null"); Objects.requireNonNull(rev, "revised sequence is null"); @@ -190,7 +192,7 @@ public DiffAlgorithmI create() { } @Override - public DiffAlgorithmI create(BiPredicate equalizer) { + public DiffAlgorithmI create(BiPredicate equalizer) { return new MyersDiff<>(equalizer); } }; diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java index f8c734ba..2e1a53c4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -32,19 +32,20 @@ */ public class MyersDiffWithLinearSpace implements DiffAlgorithmI { - private final BiPredicate equalizer; + private final BiPredicate equalizer; public MyersDiffWithLinearSpace() { equalizer = Object::equals; } - public MyersDiffWithLinearSpace(final BiPredicate equalizer) { + public MyersDiffWithLinearSpace(final BiPredicate equalizer) { Objects.requireNonNull(equalizer, "equalizer must not be null"); this.equalizer = equalizer; } @Override - public List computeDiff(List source, List target, DiffAlgorithmListener progress) { + public List computeDiff( + List source, List target, DiffAlgorithmListener progress) { Objects.requireNonNull(source, "source list must not be null"); Objects.requireNonNull(target, "target list must not be null"); @@ -200,10 +201,10 @@ private class DiffData { final int[] vDown; final int[] vUp; final List script; - final List source; - final List target; + final List source; + final List target; - public DiffData(List source, List target) { + public DiffData(List source, List target) { this.source = source; this.target = target; size = source.size() + target.size() + 2; @@ -237,7 +238,7 @@ public DiffAlgorithmI create() { } @Override - public DiffAlgorithmI create(BiPredicate equalizer) { + public DiffAlgorithmI create(BiPredicate equalizer) { return new MyersDiffWithLinearSpace<>(equalizer); } }; diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java index 64f05685..98cb561f 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java @@ -18,7 +18,7 @@ import java.util.List; /** - * This delta contains equal lines of data. Therefore nothing is to do in applyTo and restore. + * This delta contains equal lines of data. Therefore, nothing is to do in applyTo and restore. * @author tobens */ public class EqualDelta extends AbstractDelta { @@ -49,6 +49,6 @@ public String toString() { @Override public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new EqualDelta(original, revised); + return new EqualDelta<>(original, revised); } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java index 6a54d820..31c2c3e4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -54,7 +54,7 @@ public Patch(int estimatedPatchSize) { * @return A new list containing the applied patch. * @throws PatchFailedException if the patch cannot be applied */ - public List applyTo(List target) throws PatchFailedException { + public List applyTo(List target) throws PatchFailedException { List result = new ArrayList<>(target); applyToExisting(result); return result; @@ -244,7 +244,7 @@ public Patch withConflictOutput(ConflictOutput conflictOutput) { * @param target The list to copy and apply changes to. * @return A new list, containing the restored state. */ - public List restore(List target) { + public List restore(List target) { List result = new ArrayList<>(target); restoreToExisting(result); return result; @@ -294,12 +294,12 @@ public static Patch generate(List original, List revised, List Chunk buildChunk(int start, int end, List data) { + private static Chunk buildChunk(int start, int end, List data) { return new Chunk<>(start, new ArrayList<>(data.subList(start, end))); } public static Patch generate( - List original, List revised, List _changes, boolean includeEquals) { + List original, List revised, List _changes, boolean includeEquals) { Patch patch = new Patch<>(_changes.size()); int startOriginal = 0; int startRevised = 0; diff --git a/pom.xml b/pom.xml index e39a81d8..d0d5b28c 100644 --- a/pom.xml +++ b/pom.xml @@ -132,28 +132,28 @@ ${project.build.sourceDirectory} - + - - - - - - - - - - - + + + + + + + + + + + - + - - - - + + + + - + @@ -188,10 +188,10 @@ com.diffplug.spotless spotless-maven-plugin - 2.30.0 + 2.46.0 - + true 2 From 9b13cc8199e2a7fd7a66f1b7e017a1c46e4776c0 Mon Sep 17 00:00:00 2001 From: tw Date: Fri, 5 Sep 2025 21:47:34 +0200 Subject: [PATCH 102/107] downgrade spotless --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d0d5b28c..12c736d1 100644 --- a/pom.xml +++ b/pom.xml @@ -188,7 +188,7 @@ com.diffplug.spotless spotless-maven-plugin - 2.46.0 + 2.30.0 From ba008888abf56c7f2675608bb5222418e0698060 Mon Sep 17 00:00:00 2001 From: tw Date: Fri, 5 Sep 2025 21:48:49 +0200 Subject: [PATCH 103/107] downgrade spotless From 8a6206e3d4ecaee04bb17109d2b538ebfa1cb6db Mon Sep 17 00:00:00 2001 From: Matthias Braun Date: Mon, 29 Sep 2025 20:11:23 +0200 Subject: [PATCH 104/107] Update README.md Fix spelling. --- README.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index cbcba7af..d2d459a7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Build Status](https://travis-ci.org/java-diff-utils/java-diff-utils.svg?branch=master)](https://travis-ci.org/java-diff-utils/java-diff-utils) -[![Build Status using Github Actions](https://github.com/java-diff-utils/java-diff-utils/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/java-diff-utils/java-diff-utils/actions?query=workflow%3A%22Java+CI+with+Maven%22) +[![Build Status using GitHub Actions](https://github.com/java-diff-utils/java-diff-utils/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/java-diff-utils/java-diff-utils/actions?query=workflow%3A%22Java+CI+with+Maven%22) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/002c53aa0c924f71ac80a2f65446dfdd)](https://www.codacy.com/gh/java-diff-utils/java-diff-utils/dashboard?utm_source=github.com&utm_medium=referral&utm_content=java-diff-utils/java-diff-utils&utm_campaign=Badge_Grade) @@ -12,30 +12,30 @@ ## Intro -Diff Utils library is an OpenSource library for performing the comparison operations between texts: computing diffs, applying patches, generating unified diffs or parsing them, generating diff output for easy future displaying (like side-by-side view) and so on. +Diff Utils library is an open source library for performing comparison operations between texts: computing diffs, applying patches, generating unified diffs or parsing them, generating diff output for easy future displaying (like side-by-side view) and so on. -Main reason to build this library was the lack of easy-to-use libraries with all the usual stuff you need while working with diff files. Originally it was inspired by JRCS library and it's nice design of diff module. +The main reason to build this library was the lack of easy-to-use libraries with all the usual stuff you need while working with diff files. Originally it was inspired by JRCS library and its nice design of diff module. **This is originally a fork of java-diff-utils from Google Code Archive.** ## GPG Signature Validation -The gpg singing key in [KEYS] is used for this projects artifacts. +The gpg singing key in [KEYS] is used for this project's artifacts. ## API -Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) +Javadocs of the actual release version: [Javadocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) ## Examples -Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful informations and examples. +Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful information and examples. -These two outputs are generated using this java-diff-utils. The source code can also be found at the *Examples* page: +These two outputs are generated using java-diff-utils. The source code can also be found at the *Examples* page: **Producing a one liner including all difference information.** ```Java -//create a configured DiffRowGenerator +// Create a configured DiffRowGenerator DiffRowGenerator generator = DiffRowGenerator.create() .showInlineDiffs(true) .mergeOriginalRevised(true) @@ -44,15 +44,15 @@ DiffRowGenerator generator = DiffRowGenerator.create() .newTag(f -> "**") //introduce markdown style for bold .build(); -//compute the differences for two test texts. +// Compute the differences for two test texts List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence."), + Arrays.asList("This is a test sentence."), Arrays.asList("This is a test for diffutils.")); - + System.out.println(rows.get(0).getOldLine()); ``` -This is a test ~senctence~**for diffutils**. +This is a test ~sentence~**for diffutils**. **Producing a side by side view of computed differences.** @@ -64,9 +64,9 @@ DiffRowGenerator generator = DiffRowGenerator.create() .newTag(f -> "**") .build(); List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), + Arrays.asList("This is a test sentence.", "This is the second line.", "And here is the finish."), Arrays.asList("This is a test for diffutils.", "This is the second line.")); - + System.out.println("|original|new|"); System.out.println("|--------|---|"); for (DiffRow row : rows) { @@ -76,14 +76,14 @@ for (DiffRow row : rows) { |original|new| |--------|---| -|This is a test ~senctence~.|This is a test **for diffutils**.| +|This is a test ~sentence~.|This is a test **for diffutils**.| |This is the second line.|This is the second line.| |~And here is the finish.~|| ## Main Features -* computing the difference between two texts. -* capable to hand more than plain ascii. Arrays or List of any type that implements hashCode() and equals() correctly can be subject to differencing using this library +* Computing the difference between two texts. +* Capable to handle more than plain ASCII. Arrays or List of any type that implements hashCode() and equals() correctly can be subject to differencing using this library * patch and unpatch the text with the given patch * parsing the unified diff format * producing human-readable differences @@ -95,10 +95,10 @@ for (DiffRow row : rows) { ### Algorithms -* Myer's diff +* Myers diff * HistogramDiff -But it can easily replaced by any other which is better for handing your texts. I have plan to add implementation of some in future. +But it can easily be replaced by any other which is better for handling your texts. I have a plan to add the implementation of some in the future. ## Source Code conventions @@ -123,7 +123,7 @@ This is a valid piece of source code: ### To Install -Just add the code below to your maven dependencies: +Just add the code below to your Maven dependencies: ```xml @@ -133,7 +133,7 @@ Just add the code below to your maven dependencies: ``` -or using gradle: +or using Gradle: ```groovy // https://mvnrepository.com/artifact/io.github.java-diff-utils/java-diff-utils From c42ba091c5f965963b4cc024d0d274103bbf3bc0 Mon Sep 17 00:00:00 2001 From: Matthias Braun Date: Sun, 5 Oct 2025 20:36:53 +0200 Subject: [PATCH 105/107] Update README.md More typo fixing. --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d2d459a7..924342db 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The main reason to build this library was the lack of easy-to-use libraries with ## GPG Signature Validation -The gpg singing key in [KEYS] is used for this project's artifacts. +The GPG signing key in [KEYS] is used for this project's artifacts. ## API @@ -30,18 +30,18 @@ Javadocs of the actual release version: [Javadocs java-diff-utils](https://java- Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful information and examples. -These two outputs are generated using java-diff-utils. The source code can also be found at the *Examples* page: +These two outputs are generated using java-diff-utils. The source code can also be found on the [Examples](https://github.com/java-diff-utils/java-diff-utils/wiki/Examples) page: **Producing a one liner including all difference information.** -```Java +```java // Create a configured DiffRowGenerator DiffRowGenerator generator = DiffRowGenerator.create() .showInlineDiffs(true) .mergeOriginalRevised(true) .inlineDiffByWord(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold + .oldTag(f -> "~") // Introduce markdown style for strikethrough + .newTag(f -> "**") // Introduce markdown style for bold .build(); // Compute the differences for two test texts @@ -54,9 +54,9 @@ System.out.println(rows.get(0).getOldLine()); This is a test ~sentence~**for diffutils**. -**Producing a side by side view of computed differences.** +**Producing a side-by-side view of computed differences.** -```Java +```java DiffRowGenerator generator = DiffRowGenerator.create() .showInlineDiffs(true) .inlineDiffByWord(true) @@ -83,15 +83,15 @@ for (DiffRow row : rows) { ## Main Features * Computing the difference between two texts. -* Capable to handle more than plain ASCII. Arrays or List of any type that implements hashCode() and equals() correctly can be subject to differencing using this library -* patch and unpatch the text with the given patch -* parsing the unified diff format -* producing human-readable differences -* inline difference construction +* Capable of handling more than plain ASCII. Arrays or lists of any type that implement `hashCode()` and `equals()` correctly can be subject to differencing using this library +* Patch and unpatch the text with the given patch +* Parsing the unified diff format +* Producing human-readable differences +* Inline difference construction * Algorithms: - * Myers Standard Algorithm + * Myers standard algorithm * Myers with linear space improvement - * HistogramDiff using JGit Library + * HistogramDiff using the JGit library ### Algorithms @@ -102,7 +102,7 @@ But it can easily be replaced by any other which is better for handling your tex ## Source Code conventions -Recently a checkstyle process was integrated into the build process. java-diff-utils follows the sun java format convention. There are no TABs allowed. Use spaces. +Recently a checkstyle process was integrated into the build process. java-diff-utils follows the Sun Java format convention. There are no tabs allowed. Use spaces. ```java public static Patch diff(List original, List revised, From 696edc4c1a26d3021082cf643842d534aa8e07d7 Mon Sep 17 00:00:00 2001 From: tusharsoni52 <116971475+tusharsoni52@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:48:26 +0530 Subject: [PATCH 106/107] feat(diffrow): add processEqualities hook and ensure equalities are processed for unchanged lines (Fixes #219) (#224) - Introduces a new protected method `processEqualities(String)` in DiffRowGenerator - Builder exposes `.processEqualities(Function)` - Equal (unchanged) lines now invoke processEqualities() - Inline diffs remain unchanged (as expected in Option 3) - Added new test suite DiffRowGeneratorEqualitiesTest - Updated documentation and Javadoc for new extension point - Fixes #219 (HTML escaping issue when inline diff by word) Co-authored-by: Tushar Soni --- .../github/difflib/text/DiffRowGenerator.java | 38 +++++++- .../text/DiffRowGeneratorEqualitiesTest.java | 92 +++++++++++++++++++ 2 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index e7e09ac5..82a5b94e 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -189,6 +189,8 @@ static void wrapInTag( private final Function lineNormalizer; private final Function processDiffs; private final Function>> inlineDeltaMerger; + // processor for equal (unchanged) lines + private final Function equalityProcessor; private final boolean showInlineDiffs; private final boolean replaceOriginalLinefeedInChangesWithSpaces; @@ -214,6 +216,7 @@ private DiffRowGenerator(Builder builder) { lineNormalizer = builder.lineNormalizer; processDiffs = builder.processDiffs; inlineDeltaMerger = builder.inlineDeltaMerger; + equalityProcessor = builder.equalityProcessor; replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; @@ -262,7 +265,8 @@ public List generateDiffRows(final List original, Patch // Copy the final matching chunk if any. for (String line : original.subList(endPos, original.size())) { - diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); + String processed = processEqualities(line); + diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed)); } return diffRows; } @@ -276,7 +280,8 @@ private int transformDeltaIntoDiffRow( Chunk rev = delta.getTarget(); for (String line : original.subList(endPos, orig.getPosition())) { - diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); + String processed = processEqualities(line); + diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed)); } switch (delta.getType()) { @@ -496,6 +501,19 @@ private String preprocessLine(String line) { } } + /** + * Hook for processing equal (unchanged) text segments. + * Delegates to the builder-configured equalityProcessor if present. + * + * @author tusharsoni52 + * @param text + * @return + * + */ + protected String processEqualities(final String text) { + return equalityProcessor != null ? equalityProcessor.apply(text) : text; + } + /** * This class used for building the DiffRowGenerator. * @@ -521,6 +539,8 @@ public static class Builder { private boolean replaceOriginalLinefeedInChangesWithSpaces = false; private Function>> inlineDeltaMerger = DEFAULT_INLINE_DELTA_MERGER; + // Processor for equalities + private Function equalityProcessor = null; private Builder() {} @@ -613,6 +633,20 @@ public Builder processDiffs(Function processDiffs) { return this; } + /** + * Processor for equal (unchanged) text parts. + * Allows applying the same escaping/transformation as for diffs. + * + * @author tusharsoni52 + * @param equalityProcessor + * @return + * + */ + public Builder processEqualities(Function equalityProcessor) { + this.equalityProcessor = equalityProcessor; + return this; + } + /** * Set the column width of generated lines of original and revised * texts. diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java new file mode 100644 index 00000000..c71f455b --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java @@ -0,0 +1,92 @@ +package com.github.difflib.text; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class DiffRowGeneratorEqualitiesTest { + + @Test + public void testDefaultEqualityProcessingLeavesTextUnchanged() { + DiffRowGenerator generator = + DiffRowGenerator.create().showInlineDiffs(false).build(); + + List rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello world")); + + assertEquals(1, rows.size()); + assertEquals("hello world", rows.get(0).getOldLine()); + assertEquals("hello world", rows.get(0).getNewLine()); + assertEquals(DiffRow.Tag.EQUAL, rows.get(0).getTag()); + } + + @Test + public void testCustomEqualityProcessingIsApplied() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .processEqualities(text -> "[" + text + "]") + .build(); + + List rows = generator.generateDiffRows(Arrays.asList("A", "B"), Arrays.asList("A", "B")); + + assertEquals(2, rows.size()); + assertEquals("[A]", rows.get(0).getOldLine()); + assertEquals("[B]", rows.get(1).getOldLine()); + } + + /** + * Verifies that processEqualities can be used to HTML-escape unchanged + * lines while still working together with the default HTML-oriented + * lineNormalizer. + */ + @Test + public void testHtmlEscapingEqualitiesWorksWithDefaultNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .processEqualities(s -> s.replace("<", "<").replace(">", ">")) + .build(); + + // both lines are equal -> Tag.EQUAL, processEqualities is invoked + List rows = generator.generateDiffRows(Arrays.asList("hello "), Arrays.asList("hello ")); + + DiffRow row = rows.get(0); + + assertTrue(row.getOldLine().contains("<world>")); + assertTrue(row.getNewLine().contains("<world>")); + } + + /** + * Ensures equalities are processed while inline diff markup is still + * present somewhere in the line. + */ + @Test + public void testEqualitiesProcessedButInlineDiffStillPresent() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .processEqualities(s -> "(" + s + ")") + .build(); + + List rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello there")); + + DiffRow row = rows.get(0); + + System.out.println("OLD = " + row.getOldLine()); + System.out.println("NEW = " + row.getNewLine()); + + // Row must be CHANGE + assertEquals(DiffRow.Tag.CHANGE, row.getTag()); + + // Inline diff markup must appear + assertTrue( + row.getOldLine().contains("span") || row.getNewLine().contains("span"), + "Expected inline diff markup in old or new line"); + + // Equalities inside CHANGE row must NOT be wrapped by processEqualities + // Option 3 does NOT modify inline equalities + assertTrue(row.getOldLine().startsWith("hello "), "Equal (unchanged) inline segment should remain unchanged"); + } +} From b35ebf420274e1cf18e322218ed92b713a166b18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 08:08:13 +0200 Subject: [PATCH 107/107] Bump org.assertj:assertj-core from 3.27.3 to 3.27.7 (#226) Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.7. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 12c736d1..90da69f8 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ org.assertj assertj-core - 3.27.3 + 3.27.7 test