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/.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 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/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 cfe763bf..cbcba7af 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,13 @@ 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.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/apidocs/) ## Examples @@ -30,10 +34,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**.| @@ -49,13 +89,13 @@ This is a test ~senctence~**for diffutils**. * 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. @@ -89,7 +129,7 @@ Just add the code below to your maven dependencies: io.github.java-diff-utils java-diff-utils - 4.11 + 4.15 ``` @@ -97,5 +137,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" ``` diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index eadc1cfb..a5203b3f 100644 --- a/java-diff-utils-jgit/pom.xml +++ b/java-diff-utils-jgit/pom.xml @@ -1,57 +1,58 @@ - 4.0.0 - - io.github.java-diff-utils - java-diff-utils-parent - 4.12-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.17-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.8.1.202007141445-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 e3f6cd5b..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 @@ -28,78 +28,78 @@ 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 */ 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 e9e78f76..cbe4f34b 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.12-SNAPSHOT - - - UTF-8 - + java-diff-utils-parent + 4.17-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 44a8cff6..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 @@ -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; @@ -30,172 +30,194 @@ import java.util.function.BiPredicate; /** - * Implements the difference and patching engine + * Utility class to implement the difference and patching engine. */ public final class DiffUtils { - /** - * 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}. - */ - public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); - } - - public static Patch diff(List original, List revised) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), 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. - */ - 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 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}. - */ - 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 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 - * - * @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}. - */ - 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 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}. - */ - 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 - * @param revised - * @return - */ - 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; - } - - 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 - * - * @param original the original text - * @param patch the given patch - * @return the revised text - * @throws PatchFailedException if can't apply patch - */ - public static List patch(List original, Patch patch) - throws PatchFailedException { - return patch.applyTo(original); - } - - /** - * Unpatch the revised text for a given patch - * - * @param revised the revised text - * @param patch the given patch - * @return the original text - */ - public static List unpatch(List revised, Patch patch) { - return patch.restore(revised); - } - - 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 06a61073..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,15 +15,18 @@ */ 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; +import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * @@ -31,286 +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() { - } + 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 37d51813..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 garantee 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/MeyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java deleted file mode 100644 index 7daa91d2..00000000 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiff.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 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. - */ -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; -import com.github.difflib.patch.Patch; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.BiPredicate; - -/** - * A clean-room implementation of Eugene Meyers greedy differencing algorithm. - */ -public final class MeyersDiff implements DiffAlgorithmI { - - private final BiPredicate equalizer; - - public MeyersDiff() { - equalizer = Object::equals; - } - - public MeyersDiff(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 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 deleted file mode 100644 index bb3577f8..00000000 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpace.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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.DiffAlgorithmFactory; -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; -import java.util.function.Consumer; - -/** - * - * @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) { - 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 MeyersDiffWithLinearSpace(); - } - - @Override - public DiffAlgorithmI - create(BiPredicate < T, T > equalizer) { - return new MeyersDiffWithLinearSpace(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/MyersDiff.java new file mode 100644 index 00000000..5233e02b --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -0,0 +1,198 @@ +/* + * Copyright 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. + */ +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; +import com.github.difflib.patch.Patch; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.BiPredicate; + +/** + * A clean-room implementation of Eugene Myers greedy differencing algorithm. + */ +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 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 new file mode 100644 index 00000000..f8c734ba --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -0,0 +1,245 @@ +/* + * 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.DiffAlgorithmFactory; +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; +import java.util.function.Consumer; + +/** + * + * @author tw + */ +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 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 a315e010..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 verifyAntApplyTo(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 7e55ac0d..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 @@ -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. @@ -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 5e3e51f8..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,281 +37,313 @@ */ public final class Patch implements Serializable { - private final List> deltas; - - public Patch() { - this(10); - } - - public Patch(int estimatedPatchSize) { - deltas = new ArrayList<>(estimatedPatchSize); - } - - /** - * Apply this patch to the given target - * - * @return the patched text - * @throws PatchFailedException if can't apply patch - */ - public List applyTo(List target) throws PatchFailedException { - List result = new ArrayList<>(target); - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - VerifyChunk valid = delta.verifyAntApplyTo(result); - if (valid != VerifyChunk.OK) { - conflictOutput.processConflict(valid, delta, result); - } - } - 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. - */ - 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. 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. - * - * @param target the given target - * @return the restored text - */ - public List restore(List target) { - List result = new ArrayList<>(target); - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - delta.restore(result); - } - return result; - } - - /** - * 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 9dbf052b..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; @@ -24,13 +26,14 @@ 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; 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 @@ -49,630 +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 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 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; - - replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces; - - Objects.requireNonNull(inlineDiffSplitter); - Objects.requireNonNull(lineNormalizer); - } - - /** - * 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> inlineDeltas = DiffUtils.diff(origList, revList, equalizer).getDeltas(); - - 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 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; - } - - /** - * 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 - */ - 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; - } - } + 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 new file mode 100644 index 00000000..1e68a637 --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java @@ -0,0 +1,80 @@ +/* + * 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 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 + */ +public final 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.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()); + + 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.remove(newDeltas.size() - 1); + 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..1c9027f1 --- /dev/null +++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java @@ -0,0 +1,50 @@ +/* + * 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 com.github.difflib.patch.AbstractDelta; +import java.util.List; + +/** + * 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/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 099f4142..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,131 +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 toTimestamp; - private String index; - private String newFileMode; - private String deletedFileMode; - 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 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 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 0ac22ce8..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,396 +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 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); - 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, - 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, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, - NEW_FILE_MODE, DELETED_FILE_MODE)) { - 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 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 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 000b095a..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; @@ -13,218 +19,231 @@ 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; 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 meyer 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 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 e13d41aa..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,213 +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"); - Patch patch = DiffUtils.diff(test, test); - UnifiedDiffUtils.generateUnifiedDiff("abc", "abc", test, patch, 0); - } - - @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/MeyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java deleted file mode 100644 index 7a10f1f1..00000000 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MeyersDiffWithLinearSpaceTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.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 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(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 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(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 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/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 10db0e43..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 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()); - } - - @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 MeyersDiff().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 new file mode 100644 index 00000000..87f80134 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java @@ -0,0 +1,97 @@ +/* + * 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 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 java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +/** + * + * @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); + } + + @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"); + } +} 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 deleted file mode 100644 index 65ea1839..00000000 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMeyersDiffWithLinearSpacePatchTest.java +++ /dev/null @@ -1,394 +0,0 @@ -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 java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -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; - -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()); - } - } - - // 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 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(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/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java new file mode 100644 index 00000000..92668ce8 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java @@ -0,0 +1,395 @@ +package com.github.difflib.algorithm.myers; + +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; +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; + +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), + }; +} 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 new file mode 100644 index 00000000..a3f0b7b9 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java @@ -0,0 +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 java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +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; + } +} 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 82e72294..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.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; @@ -24,88 +22,85 @@ 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()); - } - - @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/PatchWithMeyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java deleted file mode 100644 index d7bf2798..00000000 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 deleted file mode 100644 index 8a62992e..00000000 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMeyerDiffWithLinearSpaceTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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()); - } - } -} 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 new file mode 100644 index 00000000..0dab69f3 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java @@ -0,0 +1,70 @@ +/* + * 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 static com.github.difflib.patch.Patch.CONFLICT_PRODUCES_MERGE_CONFLICT; +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; + +/** + * + * @author tw + */ +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"); + + 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()); + } + } + + @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/patch/PatchWithMyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java new file mode 100644 index 00000000..f04a1c93 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java @@ -0,0 +1,80 @@ +/* + * 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 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 org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * + * @author tw + */ +public class PatchWithMyerDiffWithLinearSpaceTest { + + @BeforeAll + public static void setupClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.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"); + + 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()); + } + } +} 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..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,764 +1,904 @@ 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; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Paths; 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; -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; 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 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 dcf32182..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,367 +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"); - } + 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 new file mode 100644 index 00000000..22d192d8 --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java @@ -0,0 +1,47 @@ +/* + * 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 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 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); + } +} 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/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. 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 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 00000000..7b071fcd Binary files /dev/null and b/java-diff-utils/src/test/resources/com/github/difflib/text/test.zip differ 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 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"> + + + + 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(); + } + } +} + diff --git a/pom.xml b/pom.xml index 40cdd391..e39a81d8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,245 +1,283 @@ - 4.0.0 - io.github.java-diff-utils - java-diff-utils-parent - 4.12-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.17-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 + + + + + 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.7.1 - test - - - org.assertj - assertj-core - 3.19.0 - 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 + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-nexus + + + + + + + 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 + f22e0543 - - - org.apache.felix - maven-bundle-plugin - 3.3.0 - - - 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.1.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.0.0-M4 - - - **/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 - - - - - - - + + +