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 70dfa521..924342db 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Build Status](https://travis-ci.org/java-diff-utils/java-diff-utils.svg?branch=master)](https://travis-ci.org/java-diff-utils/java-diff-utils) -[![Build Status using Github Actions](https://github.com/java-diff-utils/java-diff-utils/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/java-diff-utils/java-diff-utils/actions?query=workflow%3A%22Java+CI+with+Maven%22) +[![Build Status using GitHub Actions](https://github.com/java-diff-utils/java-diff-utils/workflows/Java%20CI%20with%20Maven/badge.svg)](https://github.com/java-diff-utils/java-diff-utils/actions?query=workflow%3A%22Java+CI+with+Maven%22) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/002c53aa0c924f71ac80a2f65446dfdd)](https://www.codacy.com/gh/java-diff-utils/java-diff-utils/dashboard?utm_source=github.com&utm_medium=referral&utm_content=java-diff-utils/java-diff-utils&utm_campaign=Badge_Grade) @@ -12,47 +12,51 @@ ## Intro -Diff Utils library is an OpenSource library for performing the comparison operations between texts: computing diffs, applying patches, generating unified diffs or parsing them, generating diff output for easy future displaying (like side-by-side view) and so on. +Diff Utils library is an open source library for performing comparison operations between texts: computing diffs, applying patches, generating unified diffs or parsing them, generating diff output for easy future displaying (like side-by-side view) and so on. -Main reason to build this library was the lack of easy-to-use libraries with all the usual stuff you need while working with diff files. Originally it was inspired by JRCS library and it's nice design of diff module. +The main reason to build this library was the lack of easy-to-use libraries with all the usual stuff you need while working with diff files. Originally it was inspired by JRCS library and its nice design of diff module. **This is originally a fork of java-diff-utils from Google Code Archive.** +## GPG Signature Validation + +The GPG signing key in [KEYS] is used for this project's artifacts. + ## API -Javadocs of the actual release version: [JavaDocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) +Javadocs of the actual release version: [Javadocs java-diff-utils](https://java-diff-utils.github.io/java-diff-utils/4.10/docs/apidocs/) ## Examples -Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful informations and examples. +Look [here](https://github.com/java-diff-utils/java-diff-utils/wiki) to find more helpful information and examples. -These two outputs are generated using this java-diff-utils. The source code can also be found at the *Examples* page: +These two outputs are generated using java-diff-utils. The source code can also be found on the [Examples](https://github.com/java-diff-utils/java-diff-utils/wiki/Examples) page: **Producing a one liner including all difference information.** -```Java -//create a configured DiffRowGenerator +```java +// Create a configured DiffRowGenerator DiffRowGenerator generator = DiffRowGenerator.create() .showInlineDiffs(true) .mergeOriginalRevised(true) .inlineDiffByWord(true) - .oldTag(f -> "~") //introduce markdown style for strikethrough - .newTag(f -> "**") //introduce markdown style for bold + .oldTag(f -> "~") // Introduce markdown style for strikethrough + .newTag(f -> "**") // Introduce markdown style for bold .build(); -//compute the differences for two test texts. +// Compute the differences for two test texts List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence."), + Arrays.asList("This is a test sentence."), Arrays.asList("This is a test for diffutils.")); - + System.out.println(rows.get(0).getOldLine()); ``` -This is a test ~senctence~**for diffutils**. +This is a test ~sentence~**for diffutils**. -**Producing a side by side view of computed differences.** +**Producing a side-by-side view of computed differences.** -```Java +```java DiffRowGenerator generator = DiffRowGenerator.create() .showInlineDiffs(true) .inlineDiffByWord(true) @@ -60,9 +64,9 @@ DiffRowGenerator generator = DiffRowGenerator.create() .newTag(f -> "**") .build(); List rows = generator.generateDiffRows( - Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), + Arrays.asList("This is a test sentence.", "This is the second line.", "And here is the finish."), Arrays.asList("This is a test for diffutils.", "This is the second line.")); - + System.out.println("|original|new|"); System.out.println("|--------|---|"); for (DiffRow row : rows) { @@ -72,33 +76,33 @@ for (DiffRow row : rows) { |original|new| |--------|---| -|This is a test ~senctence~.|This is a test **for diffutils**.| +|This is a test ~sentence~.|This is a test **for diffutils**.| |This is the second line.|This is the second line.| |~And here is the finish.~|| ## Main Features -* computing the difference between two texts. -* capable to hand more than plain ascii. Arrays or List of any type that implements hashCode() and equals() correctly can be subject to differencing using this library -* patch and unpatch the text with the given patch -* parsing the unified diff format -* producing human-readable differences -* inline difference construction +* Computing the difference between two texts. +* Capable of handling more than plain ASCII. Arrays or lists of any type that implement `hashCode()` and `equals()` correctly can be subject to differencing using this library +* Patch and unpatch the text with the given patch +* Parsing the unified diff format +* Producing human-readable differences +* Inline difference construction * Algorithms: - * Myers Standard Algorithm + * Myers standard algorithm * Myers with linear space improvement - * HistogramDiff using JGit Library + * HistogramDiff using the JGit library ### Algorithms -* Myer's diff +* Myers diff * HistogramDiff -But it can easily replaced by any other which is better for handing your texts. I have plan to add implementation of some in future. +But it can easily be replaced by any other which is better for handling your texts. I have a plan to add the implementation of some in the future. ## Source Code conventions -Recently a checkstyle process was integrated into the build process. java-diff-utils follows the sun java format convention. There are no TABs allowed. Use spaces. +Recently a checkstyle process was integrated into the build process. java-diff-utils follows the Sun Java format convention. There are no tabs allowed. Use spaces. ```java public static Patch diff(List original, List revised, @@ -119,17 +123,17 @@ This is a valid piece of source code: ### To Install -Just add the code below to your maven dependencies: +Just add the code below to your Maven dependencies: ```xml io.github.java-diff-utils java-diff-utils - 4.12 + 4.15 ``` -or using gradle: +or using Gradle: ```groovy // https://mvnrepository.com/artifact/io.github.java-diff-utils/java-diff-utils diff --git a/java-diff-utils-jgit/pom.xml b/java-diff-utils-jgit/pom.xml index daac9fe1..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.15 - - 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.13.3.202401111512-r - - - com.googlecode.javaewah - JavaEWAH - - - commons-codec - commons-codec - - - commons-logging - commons-logging - - - org.apache.httpcomponents - httpclient - - - com.jcraft - jsch - - - org.slf4j - slf4j-api - - - - - ${project.groupId} - java-diff-utils - ${project.version} - - - \ No newline at end of file + + + org.junit.jupiter + junit-jupiter + test + + + + org.eclipse.jgit + org.eclipse.jgit + 5.13.3.202401111512-r + + + com.googlecode.javaewah + JavaEWAH + + + commons-codec + commons-codec + + + commons-logging + commons-logging + + + org.apache.httpcomponents + httpclient + + + com.jcraft + jsch + + + org.slf4j + slf4j-api + + + + + ${project.groupId} + java-diff-utils + ${project.version} + + + diff --git a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index e3f6cd5b..62ac4b97 100644 --- a/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/java-diff-utils-jgit/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -28,78 +28,79 @@ import org.eclipse.jgit.diff.SequenceComparator; /** - * HistorgramDiff using JGit - Library. This one is much more performant than the orginal Myers + * HistogramDiff 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 13ecfd39..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.15 - - - 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 8917772b..8448a760 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java @@ -34,195 +34,196 @@ */ public final class DiffUtils { - /** - * This factory generates the DEFAULT_DIFF algorithm for all these routines. - */ - static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); - - /** - * Sets the default diff algorithm factory to be used by all diff routines. - * - * @param factory a {@link DiffAlgorithmFactory} represnting 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} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, DiffAlgorithmListener progress) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); - } - - /** - * Computes the difference between two sequences of elements using the default diff algorithm. - * - * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); - } - - /** - * Computes the difference between two sequences of elements using the default diff algorithm. - * - * @param a generic representing the type of the elements to be compared. - * @param original a {@link List} represnting the original sequence of elements. Must not be {@code null}. - * @param revised a {@link List} represnting the revised sequence of elements. Must not be {@code null}. - * @param includeEqualParts a {@link boolean} represnting whether to include equal parts in the resulting patch. - * @return The patch describing the difference between the original and revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, boolean includeEqualParts) { - return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); - } - - /** - * Computes the difference between two strings using the default diff algorithm. - * - * @param sourceText a {@link String} represnting the original string. Must not be {@code null}. - * @param targetText a {@link String} represnting the revised string. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the progress listener. Can be {@code null}. - * @return The patch describing the difference between the original and revised strings. Never {@code null}. - */ - public static Patch diff(String sourceText, String targetText, - DiffAlgorithmListener progress) { - 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} represnting the original text. Must not be {@code null}. - * @param target a {@link List} represnting the revised text. Must not be {@code null}. - * @param equalizer a {@link BiPredicate} represnting the equalizer object to replace the default compare - * algorithm (Object.equals). If {@code null} the default equalizer of the - * default algorithm is used. - * @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} represnting the original text. Must not be {@code null}. - * @param revised a {@link List} represnting the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. - * @param progress a {@link DiffAlgorithmListener} represnting the diff algorithm listener. - * @param includeEqualParts Include equal data parts into the patch. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diff(List original, List revised, - DiffAlgorithmI algorithm, DiffAlgorithmListener progress, - boolean includeEqualParts) { - 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} represnting the original text. Must not be {@code null}. - * @param revised a {@link List} represnting the revised text. Must not be {@code null}. - * @param algorithm a {@link DiffAlgorithmI} represnting the diff algorithm. Must not be {@code null}. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - 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} represnting the original text. Must not be {@code null}. - * @param revised a {@link String} represnting the revised text. Must not be {@code null}. - * @return The patch describing the difference between the original and - * revised sequences. Never {@code null}. - */ - public static Patch diffInline(String original, String revised) { - List origList = new ArrayList<>(); - 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} represnting the original list. - * @param patch a {@link List} represnting the patch to apply. - * @return the revised list. - * @throws PatchFailedException if the patch cannot be applied. - */ - public static List patch(List original, Patch patch) - throws PatchFailedException { - return patch.applyTo(original); - } - - /** - * Applies the given patch to the revised list and returns the original list. - * - * @param revised a {@link List} represnting the revised list. - * @param patch a {@link Patch} represnting the patch to apply. - * @return the original list. - * @throws PatchFailedException if the patch cannot be applied. - */ - public static List unpatch(List revised, Patch patch) { - return patch.restore(revised); - } - - private static List compressLines(List lines, String delimiter) { - if (lines.isEmpty()) { - return Collections.emptyList(); - } - return Collections.singletonList(String.join(delimiter, lines)); - } - - private DiffUtils() { - } + /** + * This factory generates the DEFAULT_DIFF algorithm for all these routines. + */ + static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory(); + + /** + * Sets the default diff algorithm factory to be used by all diff routines. + * + * @param factory a {@link DiffAlgorithmFactory} representing the new default diff algorithm factory. + */ + public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) { + DEFAULT_DIFF = factory; + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff( + List original, List revised, DiffAlgorithmListener progress) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress); + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null); + } + + /** + * Computes the difference between two sequences of elements using the default diff algorithm. + * + * @param a generic representing the type of the elements to be compared. + * @param original a {@link List} representing the original sequence of elements. Must not be {@code null}. + * @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}. + * @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised, boolean includeEqualParts) { + return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts); + } + + /** + * Computes the difference between two strings using the default diff algorithm. + * + * @param sourceText a {@link String} representing the original string. Must not be {@code null}. + * @param targetText a {@link String} representing the revised string. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}. + * @return The patch describing the difference between the original and revised strings. Never {@code null}. + */ + public static Patch diff(String sourceText, String targetText, DiffAlgorithmListener progress) { + return DiffUtils.diff(Arrays.asList(sourceText.split("\n")), Arrays.asList(targetText.split("\n")), progress); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param source a {@link List} representing the original text. Must not be {@code null}. + * @param target a {@link List} representing the revised text. Must not be {@code null}. + * @param equalizer a {@link BiPredicate} representing the equalizer object to replace the default compare + * algorithm (Object.equals). If {@code null} the default equalizer of the + * default algorithm is used. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff( + List source, List target, BiPredicate equalizer) { + if (equalizer != null) { + return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer)); + } + return DiffUtils.diff(source, target, new MyersDiff<>()); + } + + public static Patch diff( + List original, + List revised, + DiffAlgorithmI algorithm, + DiffAlgorithmListener progress) { + return diff(original, revised, algorithm, progress, false); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. + * @param progress a {@link DiffAlgorithmListener} representing the diff algorithm listener. + * @param includeEqualParts Include equal data parts into the patch. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff( + List original, + List revised, + DiffAlgorithmI algorithm, + DiffAlgorithmListener progress, + boolean includeEqualParts) { + Objects.requireNonNull(original, "original must not be null"); + Objects.requireNonNull(revised, "revised must not be null"); + Objects.requireNonNull(algorithm, "algorithm must not be null"); + + return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts); + } + + /** + * Computes the difference between the original and revised list of elements + * with default diff algorithm + * + * @param original a {@link List} representing the original text. Must not be {@code null}. + * @param revised a {@link List} representing the revised text. Must not be {@code null}. + * @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diff( + List original, List revised, DiffAlgorithmI algorithm) { + return diff(original, revised, algorithm, null); + } + + /** + * Computes the difference between the given texts inline. This one uses the + * "trick" to make out of texts lists of characters, like DiffRowGenerator + * does and merges those changes at the end together again. + * + * @param original a {@link String} representing the original text. Must not be {@code null}. + * @param revised a {@link String} representing the revised text. Must not be {@code null}. + * @return The patch describing the difference between the original and + * revised sequences. Never {@code null}. + */ + public static Patch diffInline(String original, String revised) { + List origList = new ArrayList<>(); + List revList = new ArrayList<>(); + for (Character character : original.toCharArray()) { + origList.add(character.toString()); + } + for (Character character : revised.toCharArray()) { + revList.add(character.toString()); + } + Patch patch = DiffUtils.diff(origList, revList); + for (AbstractDelta delta : patch.getDeltas()) { + delta.getSource().setLines(compressLines(delta.getSource().getLines(), "")); + delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), "")); + } + return patch; + } + + /** + * Applies the given patch to the original list and returns the revised list. + * + * @param original a {@link List} representing the original list. + * @param patch a {@link List} representing the patch to apply. + * @return the revised list. + * @throws PatchFailedException if the patch cannot be applied. + */ + public static List patch(List original, Patch patch) throws PatchFailedException { + return patch.applyTo(original); + } + + /** + * Applies the given patch to the revised list and returns the original list. + * + * @param revised a {@link List} representing the revised list. + * @param patch a {@link Patch} representing the patch to apply. + * @return the original list. + * @throws PatchFailedException if the patch cannot be applied. + */ + public static List unpatch(List revised, Patch patch) { + return patch.restore(revised); + } + + private static List compressLines(List lines, String delimiter) { + if (lines.isEmpty()) { + return Collections.emptyList(); + } + return Collections.singletonList(String.join(delimiter, lines)); + } + + private DiffUtils() {} } diff --git a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 727008db..ca6a34ad 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/java-diff-utils/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -15,11 +15,10 @@ */ package com.github.difflib; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; -import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -35,433 +34,430 @@ */ public final class UnifiedDiffUtils { - private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern - .compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@$"); - private static final String NULL_FILE_INDICATOR = "/dev/null"; - - /** - * Parse the given text in unified format and creates the list of deltas for it. - * - * @param diff the text in unified format - * @return the patch with deltas. - */ - public static Patch parseUnifiedDiff(List diff) { - boolean inPrelude = true; - List rawChunk = new ArrayList<>(); - Patch patch = new Patch<>(); - - int old_ln = 0; - int new_ln = 0; - String tag; - String rest; - for (String line : diff) { - // Skip leading lines until after we've seen one starting with '+++' - if (inPrelude) { - if (line.startsWith("+++")) { - inPrelude = false; - } - continue; - } - Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); - if (m.find()) { - // Process the lines in the previous chunk - processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); - // Parse the @@ header - old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); - new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); - - if (old_ln == 0) { - old_ln = 1; - } - if (new_ln == 0) { - new_ln = 1; - } - } else { - if (line.length() > 0) { - tag = line.substring(0, 1); - rest = line.substring(1); - if (" ".equals(tag) || "+".equals(tag) || "-".equals(tag)) { - rawChunk.add(new String[]{tag, rest}); - } - } else { - rawChunk.add(new String[]{" ", ""}); - } - } - } - - // Process the lines in the last chunk - processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); - - return patch; - } - - private static void processLinesInPrevChunk(List rawChunk, Patch patch, int old_ln, int new_ln) { - String tag; - String rest; - if (!rawChunk.isEmpty()) { - List oldChunkLines = new ArrayList<>(); - List newChunkLines = new ArrayList<>(); - - List removePosition = new ArrayList<>(); - List addPosition = new ArrayList<>(); - int removeNum = 0; - int addNum = 0; - for (String[] raw_line : rawChunk) { - tag = raw_line[0]; - rest = raw_line[1]; - if (" ".equals(tag) || "-".equals(tag)) { - removeNum++; - oldChunkLines.add(rest); - if ("-".equals(tag)) { - removePosition.add(old_ln - 1 + removeNum); - } - } - if (" ".equals(tag) || "+".equals(tag)) { - addNum++; - newChunkLines.add(rest); - if ("+".equals(tag)) { - addPosition.add(new_ln - 1 + addNum); - } - } - } - patch.addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, oldChunkLines, removePosition), new Chunk<>( - new_ln - 1, newChunkLines, addPosition))); - rawChunk.clear(); - } - } - - /** - * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format - * text representing the Patch. Author: Bill James (tankerbay@gmail.com). - * - * @param originalFileName - Filename of the original (unrevised file) - * @param revisedFileName - Filename of the revised file - * @param originalLines - Lines of the original file - * @param patch - Patch created by the diff() function - * @param contextSize - number of lines of context output around each difference in the file. - * @return List of strings representing the Unified Diff representation of the Patch argument. - */ - public static List generateUnifiedDiff(String originalFileName, - String revisedFileName, List originalLines, Patch patch, - int contextSize) { - if (!patch.getDeltas().isEmpty()) { - List ret = new ArrayList<>(); - ret.add("--- " + Optional.ofNullable(originalFileName).orElse(NULL_FILE_INDICATOR)); - ret.add("+++ " + Optional.ofNullable(revisedFileName).orElse(NULL_FILE_INDICATOR)); - - List> patchDeltas = new ArrayList<>( - patch.getDeltas()); - - // code outside the if block also works for single-delta issues. - List> deltas = new ArrayList<>(); // current - // list - // of - // Delta's to - // process - AbstractDelta delta = patchDeltas.get(0); - deltas.add(delta); // add the first Delta to the current set - // if there's more than 1 Delta, we may need to output them together - if (patchDeltas.size() > 1) { - for (int i = 1; i < patchDeltas.size(); i++) { - int position = delta.getSource().getPosition(); // store - // the - // current - // position - // of - // the first Delta - - // Check if the next Delta is too close to the current - // position. - // And if it is, add it to the current set - AbstractDelta nextDelta = patchDeltas.get(i); - if ((position + delta.getSource().size() + contextSize) >= (nextDelta - .getSource().getPosition() - contextSize)) { - deltas.add(nextDelta); - } else { - // if it isn't, output the current set, - // then create a new set and add the current Delta to - // it. - List curBlock = processDeltas(originalLines, - deltas, contextSize, false); - ret.addAll(curBlock); - deltas.clear(); - deltas.add(nextDelta); - } - delta = nextDelta; - } - - } - // don't forget to process the last set of Deltas - List curBlock = processDeltas(originalLines, deltas, - contextSize, patchDeltas.size() == 1 && originalFileName == null); - ret.addAll(curBlock); - return ret; - } - return new ArrayList<>(); - } - - /** - * processDeltas takes a list of Deltas and outputs them together in a single block of - * Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com). - * - * @param origLines - the lines of the original file - * @param deltas - the Deltas to be output as a single block - * @param contextSize - the number of lines of context to place around block - * @return - */ - private static List processDeltas(List origLines, - List> deltas, int contextSize, boolean newFile) { - List buffer = new ArrayList<>(); - int origTotal = 0; // counter for total lines output from Original - int revTotal = 0; // counter for total lines output from Original - int line; - - AbstractDelta curDelta = deltas.get(0); - int origStart; - if (newFile) { - origStart = 0; - } else { - // NOTE: +1 to overcome the 0-offset Position - origStart = curDelta.getSource().getPosition() + 1 - contextSize; - if (origStart < 1) { - origStart = 1; - } - } - - int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; - if (revStart < 1) { - revStart = 1; - } - - // find the start of the wrapper context code - int contextStart = curDelta.getSource().getPosition() - contextSize; - if (contextStart < 0) { - contextStart = 0; // clamp to the start of the file - } - - // output the context before the first Delta - for (line = contextStart; line < curDelta.getSource().getPosition(); line++) { // - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // output the first Delta - buffer.addAll(getDeltaText(curDelta)); - origTotal += curDelta.getSource().getLines().size(); - revTotal += curDelta.getTarget().getLines().size(); - - int deltaIndex = 1; - while (deltaIndex < deltas.size()) { // for each of the other Deltas - AbstractDelta nextDelta = deltas.get(deltaIndex); - int intermediateStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = intermediateStart; line < nextDelta.getSource() - .getPosition(); line++) { - // output the code between the last Delta and this one - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - buffer.addAll(getDeltaText(nextDelta)); // output the Delta - origTotal += nextDelta.getSource().getLines().size(); - revTotal += nextDelta.getTarget().getLines().size(); - curDelta = nextDelta; - deltaIndex++; - } - - // Now output the post-Delta context code, clamping the end of the file - contextStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = contextStart; (line < (contextStart + contextSize)) - && (line < origLines.size()); line++) { - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // Create and insert the block header, conforming to the Unified Diff - // standard - StringBuilder header = new StringBuilder(); - header.append("@@ -"); - header.append(origStart); - header.append(","); - header.append(origTotal); - header.append(" +"); - header.append(revStart); - header.append(","); - header.append(revTotal); - header.append(" @@"); - buffer.add(0, header.toString()); - - return buffer; - } - - /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com). - * - * @param delta - the Delta to output - * @return list of String lines of code. - */ - private static List getDeltaText(AbstractDelta delta) { - List buffer = new ArrayList<>(); - for (String line : delta.getSource().getLines()) { - buffer.add("-" + line); - } - for (String line : delta.getTarget().getLines()) { - buffer.add("+" + line); - } - return buffer; - } - - private UnifiedDiffUtils() { - } - - /** - * Compare the differences between two files and return to the original file and diff format - * - * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. - * You can see all the differences and unmodified places from the original file. - * Also, this will be very easy and useful for making side-by-side comparison display applications, - * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) - * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) - * - * @param original Original file content - * @param revised revised file content - * - */ - public static List generateOriginalAndDiff(List original, List revised) { - return generateOriginalAndDiff(original, revised, null, null); - } - - - /** - * Compare the differences between two files and return to the original file and diff format - * - * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. - * You can see all the differences and unmodified places from the original file. - * Also, this will be very easy and useful for making side-by-side comparison display applications, - * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) - * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) - * - * @param original Original file content - * @param revised revised file content - * @param originalFileName Original file name - * @param revisedFileName revised file name - */ - public static List generateOriginalAndDiff(List original, List revised, String originalFileName, String revisedFileName) { - String originalFileNameTemp = originalFileName; - String revisedFileNameTemp = originalFileName; - if (originalFileNameTemp == null) { - originalFileNameTemp = "original"; - } - if (revisedFileNameTemp == null) { - revisedFileNameTemp = "revised"; - } - Patch patch = DiffUtils.diff(original, revised); - List unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0); - if (unifiedDiff.isEmpty()) { - unifiedDiff.add("--- " + originalFileNameTemp); - unifiedDiff.add("+++ " + revisedFileNameTemp); - unifiedDiff.add("@@ -0,0 +0,0 @@"); - } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { - unifiedDiff.set(1, unifiedDiff.get(1)); - unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); - } - List originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList()); - return insertOrig(originalWithPrefix, unifiedDiff); - } - - //Insert the diff format to the original file - private static List insertOrig(List original, List unifiedDiff) { - List result = new ArrayList<>(); - List> diffList = new ArrayList<>(); - List diff = new ArrayList<>(); - for (int i = 0; i < unifiedDiff.size(); i++) { - String u = unifiedDiff.get(i); - if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { - List twoList = new ArrayList<>(); - twoList.addAll(diff); - diffList.add(twoList); - diff.clear(); - diff.add(u); - continue; - } - if (i == unifiedDiff.size() - 1) { - diff.add(u); - List twoList = new ArrayList<>(); - twoList.addAll(diff); - diffList.add(twoList); - diff.clear(); - break; - } - diff.add(u); - } - insertOrig(diffList, result, original); - return result; - } - - //Insert the diff format to the original file - private static void insertOrig(List> diffList, List result, List original) { - for (int i = 0; i < diffList.size(); i++) { - List diff = diffList.get(i); - List nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); - String simb = i == 0 ? diff.get(2) : diff.get(0); - String nexSimb = nexDiff == null ? null : nexDiff.get(0); - insert(result, diff); - Map map = getRowMap(simb); - if (null != nexSimb) { - Map nexMap = getRowMap(nexSimb); - int start = 0; - if (map.get("orgRow") != 0) { - start = map.get("orgRow") + map.get("orgDel") - 1; - } - int end = nexMap.get("revRow") - 2; - insert(result, getOrigList(original, start, end)); - } - int start = map.get("orgRow") + map.get("orgDel") - 1; - start = start == -1 ? 0 : start; - if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { - insert(result, getOrigList(original, start, original.size() - 1)); - } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { - insert(result, getOrigList(original, start, original.size() - 1)); - } - } - } - - //Insert the unchanged content in the source file into result - private static void insert(List result, List noChangeContent) { - for (String ins : noChangeContent) { - result.add(ins); - } - } - - //Parse the line containing @@ to get the modified line number to delete or add a few lines - private static Map getRowMap(String str) { - Map map = new HashMap<>(); - if (str.startsWith("@@")) { - String[] sp = str.split(" "); - String org = sp[1]; - String[] orgSp = org.split(","); - map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); - map.put("orgDel", Integer.valueOf(orgSp[1])); - String[] revSp = org.split(","); - map.put("revRow", Integer.valueOf(revSp[0].substring(1))); - map.put("revAdd", Integer.valueOf(revSp[1])); - } - return map; - } - - //Get the specified part of the line from the original file - private static List getOrigList(List originalWithPrefix, int start, int end) { - List list = new ArrayList<>(); - if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) { - int startTemp = start; - for (; startTemp <= end; startTemp++) { - list.add(originalWithPrefix.get(startTemp)); - } - } - return list; - } + private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = + Pattern.compile("^@@\\s+-(\\d+)(?:,(\\d+))?\\s+\\+(\\d+)(?:,(\\d+))?\\s+@@.*$"); + private static final String NULL_FILE_INDICATOR = "/dev/null"; + + /** + * Parse the given text in unified format and creates the list of deltas for it. + * + * @param diff the text in unified format + * @return the patch with deltas. + */ + public static Patch parseUnifiedDiff(List diff) { + boolean inPrelude = true; + List rawChunk = new ArrayList<>(); + Patch patch = new Patch<>(); + + int old_ln = 0; + int new_ln = 0; + String tag; + String rest; + for (String line : diff) { + // Skip leading lines until after we've seen one starting with '+++' + if (inPrelude) { + if (line.startsWith("+++")) { + inPrelude = false; + } + continue; + } + Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); + if (m.find()) { + // Process the lines in the previous chunk + processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); + // Parse the @@ header + old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); + new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); + + if (old_ln == 0) { + old_ln = 1; + } + if (new_ln == 0) { + new_ln = 1; + } + } else { + if (line.length() > 0) { + tag = line.substring(0, 1); + rest = line.substring(1); + if (" ".equals(tag) || "+".equals(tag) || "-".equals(tag)) { + rawChunk.add(new String[] {tag, rest}); + } + } else { + rawChunk.add(new String[] {" ", ""}); + } + } + } + + // Process the lines in the last chunk + processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln); + + return patch; + } + + private static void processLinesInPrevChunk(List rawChunk, Patch patch, int old_ln, int new_ln) { + String tag; + String rest; + if (!rawChunk.isEmpty()) { + List oldChunkLines = new ArrayList<>(); + List newChunkLines = new ArrayList<>(); + + List removePosition = new ArrayList<>(); + List addPosition = new ArrayList<>(); + int removeNum = 0; + int addNum = 0; + for (String[] raw_line : rawChunk) { + tag = raw_line[0]; + rest = raw_line[1]; + if (" ".equals(tag) || "-".equals(tag)) { + removeNum++; + oldChunkLines.add(rest); + if ("-".equals(tag)) { + removePosition.add(old_ln - 1 + removeNum); + } + } + if (" ".equals(tag) || "+".equals(tag)) { + addNum++; + newChunkLines.add(rest); + if ("+".equals(tag)) { + addPosition.add(new_ln - 1 + addNum); + } + } + } + patch.addDelta(new ChangeDelta<>( + new Chunk<>(old_ln - 1, oldChunkLines, removePosition), + new Chunk<>(new_ln - 1, newChunkLines, addPosition))); + rawChunk.clear(); + } + } + + /** + * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format + * text representing the Patch. Author: Bill James (tankerbay@gmail.com). + * + * @param originalFileName - Filename of the original (unrevised file) + * @param revisedFileName - Filename of the revised file + * @param originalLines - Lines of the original file + * @param patch - Patch created by the diff() function + * @param contextSize - number of lines of context output around each difference in the file. + * @return List of strings representing the Unified Diff representation of the Patch argument. + */ + public static List generateUnifiedDiff( + String originalFileName, + String revisedFileName, + List originalLines, + Patch patch, + int contextSize) { + if (!patch.getDeltas().isEmpty()) { + List ret = new ArrayList<>(); + ret.add("--- " + Optional.ofNullable(originalFileName).orElse(NULL_FILE_INDICATOR)); + ret.add("+++ " + Optional.ofNullable(revisedFileName).orElse(NULL_FILE_INDICATOR)); + + List> patchDeltas = new ArrayList<>(patch.getDeltas()); + + // code outside the if block also works for single-delta issues. + List> deltas = new ArrayList<>(); // current + // list + // of + // Delta's to + // process + AbstractDelta delta = patchDeltas.get(0); + deltas.add(delta); // add the first Delta to the current set + // if there's more than 1 Delta, we may need to output them together + if (patchDeltas.size() > 1) { + for (int i = 1; i < patchDeltas.size(); i++) { + int position = delta.getSource().getPosition(); // store + // the + // current + // position + // of + // the first Delta + + // Check if the next Delta is too close to the current + // position. + // And if it is, add it to the current set + AbstractDelta nextDelta = patchDeltas.get(i); + if ((position + delta.getSource().size() + contextSize) + >= (nextDelta.getSource().getPosition() - contextSize)) { + deltas.add(nextDelta); + } else { + // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + List curBlock = processDeltas(originalLines, deltas, contextSize, false); + ret.addAll(curBlock); + deltas.clear(); + deltas.add(nextDelta); + } + delta = nextDelta; + } + } + // don't forget to process the last set of Deltas + List curBlock = processDeltas( + originalLines, deltas, contextSize, patchDeltas.size() == 1 && originalFileName == null); + ret.addAll(curBlock); + return ret; + } + return new ArrayList<>(); + } + + /** + * processDeltas takes a list of Deltas and outputs them together in a single block of + * Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com). + * + * @param origLines - the lines of the original file + * @param deltas - the Deltas to be output as a single block + * @param contextSize - the number of lines of context to place around block + * @return + */ + private static List processDeltas( + List origLines, List> deltas, int contextSize, boolean newFile) { + List buffer = new ArrayList<>(); + int origTotal = 0; // counter for total lines output from Original + int revTotal = 0; // counter for total lines output from Original + int line; + + AbstractDelta curDelta = deltas.get(0); + int origStart; + if (newFile) { + origStart = 0; + } else { + // NOTE: +1 to overcome the 0-offset Position + origStart = curDelta.getSource().getPosition() + 1 - contextSize; + if (origStart < 1) { + origStart = 1; + } + } + + int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; + if (revStart < 1) { + revStart = 1; + } + + // find the start of the wrapper context code + int contextStart = curDelta.getSource().getPosition() - contextSize; + if (contextStart < 0) { + contextStart = 0; // clamp to the start of the file + } + + // output the context before the first Delta + for (line = contextStart; line < curDelta.getSource().getPosition(); line++) { // + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // output the first Delta + buffer.addAll(getDeltaText(curDelta)); + origTotal += curDelta.getSource().getLines().size(); + revTotal += curDelta.getTarget().getLines().size(); + + int deltaIndex = 1; + while (deltaIndex < deltas.size()) { // for each of the other Deltas + AbstractDelta nextDelta = deltas.get(deltaIndex); + int intermediateStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = intermediateStart; line < nextDelta.getSource().getPosition(); line++) { + // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + buffer.addAll(getDeltaText(nextDelta)); // output the Delta + origTotal += nextDelta.getSource().getLines().size(); + revTotal += nextDelta.getTarget().getLines().size(); + curDelta = nextDelta; + deltaIndex++; + } + + // Now output the post-Delta context code, clamping the end of the file + contextStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // Create and insert the block header, conforming to the Unified Diff + // standard + StringBuilder header = new StringBuilder(); + header.append("@@ -"); + header.append(origStart); + header.append(","); + header.append(origTotal); + header.append(" +"); + header.append(revStart); + header.append(","); + header.append(revTotal); + header.append(" @@"); + buffer.add(0, header.toString()); + + return buffer; + } + + /** + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com). + * + * @param delta - the Delta to output + * @return list of String lines of code. + */ + private static List getDeltaText(AbstractDelta delta) { + List buffer = new ArrayList<>(); + for (String line : delta.getSource().getLines()) { + buffer.add("-" + line); + } + for (String line : delta.getTarget().getLines()) { + buffer.add("+" + line); + } + return buffer; + } + + private UnifiedDiffUtils() {} + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * + */ + public static List generateOriginalAndDiff(List original, List revised) { + return generateOriginalAndDiff(original, revised, null, null); + } + + /** + * Compare the differences between two files and return to the original file and diff format + * + * (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file. + * You can see all the differences and unmodified places from the original file. + * Also, this will be very easy and useful for making side-by-side comparison display applications, + * for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage) + * Wait for tools to display your differences on html pages, you only need to insert the return value into your js code) + * + * @param original Original file content + * @param revised revised file content + * @param originalFileName Original file name + * @param revisedFileName revised file name + */ + public static List generateOriginalAndDiff( + List original, List revised, String originalFileName, String revisedFileName) { + String originalFileNameTemp = originalFileName; + String revisedFileNameTemp = revisedFileName; + if (originalFileNameTemp == null) { + originalFileNameTemp = "original"; + } + if (revisedFileNameTemp == null) { + revisedFileNameTemp = "revised"; + } + Patch patch = DiffUtils.diff(original, revised); + List unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0); + if (unifiedDiff.isEmpty()) { + unifiedDiff.add("--- " + originalFileNameTemp); + unifiedDiff.add("+++ " + revisedFileNameTemp); + unifiedDiff.add("@@ -0,0 +0,0 @@"); + } else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) { + unifiedDiff.set(1, unifiedDiff.get(1)); + unifiedDiff.add(2, "@@ -0,0 +0,0 @@"); + } + List originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList()); + return insertOrig(originalWithPrefix, unifiedDiff); + } + + // Insert the diff format to the original file + private static List insertOrig(List original, List unifiedDiff) { + List result = new ArrayList<>(); + List> diffList = new ArrayList<>(); + List diff = new ArrayList<>(); + for (int i = 0; i < unifiedDiff.size(); i++) { + String u = unifiedDiff.get(i); + if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) { + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + diff.add(u); + continue; + } + if (i == unifiedDiff.size() - 1) { + diff.add(u); + List twoList = new ArrayList<>(); + twoList.addAll(diff); + diffList.add(twoList); + diff.clear(); + break; + } + diff.add(u); + } + insertOrig(diffList, result, original); + return result; + } + + // Insert the diff format to the original file + private static void insertOrig(List> diffList, List result, List original) { + for (int i = 0; i < diffList.size(); i++) { + List diff = diffList.get(i); + List nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1); + String simb = i == 0 ? diff.get(2) : diff.get(0); + String nexSimb = nexDiff == null ? null : nexDiff.get(0); + insert(result, diff); + Map map = getRowMap(simb); + if (null != nexSimb) { + Map nexMap = getRowMap(nexSimb); + int start = 0; + if (map.get("orgRow") != 0) { + start = map.get("orgRow") + map.get("orgDel") - 1; + } + int end = nexMap.get("revRow") - 2; + insert(result, getOrigList(original, start, end)); + } + int start = map.get("orgRow") + map.get("orgDel") - 1; + start = start == -1 ? 0 : start; + if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) { + insert(result, getOrigList(original, start, original.size() - 1)); + } else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) { + insert(result, getOrigList(original, start, original.size() - 1)); + } + } + } + + // Insert the unchanged content in the source file into result + private static void insert(List result, List noChangeContent) { + for (String ins : noChangeContent) { + result.add(ins); + } + } + + // Parse the line containing @@ to get the modified line number to delete or add a few lines + private static Map getRowMap(String str) { + Map map = new HashMap<>(); + if (str.startsWith("@@")) { + String[] sp = str.split(" "); + String org = sp[1]; + String[] orgSp = org.split(","); + map.put("orgRow", Integer.valueOf(orgSp[0].substring(1))); + map.put("orgDel", Integer.valueOf(orgSp[1])); + String[] revSp = org.split(","); + map.put("revRow", Integer.valueOf(revSp[0].substring(1))); + map.put("revAdd", Integer.valueOf(revSp[1])); + } + return map; + } + + // Get the specified part of the line from the original file + private static List getOrigList(List originalWithPrefix, int start, int end) { + List list = new ArrayList<>(); + if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) { + int startTemp = start; + for (; startTemp <= end; startTemp++) { + list.add(originalWithPrefix.get(startTemp)); + } + } + return list; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java index 9b6f1dfe..7c2fae8c 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/Change.java @@ -23,25 +23,25 @@ */ public class Change { - public final DeltaType deltaType; - public final int startOriginal; - public final int endOriginal; - public final int startRevised; - public final int endRevised; + public final DeltaType deltaType; + public final int startOriginal; + public final int endOriginal; + public final int startRevised; + public final int endRevised; - public Change(DeltaType deltaType, int startOriginal, int endOriginal, int startRevised, int endRevised) { - this.deltaType = deltaType; - this.startOriginal = startOriginal; - this.endOriginal = endOriginal; - this.startRevised = startRevised; - this.endRevised = endRevised; - } - - public Change withEndOriginal(int endOriginal) { - return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); - } - - public Change withEndRevised(int endRevised) { - return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); - } + public Change(DeltaType deltaType, int startOriginal, int endOriginal, int startRevised, int endRevised) { + this.deltaType = deltaType; + this.startOriginal = startOriginal; + this.endOriginal = endOriginal; + this.startRevised = startRevised; + this.endRevised = endRevised; + } + + public Change withEndOriginal(int endOriginal) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } + + public Change withEndRevised(int endRevised) { + return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java index 7e5205cd..5b9d71c9 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmFactory.java @@ -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..ee421c59 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java @@ -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/MyersDiff.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index 2517de46..c990208a 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -31,170 +31,170 @@ */ public final class MyersDiff implements DiffAlgorithmI { - private final BiPredicate equalizer; - - public MyersDiff() { - equalizer = Object::equals; - } - - public MyersDiff(final BiPredicate equalizer) { - Objects.requireNonNull(equalizer, "equalizer must not be null"); - this.equalizer = equalizer; - } - - /** - * {@inheritDoc} - * - * Return empty diff if get the error while procession the difference. - */ - @Override - public List computeDiff(final List source, final List target, DiffAlgorithmListener progress) { - Objects.requireNonNull(source, "source list must not be null"); - Objects.requireNonNull(target, "target list must not be null"); - - if (progress != null) { - progress.diffStart(); - } - PathNode path = buildPath(source, target, progress); - List result = buildRevision(path, source, target); - if (progress != null) { - progress.diffEnd(); - } - return result; - } - - /** - * Computes the minimum diffpath that expresses de differences between the - * original and revised sequences, according to Gene Myers differencing - * algorithm. - * - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A minimum {@link PathNode Path} accross the differences graph. - * @throws DifferentiationFailedException if a diff path could not be found. - */ - private PathNode buildPath(final List orig, final List rev, DiffAlgorithmListener progress) { - Objects.requireNonNull(orig, "original sequence is null"); - Objects.requireNonNull(rev, "revised sequence is null"); - - // these are local constants - final int N = orig.size(); - final int M = rev.size(); - - final int MAX = N + M + 1; - final int size = 1 + 2 * MAX; - final int middle = size / 2; - final PathNode diagonal[] = new PathNode[size]; - - diagonal[middle + 1] = new PathNode(0, -1, true, true, null); - for (int d = 0; d < MAX; d++) { - if (progress != null) { - progress.diffStep(d, MAX); - } - for (int k = -d; k <= d; k += 2) { - final int kmiddle = middle + k; - final int kplus = kmiddle + 1; - final int kminus = kmiddle - 1; - PathNode prev; - int i; - - if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) { - i = diagonal[kplus].i; - prev = diagonal[kplus]; - } else { - i = diagonal[kminus].i + 1; - prev = diagonal[kminus]; - } - - diagonal[kminus] = null; // no longer used - - int j = i - k; - - PathNode node = new PathNode(i, j, false, false, prev); - - while (i < N && j < M && equalizer.test(orig.get(i), rev.get(j))) { - i++; - j++; - } - - if (i != node.i) { - node = new PathNode(i, j, true, false, node); - } - - diagonal[kmiddle] = node; - - if (i >= N && j >= M) { - return diagonal[kmiddle]; - } - } - diagonal[middle + d - 1] = null; - } - // According to Myers, this cannot happen - throw new IllegalStateException("could not find a diff path"); - } - - /** - * Constructs a {@link Patch} from a difference path. - * - * @param actualPath The path. - * @param orig The original sequence. - * @param rev The revised sequence. - * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be - * built from the given path. - */ - private List buildRevision(PathNode actualPath, List orig, List rev) { - Objects.requireNonNull(actualPath, "path is null"); - Objects.requireNonNull(orig, "original sequence is null"); - Objects.requireNonNull(rev, "revised sequence is null"); - - PathNode path = actualPath; - List changes = new ArrayList<>(); - if (path.isSnake()) { - path = path.prev; - } - while (path != null && path.prev != null && path.prev.j >= 0) { - if (path.isSnake()) { - throw new IllegalStateException("bad diffpath: found snake when looking for diff"); - } - int i = path.i; - int j = path.j; - - path = path.prev; - int ianchor = path.i; - int janchor = path.j; - - if (ianchor == i && janchor != j) { - changes.add(new Change(DeltaType.INSERT, ianchor, i, janchor, j)); - } else if (ianchor != i && janchor == j) { - changes.add(new Change(DeltaType.DELETE, ianchor, i, janchor, j)); - } else { - changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j)); - } - - if (path.isSnake()) { - path = path.prev; - } - } - return changes; - } - - /** - * Factory to create instances of this specific diff algorithm. - */ - public static DiffAlgorithmFactory factory() { - return new DiffAlgorithmFactory() { - @Override - public DiffAlgorithmI - create() { - return new MyersDiff<>(); - } - - @Override - public DiffAlgorithmI - create(BiPredicate < T, T > equalizer) { - return new MyersDiff<>(equalizer); - } - }; - } + private final BiPredicate equalizer; + + public MyersDiff() { + equalizer = Object::equals; + } + + public MyersDiff(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + /** + * {@inheritDoc} + * + * Return empty diff if get the error while procession the difference. + */ + @Override + public List computeDiff( + final List source, final List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + PathNode path = buildPath(source, target, progress); + List result = buildRevision(path, source, target); + if (progress != null) { + progress.diffEnd(); + } + return result; + } + + /** + * Computes the minimum diffpath that expresses de differences between the + * original and revised sequences, according to Gene Myers differencing + * algorithm. + * + * @param orig The original sequence. + * @param rev The revised sequence. + * @return A minimum {@link PathNode Path} accross the differences graph. + * @throws IllegalStateException 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 IllegalStateException if a {@link Patch} could not be + * built from the given path. + */ + private List buildRevision(PathNode actualPath, List orig, List rev) { + Objects.requireNonNull(actualPath, "path is null"); + Objects.requireNonNull(orig, "original sequence is null"); + Objects.requireNonNull(rev, "revised sequence is null"); + + PathNode path = actualPath; + List changes = new ArrayList<>(); + if (path.isSnake()) { + path = path.prev; + } + while (path != null && path.prev != null && path.prev.j >= 0) { + if (path.isSnake()) { + throw new IllegalStateException("bad diffpath: found snake when looking for diff"); + } + int i = path.i; + int j = path.j; + + path = path.prev; + int ianchor = path.i; + int janchor = path.j; + + if (ianchor == i && janchor != j) { + changes.add(new Change(DeltaType.INSERT, ianchor, i, janchor, j)); + } else if (ianchor != i && janchor == j) { + changes.add(new Change(DeltaType.DELETE, ianchor, i, janchor, j)); + } else { + changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j)); + } + + if (path.isSnake()) { + path = path.prev; + } + } + return changes; + } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI create() { + return new MyersDiff<>(); + } + + @Override + public DiffAlgorithmI create(BiPredicate equalizer) { + return new MyersDiff<>(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java index ca4114c4..2e1a53c4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java @@ -32,213 +32,215 @@ */ public class MyersDiffWithLinearSpace implements DiffAlgorithmI { - private final BiPredicate equalizer; - - public MyersDiffWithLinearSpace() { - equalizer = Object::equals; - } - - public MyersDiffWithLinearSpace(final BiPredicate equalizer) { - Objects.requireNonNull(equalizer, "equalizer must not be null"); - this.equalizer = equalizer; - } - - @Override - public List computeDiff(List source, List target, DiffAlgorithmListener progress) { - Objects.requireNonNull(source, "source list must not be null"); - Objects.requireNonNull(target, "target list must not be null"); - - if (progress != null) { - progress.diffStart(); - } - - DiffData data = new DiffData(source, target); - - int maxIdx = source.size() + target.size(); - - buildScript(data, 0, source.size(), 0, target.size(), idx -> { - if (progress != null) { - progress.diffStep(idx, maxIdx); - } - }); - - if (progress != null) { - progress.diffEnd(); - } - return data.script; - } - - private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { - if (progress != null) { - progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); - } - final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); - if (middle == null - || middle.start == end1 && middle.diag == end1 - end2 - || middle.end == start1 && middle.diag == start1 - start2) { - int i = start1; - int j = start2; - while (i < end1 || j < end2) { - if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { - //script.append(new KeepCommand<>(left.charAt(i))); - ++i; - ++j; - } else { - //TODO: compress these commands. - if (end1 - start1 > end2 - start2) { - //script.append(new DeleteCommand<>(left.charAt(i))); - if (data.script.isEmpty() - || data.script.get(data.script.size() - 1).endOriginal != i - || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { - data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); - } else { - data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); - } - ++i; - } else { - if (data.script.isEmpty() - || data.script.get(data.script.size() - 1).endRevised != j - || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { - data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); - } else { - data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndRevised(j + 1)); - } - ++j; - } - } - } - } else { - buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); - buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); - } - } - - private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { - final int m = end1 - start1; - final int n = end2 - start2; - if (m == 0 || n == 0) { - return null; - } - - final int delta = m - n; - final int sum = n + m; - final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; - data.vDown[1 + offset] = start1; - data.vUp[1 + offset] = end1 + 1; - - for (int d = 0; d <= offset; ++d) { - // Down - for (int k = -d; k <= d; k += 2) { - // First step - - final int i = k + offset; - if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { - data.vDown[i] = data.vDown[i + 1]; - } else { - data.vDown[i] = data.vDown[i - 1] + 1; - } - - int x = data.vDown[i]; - int y = x - start1 + start2 - k; - - while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { - data.vDown[i] = ++x; - ++y; - } - // Second step - if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { - if (data.vUp[i - delta] <= data.vDown[i]) { - return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); - } - } - } - - // Up - for (int k = delta - d; k <= delta + d; k += 2) { - // First step - final int i = k + offset - delta; - if (k == delta - d - || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { - data.vUp[i] = data.vUp[i + 1] - 1; - } else { - data.vUp[i] = data.vUp[i - 1]; - } - - int x = data.vUp[i] - 1; - int y = x - start1 + start2 - k; - while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { - data.vUp[i] = x--; - y--; - } - // Second step - if (delta % 2 == 0 && -d <= k && k <= d) { - if (data.vUp[i] <= data.vDown[i + delta]) { - return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); - } - } - } - } - - // According to Myers, this cannot happen - throw new IllegalStateException("could not find a diff path"); - } - - private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { - int end = start; - while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { - ++end; - } - return new Snake(start, end, diag); - } - - private class DiffData { - - final int size; - final int[] vDown; - final int[] vUp; - final List script; - final List source; - final List target; - - public DiffData(List source, List target) { - this.source = source; - this.target = target; - size = source.size() + target.size() + 2; - vDown = new int[size]; - vUp = new int[size]; - script = new ArrayList<>(); - } - } - - private class Snake { - - final int start; - final int end; - final int diag; - - public Snake(final int start, final int end, final int diag) { - this.start = start; - this.end = end; - this.diag = diag; - } - } - - /** - * Factory to create instances of this specific diff algorithm. - */ - public static DiffAlgorithmFactory factory() { - return new DiffAlgorithmFactory() { - @Override - public DiffAlgorithmI - create() { - return new MyersDiffWithLinearSpace<>(); - } - - @Override - public DiffAlgorithmI - create(BiPredicate < T, T > equalizer) { - return new MyersDiffWithLinearSpace<>(equalizer); - } - }; - } + private final BiPredicate equalizer; + + public MyersDiffWithLinearSpace() { + equalizer = Object::equals; + } + + public MyersDiffWithLinearSpace(final BiPredicate equalizer) { + Objects.requireNonNull(equalizer, "equalizer must not be null"); + this.equalizer = equalizer; + } + + @Override + public List computeDiff( + List source, List target, DiffAlgorithmListener progress) { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); + + if (progress != null) { + progress.diffStart(); + } + + DiffData data = new DiffData(source, target); + + int maxIdx = source.size() + target.size(); + + buildScript(data, 0, source.size(), 0, target.size(), idx -> { + if (progress != null) { + progress.diffStep(idx, maxIdx); + } + }); + + if (progress != null) { + progress.diffEnd(); + } + return data.script; + } + + private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer progress) { + if (progress != null) { + progress.accept((end1 - start1) / 2 + (end2 - start2) / 2); + } + final Snake middle = getMiddleSnake(data, start1, end1, start2, end2); + if (middle == null + || middle.start == end1 && middle.diag == end1 - end2 + || middle.end == start1 && middle.diag == start1 - start2) { + int i = start1; + int j = start2; + while (i < end1 || j < end2) { + if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) { + // script.append(new KeepCommand<>(left.charAt(i))); + ++i; + ++j; + } else { + // TODO: compress these commands. + if (end1 - start1 > end2 - start2) { + // script.append(new DeleteCommand<>(left.charAt(i))); + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endOriginal != i + || data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) { + data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j)); + } else { + data.script.set( + data.script.size() - 1, + data.script.get(data.script.size() - 1).withEndOriginal(i + 1)); + } + ++i; + } else { + if (data.script.isEmpty() + || data.script.get(data.script.size() - 1).endRevised != j + || data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) { + data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1)); + } else { + data.script.set( + data.script.size() - 1, + data.script.get(data.script.size() - 1).withEndRevised(j + 1)); + } + ++j; + } + } + } + } else { + buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress); + buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress); + } + } + + private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) { + final int m = end1 - start1; + final int n = end2 - start2; + if (m == 0 || n == 0) { + return null; + } + + final int delta = m - n; + final int sum = n + m; + final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2; + data.vDown[1 + offset] = start1; + data.vUp[1 + offset] = end1 + 1; + + for (int d = 0; d <= offset; ++d) { + // Down + for (int k = -d; k <= d; k += 2) { + // First step + + final int i = k + offset; + if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) { + data.vDown[i] = data.vDown[i + 1]; + } else { + data.vDown[i] = data.vDown[i - 1] + 1; + } + + int x = data.vDown[i]; + int y = x - start1 + start2 - k; + + while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vDown[i] = ++x; + ++y; + } + // Second step + if (delta % 2 != 0 && delta - d <= k && k <= delta + d) { + if (data.vUp[i - delta] <= data.vDown[i]) { + return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2); + } + } + } + + // Up + for (int k = delta - d; k <= delta + d; k += 2) { + // First step + final int i = k + offset - delta; + if (k == delta - d || k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) { + data.vUp[i] = data.vUp[i + 1] - 1; + } else { + data.vUp[i] = data.vUp[i - 1]; + } + + int x = data.vUp[i] - 1; + int y = x - start1 + start2 - k; + while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) { + data.vUp[i] = x--; + y--; + } + // Second step + if (delta % 2 == 0 && -d <= k && k <= d) { + if (data.vUp[i] <= data.vDown[i + delta]) { + return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2); + } + } + } + } + + // According to Myers, this cannot happen + throw new IllegalStateException("could not find a diff path"); + } + + private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) { + int end = start; + while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) { + ++end; + } + return new Snake(start, end, diag); + } + + private class DiffData { + + final int size; + final int[] vDown; + final int[] vUp; + final List script; + final List source; + final List target; + + public DiffData(List source, List target) { + this.source = source; + this.target = target; + size = source.size() + target.size() + 2; + vDown = new int[size]; + vUp = new int[size]; + script = new ArrayList<>(); + } + } + + private class Snake { + + final int start; + final int end; + final int diag; + + public Snake(final int start, final int end, final int diag) { + this.start = start; + this.end = end; + this.diag = diag; + } + } + + /** + * Factory to create instances of this specific diff algorithm. + */ + public static DiffAlgorithmFactory factory() { + return new DiffAlgorithmFactory() { + @Override + public DiffAlgorithmI create() { + return new MyersDiffWithLinearSpace<>(); + } + + @Override + public DiffAlgorithmI create(BiPredicate equalizer) { + return new MyersDiffWithLinearSpace<>(equalizer); + } + }; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java index fe8fd03a..5504e817 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java +++ b/java-diff-utils/src/main/java/com/github/difflib/algorithm/myers/PathNode.java @@ -22,89 +22,89 @@ */ public final class PathNode { - /** - * Position in the original sequence. - */ - public final int i; - /** - * Position in the revised sequence. - */ - public final int j; - /** - * The previous node in the path. - */ - public final PathNode prev; + /** + * Position in the original sequence. + */ + public final int i; + /** + * Position in the revised sequence. + */ + public final int j; + /** + * The previous node in the path. + */ + public final PathNode prev; - public final boolean snake; + public final boolean snake; - public final boolean bootstrap; + public final boolean bootstrap; - /** - * Concatenates a new path node with an existing diffpath. - * - * @param i The position in the original sequence for the new node. - * @param j The position in the revised sequence for the new node. - * @param prev The previous node in the path. - */ - public PathNode(int i, int j, boolean snake, boolean bootstrap, PathNode prev) { - this.i = i; - this.j = j; - this.bootstrap = bootstrap; - if (snake) { - this.prev = prev; - } else { - this.prev = prev == null ? null : prev.previousSnake(); - } - this.snake = snake; - } + /** + * Concatenates a new path node with an existing diffpath. + * + * @param i The position in the original sequence for the new node. + * @param j The position in the revised sequence for the new node. + * @param prev The previous node in the path. + */ + public PathNode(int i, int j, boolean snake, boolean bootstrap, PathNode prev) { + this.i = i; + this.j = j; + this.bootstrap = bootstrap; + if (snake) { + this.prev = prev; + } else { + this.prev = prev == null ? null : prev.previousSnake(); + } + this.snake = snake; + } - public boolean isSnake() { - return snake; - } + public boolean isSnake() { + return snake; + } - /** - * Is this a bootstrap node? - *

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

+ * In bottstrap nodes one of the two corrdinates is less than zero. + * + * @return tru if this is a bootstrap node. + */ + public boolean isBootstrap() { + return bootstrap; + } - /** - * Skips sequences of {@link PathNode PathNodes} until a snake or bootstrap node is found, or the end of the - * path is reached. - * - * @return The next first {@link PathNode} or bootstrap node in the path, or null if none found. - */ - public final PathNode previousSnake() { - if (isBootstrap()) { - return null; - } - if (!isSnake() && prev != null) { - return prev.previousSnake(); - } - return this; - } + /** + * Skips sequences of {@link PathNode PathNodes} until a snake or bootstrap node is found, or the end of the + * path is reached. + * + * @return The next first {@link PathNode} or bootstrap node in the path, or null if none found. + */ + public final PathNode previousSnake() { + if (isBootstrap()) { + return null; + } + if (!isSnake() && prev != null) { + return prev.previousSnake(); + } + return this; + } - /** - * {@inheritDoc} - */ - @Override - public String toString() { - StringBuilder buf = new StringBuilder("["); - PathNode node = this; - while (node != null) { - buf.append("("); - buf.append(node.i); - buf.append(","); - buf.append(node.j); - buf.append(")"); - node = node.prev; - } - buf.append("]"); - return buf.toString(); - } + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder("["); + PathNode node = this; + while (node != null) { + buf.append("("); + buf.append(node.i); + buf.append(","); + buf.append(node.j); + buf.append(")"); + node = node.prev; + } + buf.append("]"); + return buf.toString(); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java index f74f62ca..acf231e1 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/AbstractDelta.java @@ -20,97 +20,98 @@ import java.util.Objects; /** - * Abstract delta between a source and a target. + * Abstract delta between a source and a target. * @author Tobias Warneke (t.warneke@gmx.net) */ public abstract class AbstractDelta implements Serializable { - private final Chunk source; - private final Chunk target; - private final DeltaType type; - - public AbstractDelta(DeltaType type, Chunk source, Chunk target) { - Objects.requireNonNull(source); - Objects.requireNonNull(target); - Objects.requireNonNull(type); - this.type = type; - this.source = source; - this.target = target; - } + private final Chunk source; + private final Chunk target; + private final DeltaType type; - public Chunk getSource() { - return source; - } + public AbstractDelta(DeltaType type, Chunk source, Chunk target) { + Objects.requireNonNull(source); + Objects.requireNonNull(target); + Objects.requireNonNull(type); + this.type = type; + this.source = source; + this.target = target; + } - public Chunk getTarget() { - return target; - } + public Chunk getSource() { + return source; + } - public DeltaType getType() { - return type; - } - - /** - * Verify the chunk of this delta, to fit the target. - * @param target - * @throws PatchFailedException - */ - protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException { - return getSource().verifyChunk(target); - } - - protected VerifyChunk verifyAndApplyTo(List target) throws PatchFailedException { - final VerifyChunk verify = verifyChunkToFitTarget(target); - if (verify == VerifyChunk.OK) { - applyTo(target); - } - return verify; - } - - protected abstract void applyTo(List target) throws PatchFailedException; - - protected abstract void restore(List target); - - /** - * Apply patch fuzzy. - * - * @param target the list this patch will be applied to - * @param fuzz the number of elements to ignore before/after the patched elements - * @param position the position this patch will be applied to. ignores {@code source.getPosition()} - * @see Description of Fuzzy Patch for more information. - */ - @SuppressWarnings("RedundantThrows") - protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { - throw new UnsupportedOperationException(this.getClass().getSimpleName() + " does not supports applying patch fuzzy"); - } + public Chunk getTarget() { + return target; + } - /** - * Create a new delta of the actual instance with customized chunk data. - */ - public abstract AbstractDelta withChunks(Chunk original, Chunk revised); + public DeltaType getType() { + return type; + } - @Override - public int hashCode() { - return Objects.hash(this.source, this.target, this.type); - } + /** + * Verify the chunk of this delta, to fit the target. + * @param target + * @throws PatchFailedException + */ + protected VerifyChunk verifyChunkToFitTarget(List target) throws PatchFailedException { + return getSource().verifyChunk(target); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AbstractDelta other = (AbstractDelta) obj; - if (!Objects.equals(this.source, other.source)) { - return false; - } - if (!Objects.equals(this.target, other.target)) { - return false; - } - return this.type == other.type; - } + protected VerifyChunk verifyAndApplyTo(List target) throws PatchFailedException { + final VerifyChunk verify = verifyChunkToFitTarget(target); + if (verify == VerifyChunk.OK) { + applyTo(target); + } + return verify; + } + + protected abstract void applyTo(List target) throws PatchFailedException; + + protected abstract void restore(List target); + + /** + * Apply patch fuzzy. + * + * @param target the list this patch will be applied to + * @param fuzz the number of elements to ignore before/after the patched elements + * @param position the position this patch will be applied to. ignores {@code source.getPosition()} + * @see Description of Fuzzy Patch for more information. + */ + @SuppressWarnings("RedundantThrows") + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + throw new UnsupportedOperationException( + this.getClass().getSimpleName() + " does not supports applying patch fuzzy"); + } + + /** + * Create a new delta of the actual instance with customized chunk data. + */ + public abstract AbstractDelta withChunks(Chunk original, Chunk revised); + + @Override + public int hashCode() { + return Objects.hash(this.source, this.target, this.type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractDelta other = (AbstractDelta) obj; + if (!Objects.equals(this.source, other.source)) { + return false; + } + if (!Objects.equals(this.target, other.target)) { + return false; + } + return this.type == other.type; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java index 376fd625..f82f13ac 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/ChangeDelta.java @@ -26,67 +26,67 @@ */ public final class ChangeDelta extends AbstractDelta { - /** - * Creates a change delta with the two given chunks. - * - * @param source The source chunk. Must not be {@code null}. - * @param target The target chunk. Must not be {@code null}. - */ - public ChangeDelta(Chunk source, Chunk target) { - super(DeltaType.CHANGE, source, target); - Objects.requireNonNull(source, "source must not be null"); - Objects.requireNonNull(target, "target must not be null"); - } + /** + * Creates a change delta with the two given chunks. + * + * @param source The source chunk. Must not be {@code null}. + * @param target The target chunk. Must not be {@code null}. + */ + public ChangeDelta(Chunk source, Chunk target) { + super(DeltaType.CHANGE, source, target); + Objects.requireNonNull(source, "source must not be null"); + Objects.requireNonNull(target, "target must not be null"); + } - @Override - protected void applyTo(List target) throws PatchFailedException { - int position = getSource().getPosition(); - int size = getSource().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - int i = 0; - for (T line : getTarget().getLines()) { - target.add(position + i, line); - i++; - } - } + @Override + protected void applyTo(List target) throws PatchFailedException { + int position = getSource().getPosition(); + int size = getSource().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + int i = 0; + for (T line : getTarget().getLines()) { + target.add(position + i, line); + i++; + } + } - @Override - protected void restore(List target) { - int position = getTarget().getPosition(); - int size = getTarget().size(); - for (int i = 0; i < size; i++) { - target.remove(position); - } - int i = 0; - for (T line : getSource().getLines()) { - target.add(position + i, line); - i++; - } - } + @Override + protected void restore(List target) { + int position = getTarget().getPosition(); + int size = getTarget().size(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + int i = 0; + for (T line : getSource().getLines()) { + target.add(position + i, line); + i++; + } + } - protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { - int size = getSource().size(); - for (int i = fuzz; i < size - fuzz; i++) { - target.remove(position + fuzz); - } + protected void applyFuzzyToAt(List target, int fuzz, int position) throws PatchFailedException { + int size = getSource().size(); + for (int i = fuzz; i < size - fuzz; i++) { + target.remove(position + fuzz); + } - int i = fuzz; - for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) { - target.add(position + i, line); - i++; - } - } + int i = fuzz; + for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) { + target.add(position + i, line); + i++; + } + } - @Override - public String toString() { - return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " - + getSource().getLines() + " to " + getTarget().getLines() + "]"; - } + @Override + public String toString() { + return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + " to " + getTarget().getLines() + "]"; + } - @Override - public AbstractDelta withChunks(Chunk original, Chunk revised) { - return new ChangeDelta(original, revised); - } + @Override + public AbstractDelta withChunks(Chunk original, Chunk revised) { + return new ChangeDelta(original, revised); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java index 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..98cb561f 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/EqualDelta.java @@ -18,39 +18,37 @@ import java.util.List; /** - * This delta contains equal lines of data. Therefore nothing is to do in applyTo and restore. + * This delta contains equal lines of data. Therefore, nothing is to do in applyTo and restore. * @author tobens */ public class EqualDelta extends AbstractDelta { - 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 aaff7d94..31c2c3e4 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java @@ -8,7 +8,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -20,8 +20,8 @@ package com.github.difflib.patch; import static java.util.Comparator.comparing; -import com.github.difflib.algorithm.Change; +import com.github.difflib.algorithm.Change; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -37,308 +37,313 @@ */ public final class Patch implements Serializable { - private final List> deltas; - - public Patch() { - this(10); - } - - public Patch(int estimatedPatchSize) { - deltas = new ArrayList<>(estimatedPatchSize); - } - - /** - * Creates a new list, the patch is being applied to. - * - * @param target The list to apply the changes to. - * @return A new list containing the applied patch. - * @throws PatchFailedException if the patch cannot be applied - */ - public List applyTo(List target) throws PatchFailedException { - List result = new ArrayList<>(target); - applyToExisting(result); - return result; - } - - /** - * Applies the patch to the supplied list. - * - * @param target The list to apply the changes to. This list has to be modifiable, - * otherwise exceptions may be thrown, depending on the used type of list. - * @throws PatchFailedException if the patch cannot be applied - * @throws RuntimeException (or similar) if the list is not modifiable. - */ - public void applyToExisting(List target) throws PatchFailedException { - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - VerifyChunk valid = delta.verifyAndApplyTo(target); - if (valid != VerifyChunk.OK) { - conflictOutput.processConflict(valid, delta, target); - } - } - } - - private static class PatchApplyingContext { - public final List result; - public final int maxFuzz; - - // the position last patch applied to. - public int lastPatchEnd = -1; - - ///// passing values from find to apply - public int currentFuzz = 0; - - public int defaultPosition; - public boolean beforeOutRange = false; - public boolean afterOutRange = false; - - private PatchApplyingContext(List result, int maxFuzz) { - this.result = result; - this.maxFuzz = maxFuzz; - } - } - - public List applyFuzzy(List target, int maxFuzz) throws PatchFailedException { - PatchApplyingContext ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz); - - // the difference between patch's position and actually applied position - int lastPatchDelta = 0; - - for (AbstractDelta delta : getDeltas()) { - ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta; - int patchPosition = findPositionFuzzy(ctx, delta); - if (0 <= patchPosition) { - delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition); - lastPatchDelta = patchPosition - delta.getSource().getPosition(); - ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta; - } else { - conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result); - } - } - - return ctx.result; - } - - // negative for not found - private int findPositionFuzzy(PatchApplyingContext ctx, AbstractDelta delta) throws PatchFailedException { - for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) { - ctx.currentFuzz = fuzz; - int foundPosition = findPositionWithFuzz(ctx, delta, fuzz); - if (foundPosition >= 0) { - return foundPosition; - } - } - return -1; - } - - // negative for not found - private int findPositionWithFuzz(PatchApplyingContext ctx, AbstractDelta delta, int fuzz) throws PatchFailedException { - if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) { - return ctx.defaultPosition; - } - - ctx.beforeOutRange = false; - ctx.afterOutRange = false; - - // moreDelta >= 0: just for overflow guard, not a normal condition - //noinspection OverflowingLoopIndex - for (int moreDelta = 0; moreDelta >= 0; moreDelta++) { - int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta); - if (pos >= 0) { - return pos; - } - if (ctx.beforeOutRange && ctx.afterOutRange) { - break; - } - } - - return -1; - } - - // negative for not found - private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext ctx, AbstractDelta delta, int fuzz, int moreDelta) throws PatchFailedException { - // range check: can't apply before end of last patch - if (!ctx.beforeOutRange) { - int beginAt = ctx.defaultPosition - moreDelta + fuzz; - // We can't apply patch before end of last patch. - if (beginAt <= ctx.lastPatchEnd) { - ctx.beforeOutRange = true; - } - } - // range check: can't apply after end of result - if (!ctx.afterOutRange) { - int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz; - // We can't apply patch before end of last patch. - if (ctx.result.size() < beginAt) { - ctx.afterOutRange = true; - } - } - - if (!ctx.beforeOutRange) { - VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta); - if (before == VerifyChunk.OK) { - return ctx.defaultPosition - moreDelta; - } - } - if (!ctx.afterOutRange) { - VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta); - if (after == VerifyChunk.OK) { - return ctx.defaultPosition + moreDelta; - } - } - return -1; - } - - /** - * Standard Patch behaviour to throw an exception for pathching conflicts. - */ - public final ConflictOutput CONFLICT_PRODUCES_EXCEPTION = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { - throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString()); - }; - - /** - * Git like merge conflict output. - */ - public static final ConflictOutput CONFLICT_PRODUCES_MERGE_CONFLICT = (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { - if (result.size() > delta.getSource().getPosition()) { - List orgData = new ArrayList<>(); - - for (int i = 0; i < delta.getSource().size(); i++) { - orgData.add(result.get(delta.getSource().getPosition())); - result.remove(delta.getSource().getPosition()); - } - - orgData.add(0, "<<<<<< HEAD"); - orgData.add("======"); - orgData.addAll(delta.getSource().getLines()); - orgData.add(">>>>>>> PATCH"); - - result.addAll(delta.getSource().getPosition(), orgData); - - } else { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - }; - - private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; - - /** - * Alter normal conflict output behaviour to e.g. inclide some conflict - * statements in the result, like git does it. - */ - public Patch withConflictOutput(ConflictOutput conflictOutput) { - this.conflictOutput = conflictOutput; - return this; - } - - /** - * Creates a new list, containing the restored state of the given list. - * Opposite to {@link #applyTo(List)} method. - * - * @param target The list to copy and apply changes to. - * @return A new list, containing the restored state. - */ - public List restore(List target) { - List result = new ArrayList<>(target); - restoreToExisting(result); - return result; - } - - - /** - * Restores all changes within the given list. - * Opposite to {@link #applyToExisting(List)} method. - * - * @param target The list to restore changes in. This list has to be modifiable, - * otherwise exceptions may be thrown, depending on the used type of list. - * @throws RuntimeException (or similar) if the list is not modifiable. - */ - public void restoreToExisting(List target) { - ListIterator> it = getDeltas().listIterator(deltas.size()); - while (it.hasPrevious()) { - AbstractDelta delta = it.previous(); - delta.restore(target); - } - } - - /** - * Add the given delta to this patch - * - * @param delta the given delta - */ - public void addDelta(AbstractDelta delta) { - deltas.add(delta); - } - - /** - * Get the list of computed deltas - * - * @return the deltas - */ - public List> getDeltas() { - deltas.sort(comparing(d -> d.getSource().getPosition())); - return deltas; - } - - @Override - public String toString() { - return "Patch{" + "deltas=" + deltas + '}'; - } - - public static Patch generate(List original, List revised, List changes) { - return generate(original, revised, changes, false); - } - - private static Chunk buildChunk(int start, int end, List data) { - return new Chunk<>(start, new ArrayList<>(data.subList(start, end))); - } - - public static Patch generate(List original, List revised, List _changes, boolean includeEquals) { - Patch patch = new Patch<>(_changes.size()); - int startOriginal = 0; - int startRevised = 0; - - List changes = _changes; - - if (includeEquals) { - changes = new ArrayList(_changes); - Collections.sort(changes, comparing(d -> d.startOriginal)); - } - - for (Change change : changes) { - - if (includeEquals && startOriginal < change.startOriginal) { - patch.addDelta(new EqualDelta( - buildChunk(startOriginal, change.startOriginal, original), - buildChunk(startRevised, change.startRevised, revised))); - } - - Chunk orgChunk = buildChunk(change.startOriginal, change.endOriginal, original); - Chunk revChunk = buildChunk(change.startRevised, change.endRevised, revised); - switch (change.deltaType) { - case DELETE: - patch.addDelta(new DeleteDelta<>(orgChunk, revChunk)); - break; - case INSERT: - patch.addDelta(new InsertDelta<>(orgChunk, revChunk)); - break; - case CHANGE: - patch.addDelta(new ChangeDelta<>(orgChunk, revChunk)); - break; - default: - } - - startOriginal = change.endOriginal; - startRevised = change.endRevised; - } - - if (includeEquals && startOriginal < original.size()) { - patch.addDelta(new EqualDelta( - buildChunk(startOriginal, original.size(), original), - buildChunk(startRevised, revised.size(), revised))); - } - - return patch; - } + private final List> deltas; + + public Patch() { + this(10); + } + + public Patch(int estimatedPatchSize) { + deltas = new ArrayList<>(estimatedPatchSize); + } + + /** + * Creates a new list, the patch is being applied to. + * + * @param target The list to apply the changes to. + * @return A new list containing the applied patch. + * @throws PatchFailedException if the patch cannot be applied + */ + public List applyTo(List target) throws PatchFailedException { + List result = new ArrayList<>(target); + applyToExisting(result); + return result; + } + + /** + * Applies the patch to the supplied list. + * + * @param target The list to apply the changes to. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws PatchFailedException if the patch cannot be applied + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void applyToExisting(List target) throws PatchFailedException { + ListIterator> it = getDeltas().listIterator(deltas.size()); + while (it.hasPrevious()) { + AbstractDelta delta = it.previous(); + VerifyChunk valid = delta.verifyAndApplyTo(target); + if (valid != VerifyChunk.OK) { + conflictOutput.processConflict(valid, delta, target); + } + } + } + + private static class PatchApplyingContext { + public final List result; + public final int maxFuzz; + + // the position last patch applied to. + public int lastPatchEnd = -1; + + ///// passing values from find to apply + public int currentFuzz = 0; + + public int defaultPosition; + public boolean beforeOutRange = false; + public boolean afterOutRange = false; + + private PatchApplyingContext(List result, int maxFuzz) { + this.result = result; + this.maxFuzz = maxFuzz; + } + } + + public List applyFuzzy(List target, int maxFuzz) throws PatchFailedException { + PatchApplyingContext ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz); + + // the difference between patch's position and actually applied position + int lastPatchDelta = 0; + + for (AbstractDelta delta : getDeltas()) { + ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta; + int patchPosition = findPositionFuzzy(ctx, delta); + if (0 <= patchPosition) { + delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition); + lastPatchDelta = patchPosition - delta.getSource().getPosition(); + ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta; + } else { + conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result); + } + } + + return ctx.result; + } + + // negative for not found + private int findPositionFuzzy(PatchApplyingContext ctx, AbstractDelta delta) throws PatchFailedException { + for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) { + ctx.currentFuzz = fuzz; + int foundPosition = findPositionWithFuzz(ctx, delta, fuzz); + if (foundPosition >= 0) { + return foundPosition; + } + } + return -1; + } + + // negative for not found + private int findPositionWithFuzz(PatchApplyingContext ctx, AbstractDelta delta, int fuzz) + throws PatchFailedException { + if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) { + return ctx.defaultPosition; + } + + ctx.beforeOutRange = false; + ctx.afterOutRange = false; + + // moreDelta >= 0: just for overflow guard, not a normal condition + //noinspection OverflowingLoopIndex + for (int moreDelta = 0; moreDelta >= 0; moreDelta++) { + int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta); + if (pos >= 0) { + return pos; + } + if (ctx.beforeOutRange && ctx.afterOutRange) { + break; + } + } + + return -1; + } + + // negative for not found + private int findPositionWithFuzzAndMoreDelta( + PatchApplyingContext ctx, AbstractDelta delta, int fuzz, int moreDelta) throws PatchFailedException { + // range check: can't apply before end of last patch + if (!ctx.beforeOutRange) { + int beginAt = ctx.defaultPosition - moreDelta + fuzz; + // We can't apply patch before end of last patch. + if (beginAt <= ctx.lastPatchEnd) { + ctx.beforeOutRange = true; + } + } + // range check: can't apply after end of result + if (!ctx.afterOutRange) { + int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz; + // We can't apply patch before end of last patch. + if (ctx.result.size() < beginAt) { + ctx.afterOutRange = true; + } + } + + if (!ctx.beforeOutRange) { + VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta); + if (before == VerifyChunk.OK) { + return ctx.defaultPosition - moreDelta; + } + } + if (!ctx.afterOutRange) { + VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta); + if (after == VerifyChunk.OK) { + return ctx.defaultPosition + moreDelta; + } + } + return -1; + } + + /** + * Standard Patch behaviour to throw an exception for pathching conflicts. + */ + public final ConflictOutput CONFLICT_PRODUCES_EXCEPTION = + (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { + throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString()); + }; + + /** + * Git like merge conflict output. + */ + public static final ConflictOutput CONFLICT_PRODUCES_MERGE_CONFLICT = + (VerifyChunk verifyChunk, AbstractDelta delta, List result) -> { + if (result.size() > delta.getSource().getPosition()) { + List orgData = new ArrayList<>(); + + for (int i = 0; i < delta.getSource().size(); i++) { + orgData.add(result.get(delta.getSource().getPosition())); + result.remove(delta.getSource().getPosition()); + } + + orgData.add(0, "<<<<<< HEAD"); + orgData.add("======"); + orgData.addAll(delta.getSource().getLines()); + orgData.add(">>>>>>> PATCH"); + + result.addAll(delta.getSource().getPosition(), orgData); + + } else { + throw new UnsupportedOperationException( + "Not supported yet."); // To change body of generated methods, choose Tools | Templates. + } + }; + + private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION; + + /** + * Alter normal conflict output behaviour to e.g. include some conflict + * statements in the result, like git does it. + */ + public Patch withConflictOutput(ConflictOutput conflictOutput) { + this.conflictOutput = conflictOutput; + return this; + } + + /** + * Creates a new list, containing the restored state of the given list. + * Opposite to {@link #applyTo(List)} method. + * + * @param target The list to copy and apply changes to. + * @return A new list, containing the restored state. + */ + public List restore(List target) { + List result = new ArrayList<>(target); + restoreToExisting(result); + return result; + } + + /** + * Restores all changes within the given list. + * Opposite to {@link #applyToExisting(List)} method. + * + * @param target The list to restore changes in. This list has to be modifiable, + * otherwise exceptions may be thrown, depending on the used type of list. + * @throws RuntimeException (or similar) if the list is not modifiable. + */ + public void restoreToExisting(List target) { + ListIterator> it = getDeltas().listIterator(deltas.size()); + while (it.hasPrevious()) { + AbstractDelta delta = it.previous(); + delta.restore(target); + } + } + + /** + * Add the given delta to this patch + * + * @param delta the given delta + */ + public void addDelta(AbstractDelta delta) { + deltas.add(delta); + } + + /** + * Get the list of computed deltas + * + * @return the deltas + */ + public List> getDeltas() { + deltas.sort(comparing(d -> d.getSource().getPosition())); + return deltas; + } + + @Override + public String toString() { + return "Patch{" + "deltas=" + deltas + '}'; + } + + public static Patch generate(List original, List revised, List changes) { + return generate(original, revised, changes, false); + } + + private static Chunk buildChunk(int start, int end, List data) { + return new Chunk<>(start, new ArrayList<>(data.subList(start, end))); + } + + public static Patch generate( + List original, List revised, List _changes, boolean includeEquals) { + Patch patch = new Patch<>(_changes.size()); + int startOriginal = 0; + int startRevised = 0; + + List changes = _changes; + + if (includeEquals) { + changes = new ArrayList(_changes); + Collections.sort(changes, comparing(d -> d.startOriginal)); + } + + for (Change change : changes) { + + if (includeEquals && startOriginal < change.startOriginal) { + patch.addDelta(new EqualDelta( + buildChunk(startOriginal, change.startOriginal, original), + buildChunk(startRevised, change.startRevised, revised))); + } + + Chunk orgChunk = buildChunk(change.startOriginal, change.endOriginal, original); + Chunk revChunk = buildChunk(change.startRevised, change.endRevised, revised); + switch (change.deltaType) { + case DELETE: + patch.addDelta(new DeleteDelta<>(orgChunk, revChunk)); + break; + case INSERT: + patch.addDelta(new InsertDelta<>(orgChunk, revChunk)); + break; + case CHANGE: + patch.addDelta(new ChangeDelta<>(orgChunk, revChunk)); + break; + default: + } + + startOriginal = change.endOriginal; + startRevised = change.endRevised; + } + + if (includeEquals && startOriginal < original.size()) { + patch.addDelta(new EqualDelta( + buildChunk(startOriginal, original.size(), original), + buildChunk(startRevised, revised.size(), revised))); + } + + return patch; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java b/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java index 7521c892..c6673573 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/PatchFailedException.java @@ -22,12 +22,11 @@ */ public class PatchFailedException extends DiffException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public PatchFailedException() { - } + public PatchFailedException() {} - public PatchFailedException(String msg) { - super(msg); - } + public PatchFailedException(String msg) { + super(msg); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java index 076f633a..c8839b91 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java +++ b/java-diff-utils/src/main/java/com/github/difflib/patch/VerifyChunk.java @@ -20,7 +20,7 @@ * @author tw */ public enum VerifyChunk { - OK, - POSITION_OUT_OF_TARGET, - CONTENT_DOES_NOT_MATCH_TARGET + OK, + POSITION_OUT_OF_TARGET, + CONTENT_DOES_NOT_MATCH_TARGET } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java index 95908393..6ba79beb 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRow.java @@ -25,91 +25,94 @@ */ public final class DiffRow implements Serializable { - private Tag tag; - private final String oldLine; - private final String newLine; + private Tag tag; + private final String oldLine; + private final String newLine; - public DiffRow(Tag tag, String oldLine, String newLine) { - this.tag = tag; - this.oldLine = oldLine; - this.newLine = newLine; - } + public DiffRow(Tag tag, String oldLine, String newLine) { + this.tag = tag; + this.oldLine = oldLine; + this.newLine = newLine; + } - public enum Tag { - INSERT, DELETE, CHANGE, EQUAL - } + public enum Tag { + INSERT, + DELETE, + CHANGE, + EQUAL + } - /** - * @return the tag - */ - public Tag getTag() { - return tag; - } + /** + * @return the tag + */ + public Tag getTag() { + return tag; + } - /** - * @param tag the tag to set - */ - public void setTag(Tag tag) { - this.tag = tag; - } + /** + * @param tag the tag to set + */ + public void setTag(Tag tag) { + this.tag = tag; + } - /** - * @return the oldLine - */ - public String getOldLine() { - return oldLine; - } + /** + * @return the oldLine + */ + public String getOldLine() { + return oldLine; + } - /** - * @return the newLine - */ - public String getNewLine() { - return newLine; - } + /** + * @return the newLine + */ + public String getNewLine() { + return newLine; + } - @Override - public int hashCode() { - return Objects.hash(newLine, oldLine, tag); - } + @Override + public int hashCode() { + return Objects.hash(newLine, oldLine, tag); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - DiffRow other = (DiffRow) obj; - if (newLine == null) { - if (other.newLine != null) { - return false; - } - } else if (!newLine.equals(other.newLine)) { - return false; - } - if (oldLine == null) { - if (other.oldLine != null) { - return false; - } - } else if (!oldLine.equals(other.oldLine)) { - return false; - } - if (tag == null) { - if (other.tag != null) { - return false; - } - } else if (!tag.equals(other.tag)) { - return false; - } - return true; - } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DiffRow other = (DiffRow) obj; + if (newLine == null) { + if (other.newLine != null) { + return false; + } + } else if (!newLine.equals(other.newLine)) { + return false; + } + if (oldLine == null) { + if (other.oldLine != null) { + return false; + } + } else if (!oldLine.equals(other.oldLine)) { + return false; + } + if (tag == null) { + if (other.tag != null) { + return false; + } + } else if (!tag.equals(other.tag)) { + return false; + } + return true; + } - @Override - public String toString() { - return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]"; - } + @Override + public String toString() { + return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]"; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 3711bfb6..82a5b94e 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -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,629 +52,727 @@ */ 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; - } - - /** - * 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; - } - } + 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; + // processor for equal (unchanged) lines + private final Function equalityProcessor; + + 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; + equalityProcessor = builder.equalityProcessor; + + 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())) { + String processed = processEqualities(line); + diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed)); + } + 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())) { + String processed = processEqualities(line); + diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed)); + } + + 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); + } + } + + /** + * Hook for processing equal (unchanged) text segments. + * Delegates to the builder-configured equalityProcessor if present. + * + * @author tusharsoni52 + * @param text + * @return + * + */ + protected String processEqualities(final String text) { + return equalityProcessor != null ? equalityProcessor.apply(text) : text; + } + + /** + * This class used for building the DiffRowGenerator. + * + * @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; + // Processor for equalities + private Function equalityProcessor = null; + + 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; + } + + /** + * Processor for equal (unchanged) text parts. + * Allows applying the same escaping/transformation as for diffs. + * + * @author tusharsoni52 + * @param equalityProcessor + * @return + * + */ + public Builder processEqualities(Function equalityProcessor) { + this.equalityProcessor = equalityProcessor; + return this; + } + + /** + * Set the column width of generated lines of original and revised + * texts. + * + * @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 1ae3b7ca..4fc54186 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffFile.java @@ -18,194 +18,194 @@ import com.github.difflib.patch.Patch; /** - * Data structure for one patched file from a unified diff file. - * + * Data structure for one patched file from a unified diff file. + * * @author Tobias Warneke (t.warneke@gmx.net) */ public final class UnifiedDiffFile { - private String diffCommand; - private String fromFile; - private String fromTimestamp; - private String toFile; - private String renameFrom; - private String renameTo; - private String copyFrom; - private String copyTo; - private String toTimestamp; - private String index; - private String newFileMode; - private String oldMode; - private String newMode; - private String deletedFileMode; - private String binaryAdded; - private String binaryDeleted; - private String binaryEdited; - private Patch patch = new Patch<>(); - private boolean noNewLineAtTheEndOfTheFile = false; - private Integer similarityIndex; - - public String getDiffCommand() { - return diffCommand; - } - - public void setDiffCommand(String diffCommand) { - this.diffCommand = diffCommand; - } - - public String getFromFile() { - return fromFile; - } - - public void setFromFile(String fromFile) { - this.fromFile = fromFile; - } - - public String getToFile() { - return toFile; - } - - public void setToFile(String toFile) { - this.toFile = toFile; - } - - public void setIndex(String index) { - this.index = index; - } - - public String getIndex() { - return index; - } - - public Patch getPatch() { - return patch; - } - - public String getFromTimestamp() { - return fromTimestamp; - } - - public void setFromTimestamp(String fromTimestamp) { - this.fromTimestamp = fromTimestamp; - } - - public String getToTimestamp() { - return toTimestamp; - } - - public void setToTimestamp(String toTimestamp) { - this.toTimestamp = toTimestamp; - } - - public Integer getSimilarityIndex() { - return similarityIndex; - } - - public void setSimilarityIndex(Integer similarityIndex) { - this.similarityIndex = similarityIndex; - } - - public String getRenameFrom() { - return renameFrom; - } - - public void setRenameFrom(String renameFrom) { - this.renameFrom = renameFrom; - } - - public String getRenameTo() { - return renameTo; - } - - public void setRenameTo(String renameTo) { - this.renameTo = renameTo; - } - - public String getCopyFrom() { - return copyFrom; - } - - public void setCopyFrom(String copyFrom) { - this.copyFrom = copyFrom; - } - - public String getCopyTo() { - return copyTo; - } - - public void setCopyTo(String copyTo) { - this.copyTo = copyTo; - } - - public static UnifiedDiffFile from(String fromFile, String toFile, Patch patch) { - UnifiedDiffFile file = new UnifiedDiffFile(); - file.setFromFile(fromFile); - file.setToFile(toFile); - file.patch = patch; - return file; - } - - public void setNewFileMode(String newFileMode) { - this.newFileMode = newFileMode; - } - - public String getNewFileMode() { - return newFileMode; - } - - public String getDeletedFileMode() { - return deletedFileMode; - } - - public void setDeletedFileMode(String deletedFileMode) { - this.deletedFileMode = deletedFileMode; - } - - public String getOldMode() { - return oldMode; - } - - public void setOldMode(String oldMode) { - this.oldMode = oldMode; - } - - public String getNewMode() { - return newMode; - } - - public void setNewMode(String newMode) { - this.newMode = newMode; - } - - public String getBinaryAdded() { - return binaryAdded; - } - - public void setBinaryAdded(String binaryAdded) { - this.binaryAdded = binaryAdded; - } - - public String getBinaryDeleted() { - return binaryDeleted; - } - - public void setBinaryDeleted(String binaryDeleted) { - this.binaryDeleted = binaryDeleted; - } - - public String getBinaryEdited() { - return binaryEdited; - } - - public void setBinaryEdited(String binaryEdited) { - this.binaryEdited = binaryEdited; - } - - public boolean isNoNewLineAtTheEndOfTheFile() { - return noNewLineAtTheEndOfTheFile; - } - - public void setNoNewLineAtTheEndOfTheFile(boolean noNewLineAtTheEndOfTheFile) { - this.noNewLineAtTheEndOfTheFile = noNewLineAtTheEndOfTheFile; - } + private String diffCommand; + private String fromFile; + private String fromTimestamp; + private String toFile; + private String renameFrom; + private String renameTo; + private String copyFrom; + private String copyTo; + private String toTimestamp; + private String index; + private String newFileMode; + private String oldMode; + private String newMode; + private String deletedFileMode; + private String binaryAdded; + private String binaryDeleted; + private String binaryEdited; + private Patch patch = new Patch<>(); + private boolean noNewLineAtTheEndOfTheFile = false; + private Integer similarityIndex; + + public String getDiffCommand() { + return diffCommand; + } + + public void setDiffCommand(String diffCommand) { + this.diffCommand = diffCommand; + } + + public String getFromFile() { + return fromFile; + } + + public void setFromFile(String fromFile) { + this.fromFile = fromFile; + } + + public String getToFile() { + return toFile; + } + + public void setToFile(String toFile) { + this.toFile = toFile; + } + + public void setIndex(String index) { + this.index = index; + } + + public String getIndex() { + return index; + } + + public Patch getPatch() { + return patch; + } + + public String getFromTimestamp() { + return fromTimestamp; + } + + public void setFromTimestamp(String fromTimestamp) { + this.fromTimestamp = fromTimestamp; + } + + public String getToTimestamp() { + return toTimestamp; + } + + public void setToTimestamp(String toTimestamp) { + this.toTimestamp = toTimestamp; + } + + public Integer getSimilarityIndex() { + return similarityIndex; + } + + public void setSimilarityIndex(Integer similarityIndex) { + this.similarityIndex = similarityIndex; + } + + public String getRenameFrom() { + return renameFrom; + } + + public void setRenameFrom(String renameFrom) { + this.renameFrom = renameFrom; + } + + public String getRenameTo() { + return renameTo; + } + + public void setRenameTo(String renameTo) { + this.renameTo = renameTo; + } + + public String getCopyFrom() { + return copyFrom; + } + + public void setCopyFrom(String copyFrom) { + this.copyFrom = copyFrom; + } + + public String getCopyTo() { + return copyTo; + } + + public void setCopyTo(String copyTo) { + this.copyTo = copyTo; + } + + public static UnifiedDiffFile from(String fromFile, String toFile, Patch patch) { + UnifiedDiffFile file = new UnifiedDiffFile(); + file.setFromFile(fromFile); + file.setToFile(toFile); + file.patch = patch; + return file; + } + + public void setNewFileMode(String newFileMode) { + this.newFileMode = newFileMode; + } + + public String getNewFileMode() { + return newFileMode; + } + + public String getDeletedFileMode() { + return deletedFileMode; + } + + public void setDeletedFileMode(String deletedFileMode) { + this.deletedFileMode = deletedFileMode; + } + + public String getOldMode() { + return oldMode; + } + + public void setOldMode(String oldMode) { + this.oldMode = oldMode; + } + + public String getNewMode() { + return newMode; + } + + public void setNewMode(String newMode) { + this.newMode = newMode; + } + + public String getBinaryAdded() { + return binaryAdded; + } + + public void setBinaryAdded(String binaryAdded) { + this.binaryAdded = binaryAdded; + } + + public String getBinaryDeleted() { + return binaryDeleted; + } + + public void setBinaryDeleted(String binaryDeleted) { + this.binaryDeleted = binaryDeleted; + } + + public String getBinaryEdited() { + return binaryEdited; + } + + public void setBinaryEdited(String binaryEdited) { + this.binaryEdited = binaryEdited; + } + + public boolean isNoNewLineAtTheEndOfTheFile() { + return noNewLineAtTheEndOfTheFile; + } + + public void setNoNewLineAtTheEndOfTheFile(boolean noNewLineAtTheEndOfTheFile) { + this.noNewLineAtTheEndOfTheFile = noNewLineAtTheEndOfTheFile; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java index ab7114db..0e991c77 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffParserException.java @@ -21,23 +21,22 @@ */ public class UnifiedDiffParserException extends RuntimeException { - public UnifiedDiffParserException() { - } + public UnifiedDiffParserException() {} - public UnifiedDiffParserException(String message) { - super(message); - } + public UnifiedDiffParserException(String message) { + super(message); + } - public UnifiedDiffParserException(String message, Throwable cause) { - super(message, cause); - } + public UnifiedDiffParserException(String message, Throwable cause) { + super(message, cause); + } - public UnifiedDiffParserException(Throwable cause) { - super(cause); - } - - public UnifiedDiffParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } + public UnifiedDiffParserException(Throwable cause) { + super(cause); + } + public UnifiedDiffParserException( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java index b3a66ab2..61e1aa4d 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffReader.java @@ -38,438 +38,469 @@ */ public final class UnifiedDiffReader { - static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern.compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@"); - static final Pattern TIMESTAMP_REGEXP = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d{3,})(?: [+-]\\d+)?"); - - private final InternalUnifiedDiffReader READER; - private final UnifiedDiff data = new UnifiedDiff(); - - private final UnifiedDiffLine DIFF_COMMAND = new UnifiedDiffLine(true, "^diff\\s", this::processDiff); - private final UnifiedDiffLine SIMILARITY_INDEX = new UnifiedDiffLine(true, "^similarity index (\\d+)%$", this::processSimilarityIndex); - private final UnifiedDiffLine INDEX = new UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$", this::processIndex); - private final UnifiedDiffLine FROM_FILE = new UnifiedDiffLine(true, "^---\\s", this::processFromFile); - private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile); - private final UnifiedDiffLine RENAME_FROM = new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom); - private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); - - private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom); - private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo); - - private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); - - private final UnifiedDiffLine DELETED_FILE_MODE = new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); - private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode); - private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode); - private final UnifiedDiffLine BINARY_ADDED = new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded); - private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted); - private final UnifiedDiffLine BINARY_EDITED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited); - private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); - private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine); - private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine); - private final UnifiedDiffLine LINE_ADD = new UnifiedDiffLine("^\\+", this::processAddLine); - - private UnifiedDiffFile actualFile; - - UnifiedDiffReader(Reader reader) { - this.READER = new InternalUnifiedDiffReader(reader); - } - - // schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file], - // [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], - // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], - // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; - private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { -// String headerTxt = ""; -// LOG.log(Level.FINE, "header parsing"); -// String line = null; -// while (READER.ready()) { -// line = READER.readLine(); -// LOG.log(Level.FINE, "parsing line {0}", line); -// if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) -// || FROM_FILE.validLine(line) || TO_FILE.validLine(line) -// || NEW_FILE_MODE.validLine(line)) { -// break; -// } else { -// headerTxt += line + "\n"; -// } -// } -// if (!"".equals(headerTxt)) { -// data.setHeader(headerTxt); -// } - - String line = READER.readLine(); - while (line != null) { - String headerTxt = ""; - LOG.log(Level.FINE, "header parsing"); - while (line != null) { - LOG.log(Level.FINE, "parsing line {0}", line); - if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, - COPY_FROM, COPY_TO, - NEW_FILE_MODE, DELETED_FILE_MODE, - OLD_MODE, NEW_MODE, - BINARY_ADDED, BINARY_DELETED, - BINARY_EDITED, CHUNK)) { - break; - } else { - headerTxt += line + "\n"; - } - line = READER.readLine(); - } - if (!"".equals(headerTxt)) { - data.setHeader(headerTxt); - } - if (line != null && !CHUNK.validLine(line)) { - initFileIfNecessary(); - while (line != null && !CHUNK.validLine(line)) { - if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX, - FROM_FILE, TO_FILE, - RENAME_FROM, RENAME_TO, - COPY_FROM, COPY_TO, - NEW_FILE_MODE, DELETED_FILE_MODE, - OLD_MODE, NEW_MODE, - BINARY_ADDED , BINARY_DELETED, - BINARY_EDITED)) { - throw new UnifiedDiffParserException("expected file start line not found"); - } - line = READER.readLine(); - } - } - if (line != null) { - processLine(line, CHUNK); - while ((line = READER.readLine()) != null) { - line = checkForNoNewLineAtTheEndOfTheFile(line); - - if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { - throw new UnifiedDiffParserException("expected data line not found"); - } - if ((originalTxt.size() == old_size && revisedTxt.size() == new_size) - || (old_size == 0 && new_size == 0 && originalTxt.size() == this.old_ln - && revisedTxt.size() == this.new_ln)) { - finalizeChunk(); - break; - } - } - line = READER.readLine(); - - line = checkForNoNewLineAtTheEndOfTheFile(line); - } - if (line == null || (line.startsWith("--") && !line.startsWith("---"))) { - break; - } - } - - if (READER.ready()) { - String tailTxt = ""; - while (READER.ready()) { - if (tailTxt.length() > 0) { - tailTxt += "\n"; - } - tailTxt += READER.readLine(); - } - data.setTailTxt(tailTxt); - } - - return data; - } - - private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException { - if ("\\ No newline at end of file".equals(line)) { - actualFile.setNoNewLineAtTheEndOfTheFile(true); - return READER.readLine(); - } - return line; - } - - static String[] parseFileNames(String line) { - String[] split = line.split(" "); - return new String[]{ - split[2].replaceAll("^a/", ""), - split[3].replaceAll("^b/", "") - }; - } - - private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName()); - - /** - * To parse a diff file use this method. - * - * @param stream This is the diff file data. - * @return In a UnifiedDiff structure this diff file data is returned. - * @throws IOException - * @throws UnifiedDiffParserException - */ - public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException { - UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream))); - return parser.parse(); - } - - private boolean processLine(String line, UnifiedDiffLine... rules) throws UnifiedDiffParserException { - if (line == null) { - return false; - } - for (UnifiedDiffLine rule : rules) { - if (rule.processLine(line)) { - LOG.fine(" >>> processed rule " + rule.toString()); - return true; - } - } - LOG.warning(" >>> no rule matched " + line); - return false; - //throw new UnifiedDiffParserException("parsing error at line " + line); - } - - private boolean validLine(String line, UnifiedDiffLine ... rules) { - if (line == null) { - return false; - } - for (UnifiedDiffLine rule : rules) { - if (rule.validLine(line)) { - LOG.fine(" >>> accepted rule " + rule.toString()); - return true; - } - } - return false; - } - - private void initFileIfNecessary() { - if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { - throw new IllegalStateException(); - } - actualFile = null; - if (actualFile == null) { - actualFile = new UnifiedDiffFile(); - data.addFile(actualFile); - } - } - - private void processDiff(MatchResult match, String line) { - //initFileIfNecessary(); - LOG.log(Level.FINE, "start {0}", line); - String[] fromTo = parseFileNames(READER.lastLine()); - actualFile.setFromFile(fromTo[0]); - actualFile.setToFile(fromTo[1]); - actualFile.setDiffCommand(line); - } - - private void processSimilarityIndex(MatchResult match, String line) { - actualFile.setSimilarityIndex(Integer.valueOf(match.group(1))); - } - - private List originalTxt = new ArrayList<>(); - private List revisedTxt = new ArrayList<>(); - private List addLineIdxList = new ArrayList<>(); - private List delLineIdxList = new ArrayList<>(); - private int old_ln; - private int old_size; - private int new_ln; - private int new_size; - private int delLineIdx = 0; - private int addLineIdx = 0; - - private void finalizeChunk() { - if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { - actualFile.getPatch().addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, originalTxt, delLineIdxList), new Chunk<>( - new_ln - 1, revisedTxt, addLineIdxList))); - old_ln = 0; - new_ln = 0; - originalTxt.clear(); - revisedTxt.clear(); - addLineIdxList.clear(); - delLineIdxList.clear(); - delLineIdx = 0; - addLineIdx = 0; - } - } - - private void processNormalLine(MatchResult match, String line) { - String cline = line.substring(1); - originalTxt.add(cline); - revisedTxt.add(cline); - delLineIdx++; - addLineIdx++; - } - - private void processAddLine(MatchResult match, String line) { - String cline = line.substring(1); - revisedTxt.add(cline); - addLineIdx++; - addLineIdxList.add(new_ln - 1 + addLineIdx); - } - - private void processDelLine(MatchResult match, String line) { - String cline = line.substring(1); - originalTxt.add(cline); - delLineIdx++; - delLineIdxList.add(old_ln - 1 + delLineIdx); - } - - private void processChunk(MatchResult match, String chunkStart) { - // finalizeChunk(); - old_ln = toInteger(match, 1, 1); - old_size = toInteger(match, 2, 1); - new_ln = toInteger(match, 3, 1); - new_size = toInteger(match, 4, 1); - if (old_ln == 0) { - old_ln = 1; - } - if (new_ln == 0) { - new_ln = 1; - } - } - - private static Integer toInteger(MatchResult match, int group, int defValue) throws NumberFormatException { - return Integer.valueOf(Objects.toString(match.group(group), "" + defValue)); - } - - private void processIndex(MatchResult match, String line) { - //initFileIfNecessary(); - LOG.log(Level.FINE, "index {0}", line); - actualFile.setIndex(line.substring(6)); - } - - private void processFromFile(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setFromFile(extractFileName(line)); - actualFile.setFromTimestamp(extractTimestamp(line)); - } - - private void processToFile(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setToFile(extractFileName(line)); - actualFile.setToTimestamp(extractTimestamp(line)); - } - - private void processRenameFrom(MatchResult match, String line) { - actualFile.setRenameFrom(match.group(1)); - } - - private void processRenameTo(MatchResult match, String line) { - actualFile.setRenameTo(match.group(1)); - } - - private void processCopyFrom(MatchResult match, String line) { - actualFile.setCopyFrom(match.group(1)); - } - - private void processCopyTo(MatchResult match, String line) { - actualFile.setCopyTo(match.group(1)); - } - - private void processNewFileMode(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setNewFileMode(match.group(1)); - } - - private void processDeletedFileMode(MatchResult match, String line) { - //initFileIfNecessary(); - actualFile.setDeletedFileMode(match.group(1)); - } - - private void processOldMode(MatchResult match, String line) { - actualFile.setOldMode(match.group(1)); - } - - private void processNewMode(MatchResult match, String line) { - actualFile.setNewMode(match.group(1)); - } - - private void processBinaryAdded(MatchResult match, String line) { - actualFile.setBinaryAdded(match.group(1)); - } - - private void processBinaryDeleted(MatchResult match, String line) { - actualFile.setBinaryDeleted(match.group(1)); - } - - private void processBinaryEdited(MatchResult match, String line) { - actualFile.setBinaryEdited(match.group(1)); - } - - private String extractFileName(String _line) { - Matcher matcher = TIMESTAMP_REGEXP.matcher(_line); - String line = _line; - if (matcher.find()) { - line = line.substring(0, matcher.start()); - } - line = line.split("\t")[0]; - return line.substring(4).replaceFirst("^(a|b|old|new)/", "") - .trim(); - } - - private String extractTimestamp(String line) { - Matcher matcher = TIMESTAMP_REGEXP.matcher(line); - if (matcher.find()) { - return matcher.group(); - } - return null; - } - - final class UnifiedDiffLine { - - private final Pattern pattern; - private final BiConsumer command; - private final boolean stopsHeaderParsing; - - public UnifiedDiffLine(String pattern, BiConsumer command) { - this(false, pattern, command); - } - - public UnifiedDiffLine(boolean stopsHeaderParsing, String pattern, BiConsumer command) { - this.pattern = Pattern.compile(pattern); - this.command = command; - this.stopsHeaderParsing = stopsHeaderParsing; - } - - public UnifiedDiffLine(boolean stopsHeaderParsing, Pattern pattern, BiConsumer command) { - this.pattern = pattern; - this.command = command; - this.stopsHeaderParsing = stopsHeaderParsing; - } - - public boolean validLine(String line) { - Matcher m = pattern.matcher(line); - return m.find(); - } - - public boolean processLine(String line) throws UnifiedDiffParserException { - Matcher m = pattern.matcher(line); - if (m.find()) { - command.accept(m.toMatchResult(), line); - return true; - } else { - return false; - } - } - - public boolean isStopsHeaderParsing() { - return stopsHeaderParsing; - } - - @Override - public String toString() { - return "UnifiedDiffLine{" + "pattern=" + pattern + ", stopsHeaderParsing=" + stopsHeaderParsing + '}'; - } - } + static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = + Pattern.compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@"); + static final Pattern TIMESTAMP_REGEXP = + Pattern.compile("(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d{3,})(?: [+-]\\d+)?"); + + private final InternalUnifiedDiffReader READER; + private final UnifiedDiff data = new UnifiedDiff(); + + private final UnifiedDiffLine DIFF_COMMAND = new UnifiedDiffLine(true, "^diff\\s", this::processDiff); + private final UnifiedDiffLine SIMILARITY_INDEX = + new UnifiedDiffLine(true, "^similarity index (\\d+)%$", this::processSimilarityIndex); + private final UnifiedDiffLine INDEX = + new UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$", this::processIndex); + private final UnifiedDiffLine FROM_FILE = new UnifiedDiffLine(true, "^---\\s", this::processFromFile); + private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile); + private final UnifiedDiffLine RENAME_FROM = + new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom); + private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo); + + private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom); + private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo); + + private final UnifiedDiffLine NEW_FILE_MODE = + new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode); + + private final UnifiedDiffLine DELETED_FILE_MODE = + new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode); + private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode); + private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode); + private final UnifiedDiffLine BINARY_ADDED = + new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded); + private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine( + true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted); + private final UnifiedDiffLine BINARY_EDITED = + new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited); + private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk); + private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine); + private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine); + private final UnifiedDiffLine LINE_ADD = new UnifiedDiffLine("^\\+", this::processAddLine); + + private UnifiedDiffFile actualFile; + + UnifiedDiffReader(Reader reader) { + this.READER = new InternalUnifiedDiffReader(reader); + } + + // schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file], + // [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index], + // [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk], + // [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]]; + private UnifiedDiff parse() throws IOException, UnifiedDiffParserException { + // String headerTxt = ""; + // LOG.log(Level.FINE, "header parsing"); + // String line = null; + // while (READER.ready()) { + // line = READER.readLine(); + // LOG.log(Level.FINE, "parsing line {0}", line); + // if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line) + // || FROM_FILE.validLine(line) || TO_FILE.validLine(line) + // || NEW_FILE_MODE.validLine(line)) { + // break; + // } else { + // headerTxt += line + "\n"; + // } + // } + // if (!"".equals(headerTxt)) { + // data.setHeader(headerTxt); + // } + + String line = READER.readLine(); + while (line != null) { + String headerTxt = ""; + LOG.log(Level.FINE, "header parsing"); + while (line != null) { + LOG.log(Level.FINE, "parsing line {0}", line); + if (validLine( + line, + DIFF_COMMAND, + SIMILARITY_INDEX, + INDEX, + FROM_FILE, + TO_FILE, + RENAME_FROM, + RENAME_TO, + COPY_FROM, + COPY_TO, + NEW_FILE_MODE, + DELETED_FILE_MODE, + OLD_MODE, + NEW_MODE, + BINARY_ADDED, + BINARY_DELETED, + BINARY_EDITED, + CHUNK)) { + break; + } else { + headerTxt += line + "\n"; + } + line = READER.readLine(); + } + if (!"".equals(headerTxt)) { + data.setHeader(headerTxt); + } + if (line != null && !CHUNK.validLine(line)) { + initFileIfNecessary(); + while (line != null && !CHUNK.validLine(line)) { + if (!processLine( + line, + DIFF_COMMAND, + SIMILARITY_INDEX, + INDEX, + FROM_FILE, + TO_FILE, + RENAME_FROM, + RENAME_TO, + COPY_FROM, + COPY_TO, + NEW_FILE_MODE, + DELETED_FILE_MODE, + OLD_MODE, + NEW_MODE, + BINARY_ADDED, + BINARY_DELETED, + BINARY_EDITED)) { + throw new UnifiedDiffParserException("expected file start line not found"); + } + line = READER.readLine(); + } + } + if (line != null) { + processLine(line, CHUNK); + while ((line = READER.readLine()) != null) { + line = checkForNoNewLineAtTheEndOfTheFile(line); + + if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) { + throw new UnifiedDiffParserException("expected data line not found"); + } + if ((originalTxt.size() == old_size && revisedTxt.size() == new_size) + || (old_size == 0 + && new_size == 0 + && originalTxt.size() == this.old_ln + && revisedTxt.size() == this.new_ln)) { + finalizeChunk(); + break; + } + } + line = READER.readLine(); + + line = checkForNoNewLineAtTheEndOfTheFile(line); + } + if (line == null || (line.startsWith("--") && !line.startsWith("---"))) { + break; + } + } + + if (READER.ready()) { + String tailTxt = ""; + while (READER.ready()) { + if (tailTxt.length() > 0) { + tailTxt += "\n"; + } + tailTxt += READER.readLine(); + } + data.setTailTxt(tailTxt); + } + + return data; + } + + private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException { + if ("\\ No newline at end of file".equals(line)) { + actualFile.setNoNewLineAtTheEndOfTheFile(true); + return READER.readLine(); + } + return line; + } + + static String[] parseFileNames(String line) { + String[] split = line.split(" "); + return new String[] {split[2].replaceAll("^a/", ""), split[3].replaceAll("^b/", "")}; + } + + private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName()); + + /** + * To parse a diff file use this method. + * + * @param stream This is the diff file data. + * @return In a UnifiedDiff structure this diff file data is returned. + * @throws IOException + * @throws UnifiedDiffParserException + */ + public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException { + UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream))); + return parser.parse(); + } + + private boolean processLine(String line, UnifiedDiffLine... rules) throws UnifiedDiffParserException { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.processLine(line)) { + LOG.fine(" >>> processed rule " + rule.toString()); + return true; + } + } + LOG.warning(" >>> no rule matched " + line); + return false; + // throw new UnifiedDiffParserException("parsing error at line " + line); + } + + private boolean validLine(String line, UnifiedDiffLine... rules) { + if (line == null) { + return false; + } + for (UnifiedDiffLine rule : rules) { + if (rule.validLine(line)) { + LOG.fine(" >>> accepted rule " + rule.toString()); + return true; + } + } + return false; + } + + private void initFileIfNecessary() { + if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { + throw new IllegalStateException(); + } + actualFile = null; + if (actualFile == null) { + actualFile = new UnifiedDiffFile(); + data.addFile(actualFile); + } + } + + private void processDiff(MatchResult match, String line) { + // initFileIfNecessary(); + LOG.log(Level.FINE, "start {0}", line); + String[] fromTo = parseFileNames(READER.lastLine()); + actualFile.setFromFile(fromTo[0]); + actualFile.setToFile(fromTo[1]); + actualFile.setDiffCommand(line); + } + + private void processSimilarityIndex(MatchResult match, String line) { + actualFile.setSimilarityIndex(Integer.valueOf(match.group(1))); + } + + private List originalTxt = new ArrayList<>(); + private List revisedTxt = new ArrayList<>(); + private List addLineIdxList = new ArrayList<>(); + private List delLineIdxList = new ArrayList<>(); + private int old_ln; + private int old_size; + private int new_ln; + private int new_size; + private int delLineIdx = 0; + private int addLineIdx = 0; + + private void finalizeChunk() { + if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) { + actualFile + .getPatch() + .addDelta(new ChangeDelta<>( + new Chunk<>(old_ln - 1, originalTxt, delLineIdxList), + new Chunk<>(new_ln - 1, revisedTxt, addLineIdxList))); + old_ln = 0; + new_ln = 0; + originalTxt.clear(); + revisedTxt.clear(); + addLineIdxList.clear(); + delLineIdxList.clear(); + delLineIdx = 0; + addLineIdx = 0; + } + } + + private void processNormalLine(MatchResult match, String line) { + String cline = line.substring(1); + originalTxt.add(cline); + revisedTxt.add(cline); + delLineIdx++; + addLineIdx++; + } + + private void processAddLine(MatchResult match, String line) { + String cline = line.substring(1); + revisedTxt.add(cline); + addLineIdx++; + addLineIdxList.add(new_ln - 1 + addLineIdx); + } + + private void processDelLine(MatchResult match, String line) { + String cline = line.substring(1); + originalTxt.add(cline); + delLineIdx++; + delLineIdxList.add(old_ln - 1 + delLineIdx); + } + + private void processChunk(MatchResult match, String chunkStart) { + // finalizeChunk(); + old_ln = toInteger(match, 1, 1); + old_size = toInteger(match, 2, 1); + new_ln = toInteger(match, 3, 1); + new_size = toInteger(match, 4, 1); + if (old_ln == 0) { + old_ln = 1; + } + if (new_ln == 0) { + new_ln = 1; + } + } + + private static Integer toInteger(MatchResult match, int group, int defValue) throws NumberFormatException { + return Integer.valueOf(Objects.toString(match.group(group), "" + defValue)); + } + + private void processIndex(MatchResult match, String line) { + // initFileIfNecessary(); + LOG.log(Level.FINE, "index {0}", line); + actualFile.setIndex(line.substring(6)); + } + + private void processFromFile(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setFromFile(extractFileName(line)); + actualFile.setFromTimestamp(extractTimestamp(line)); + } + + private void processToFile(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setToFile(extractFileName(line)); + actualFile.setToTimestamp(extractTimestamp(line)); + } + + private void processRenameFrom(MatchResult match, String line) { + actualFile.setRenameFrom(match.group(1)); + } + + private void processRenameTo(MatchResult match, String line) { + actualFile.setRenameTo(match.group(1)); + } + + private void processCopyFrom(MatchResult match, String line) { + actualFile.setCopyFrom(match.group(1)); + } + + private void processCopyTo(MatchResult match, String line) { + actualFile.setCopyTo(match.group(1)); + } + + private void processNewFileMode(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setNewFileMode(match.group(1)); + } + + private void processDeletedFileMode(MatchResult match, String line) { + // initFileIfNecessary(); + actualFile.setDeletedFileMode(match.group(1)); + } + + private void processOldMode(MatchResult match, String line) { + actualFile.setOldMode(match.group(1)); + } + + private void processNewMode(MatchResult match, String line) { + actualFile.setNewMode(match.group(1)); + } + + private void processBinaryAdded(MatchResult match, String line) { + actualFile.setBinaryAdded(match.group(1)); + } + + private void processBinaryDeleted(MatchResult match, String line) { + actualFile.setBinaryDeleted(match.group(1)); + } + + private void processBinaryEdited(MatchResult match, String line) { + actualFile.setBinaryEdited(match.group(1)); + } + + private String extractFileName(String _line) { + Matcher matcher = TIMESTAMP_REGEXP.matcher(_line); + String line = _line; + if (matcher.find()) { + line = line.substring(0, matcher.start()); + } + line = line.split("\t")[0]; + return line.substring(4).replaceFirst("^(a|b|old|new)/", "").trim(); + } + + private String extractTimestamp(String line) { + Matcher matcher = TIMESTAMP_REGEXP.matcher(line); + if (matcher.find()) { + return matcher.group(); + } + return null; + } + + final class UnifiedDiffLine { + + private final Pattern pattern; + private final BiConsumer command; + private final boolean stopsHeaderParsing; + + public UnifiedDiffLine(String pattern, BiConsumer command) { + this(false, pattern, command); + } + + public UnifiedDiffLine(boolean stopsHeaderParsing, String pattern, BiConsumer command) { + this.pattern = Pattern.compile(pattern); + this.command = command; + this.stopsHeaderParsing = stopsHeaderParsing; + } + + public UnifiedDiffLine(boolean stopsHeaderParsing, Pattern pattern, BiConsumer command) { + this.pattern = pattern; + this.command = command; + this.stopsHeaderParsing = stopsHeaderParsing; + } + + public boolean validLine(String line) { + Matcher m = pattern.matcher(line); + return m.find(); + } + + public boolean processLine(String line) throws UnifiedDiffParserException { + Matcher m = pattern.matcher(line); + if (m.find()) { + command.accept(m.toMatchResult(), line); + return true; + } else { + return false; + } + } + + public boolean isStopsHeaderParsing() { + return stopsHeaderParsing; + } + + @Override + public String toString() { + return "UnifiedDiffLine{" + "pattern=" + pattern + ", stopsHeaderParsing=" + stopsHeaderParsing + '}'; + } + } } class InternalUnifiedDiffReader extends BufferedReader { - private String lastLine; + private String lastLine; - public InternalUnifiedDiffReader(Reader reader) { - super(reader); - } + public InternalUnifiedDiffReader(Reader reader) { + super(reader); + } - @Override - public String readLine() throws IOException { - lastLine = super.readLine(); - return lastLine(); - } + @Override + public String readLine() throws IOException { + lastLine = super.readLine(); + return lastLine(); + } - String lastLine() { - return lastLine; - } + String lastLine() { + return lastLine; + } } diff --git a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java index 7cac8a2c..aa572683 100644 --- a/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java +++ b/java-diff-utils/src/main/java/com/github/difflib/unifieddiff/UnifiedDiffWriter.java @@ -32,180 +32,195 @@ */ public class UnifiedDiffWriter { - private static final Logger LOG = Logger.getLogger(UnifiedDiffWriter.class.getName()); - - public static void write(UnifiedDiff diff, Function> originalLinesProvider, Writer writer, int contextSize) throws IOException { - Objects.requireNonNull(originalLinesProvider, "original lines provider needs to be specified"); - write(diff, originalLinesProvider, line -> { - try { - writer.append(line).append("\n"); - } catch (IOException ex) { - LOG.log(Level.SEVERE, null, ex); - } - }, contextSize); - } - - public static void write(UnifiedDiff diff, Function> originalLinesProvider, Consumer writer, int contextSize) throws IOException { - if (diff.getHeader() != null) { - writer.accept(diff.getHeader()); - } - - for (UnifiedDiffFile file : diff.getFiles()) { - List> patchDeltas = new ArrayList<>( - file.getPatch().getDeltas()); - if (!patchDeltas.isEmpty()) { - writeOrNothing(writer, file.getDiffCommand()); - if (file.getIndex() != null) { - writer.accept("index " + file.getIndex()); - } - - writer.accept("--- " + (file.getFromFile() == null ? "/dev/null" : file.getFromFile())); - - if (file.getToFile() != null) { - writer.accept("+++ " + file.getToFile()); - } - - List originalLines = originalLinesProvider.apply(file.getFromFile()); - - List> deltas = new ArrayList<>(); - - AbstractDelta delta = patchDeltas.get(0); - deltas.add(delta); // add the first Delta to the current set - // if there's more than 1 Delta, we may need to output them together - if (patchDeltas.size() > 1) { - for (int i = 1; i < patchDeltas.size(); i++) { - int position = delta.getSource().getPosition(); - - // Check if the next Delta is too close to the current - // position. - // And if it is, add it to the current set - AbstractDelta nextDelta = patchDeltas.get(i); - if ((position + delta.getSource().size() + contextSize) >= (nextDelta - .getSource().getPosition() - contextSize)) { - deltas.add(nextDelta); - } else { - // if it isn't, output the current set, - // then create a new set and add the current Delta to - // it. - processDeltas(writer, originalLines, deltas, contextSize, false); - deltas.clear(); - deltas.add(nextDelta); - } - delta = nextDelta; - } - - } - // don't forget to process the last set of Deltas - processDeltas(writer, originalLines, deltas, contextSize, - patchDeltas.size() == 1 && file.getFromFile() == null); - } - - } - if (diff.getTail() != null) { - writer.accept("--"); - writer.accept(diff.getTail()); - } - } - - private static void processDeltas(Consumer writer, - List origLines, List> deltas, - int contextSize, boolean newFile) { - List buffer = new ArrayList<>(); - int origTotal = 0; // counter for total lines output from Original - int revTotal = 0; // counter for total lines output from Original - int line; - - AbstractDelta curDelta = deltas.get(0); - - int origStart; - if (newFile) { - origStart = 0; - } else { - // NOTE: +1 to overcome the 0-offset Position - origStart = curDelta.getSource().getPosition() + 1 - contextSize; - if (origStart < 1) { - origStart = 1; - } - } - - int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; - if (revStart < 1) { - revStart = 1; - } - - // find the start of the wrapper context code - int contextStart = curDelta.getSource().getPosition() - contextSize; - if (contextStart < 0) { - contextStart = 0; // clamp to the start of the file - } - - // output the context before the first Delta - for (line = contextStart; line < curDelta.getSource().getPosition() - && line < origLines.size(); line++) { // - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - // output the first Delta - getDeltaText(txt -> buffer.add(txt), curDelta); - origTotal += curDelta.getSource().getLines().size(); - revTotal += curDelta.getTarget().getLines().size(); - - int deltaIndex = 1; - while (deltaIndex < deltas.size()) { // for each of the other Deltas - AbstractDelta nextDelta = deltas.get(deltaIndex); - int intermediateStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = intermediateStart; line < nextDelta.getSource().getPosition() - && line < origLines.size(); line++) { - // output the code between the last Delta and this one - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - getDeltaText(txt -> buffer.add(txt), nextDelta); // output the Delta - origTotal += nextDelta.getSource().getLines().size(); - revTotal += nextDelta.getTarget().getLines().size(); - curDelta = nextDelta; - deltaIndex++; - } - - // Now output the post-Delta context code, clamping the end of the file - contextStart = curDelta.getSource().getPosition() - + curDelta.getSource().getLines().size(); - for (line = contextStart; (line < (contextStart + contextSize)) - && (line < origLines.size()); line++) { - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // Create and insert the block header, conforming to the Unified Diff - // standard - writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@"); - buffer.forEach(txt -> { - writer.accept(txt); - }); - } - - /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. - * - * @param writer consumer for the list of String lines of code - * @param delta the Delta to output - */ - private static void getDeltaText(Consumer writer, AbstractDelta delta) { - for (String line : delta.getSource().getLines()) { - writer.accept("-" + line); - } - for (String line : delta.getTarget().getLines()) { - writer.accept("+" + line); - } - } - - private static void writeOrNothing(Consumer writer, String str) throws IOException { - if (str != null) { - writer.accept(str); - } - } + private static final Logger LOG = Logger.getLogger(UnifiedDiffWriter.class.getName()); + + public static void write( + UnifiedDiff diff, Function> originalLinesProvider, Writer writer, int contextSize) + throws IOException { + Objects.requireNonNull(originalLinesProvider, "original lines provider needs to be specified"); + write( + diff, + originalLinesProvider, + line -> { + try { + writer.append(line).append("\n"); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + } + }, + contextSize); + } + + public static void write( + UnifiedDiff diff, + Function> originalLinesProvider, + Consumer writer, + int contextSize) + throws IOException { + if (diff.getHeader() != null) { + writer.accept(diff.getHeader()); + } + + for (UnifiedDiffFile file : diff.getFiles()) { + List> patchDeltas = + new ArrayList<>(file.getPatch().getDeltas()); + if (!patchDeltas.isEmpty()) { + writeOrNothing(writer, file.getDiffCommand()); + if (file.getIndex() != null) { + writer.accept("index " + file.getIndex()); + } + + writer.accept("--- " + (file.getFromFile() == null ? "/dev/null" : file.getFromFile())); + + if (file.getToFile() != null) { + writer.accept("+++ " + file.getToFile()); + } + + List originalLines = originalLinesProvider.apply(file.getFromFile()); + + List> deltas = new ArrayList<>(); + + AbstractDelta delta = patchDeltas.get(0); + deltas.add(delta); // add the first Delta to the current set + // if there's more than 1 Delta, we may need to output them together + if (patchDeltas.size() > 1) { + for (int i = 1; i < patchDeltas.size(); i++) { + int position = delta.getSource().getPosition(); + + // Check if the next Delta is too close to the current + // position. + // And if it is, add it to the current set + AbstractDelta nextDelta = patchDeltas.get(i); + if ((position + delta.getSource().size() + contextSize) + >= (nextDelta.getSource().getPosition() - contextSize)) { + deltas.add(nextDelta); + } else { + // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + processDeltas(writer, originalLines, deltas, contextSize, false); + deltas.clear(); + deltas.add(nextDelta); + } + delta = nextDelta; + } + } + // don't forget to process the last set of Deltas + processDeltas( + writer, + originalLines, + deltas, + contextSize, + patchDeltas.size() == 1 && file.getFromFile() == null); + } + } + if (diff.getTail() != null) { + writer.accept("--"); + writer.accept(diff.getTail()); + } + } + + private static void processDeltas( + Consumer writer, + List origLines, + List> deltas, + int contextSize, + boolean newFile) { + List buffer = new ArrayList<>(); + int origTotal = 0; // counter for total lines output from Original + int revTotal = 0; // counter for total lines output from Original + int line; + + AbstractDelta curDelta = deltas.get(0); + + int origStart; + if (newFile) { + origStart = 0; + } else { + // NOTE: +1 to overcome the 0-offset Position + origStart = curDelta.getSource().getPosition() + 1 - contextSize; + if (origStart < 1) { + origStart = 1; + } + } + + int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; + if (revStart < 1) { + revStart = 1; + } + + // find the start of the wrapper context code + int contextStart = curDelta.getSource().getPosition() - contextSize; + if (contextStart < 0) { + contextStart = 0; // clamp to the start of the file + } + + // output the context before the first Delta + for (line = contextStart; line < curDelta.getSource().getPosition() && line < origLines.size(); line++) { // + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + // output the first Delta + getDeltaText(txt -> buffer.add(txt), curDelta); + origTotal += curDelta.getSource().getLines().size(); + revTotal += curDelta.getTarget().getLines().size(); + + int deltaIndex = 1; + while (deltaIndex < deltas.size()) { // for each of the other Deltas + AbstractDelta nextDelta = deltas.get(deltaIndex); + int intermediateStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = intermediateStart; + line < nextDelta.getSource().getPosition() && line < origLines.size(); + line++) { + // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + getDeltaText(txt -> buffer.add(txt), nextDelta); // output the Delta + origTotal += nextDelta.getSource().getLines().size(); + revTotal += nextDelta.getTarget().getLines().size(); + curDelta = nextDelta; + deltaIndex++; + } + + // Now output the post-Delta context code, clamping the end of the file + contextStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); + for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // Create and insert the block header, conforming to the Unified Diff + // standard + writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@"); + buffer.forEach(txt -> { + writer.accept(txt); + }); + } + + /** + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. + * + * @param writer consumer for the list of String lines of code + * @param delta the Delta to output + */ + private static void getDeltaText(Consumer writer, AbstractDelta delta) { + for (String line : delta.getSource().getLines()) { + writer.accept("-" + line); + } + for (String line : delta.getTarget().getLines()) { + writer.accept("+" + line); + } + } + + private static void writeOrNothing(Consumer writer, String str) throws IOException { + if (str != null) { + writer.accept(str); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java index 3a4a6995..8d133622 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -1,9 +1,15 @@ package com.github.difflib; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; import com.github.difflib.patch.DeleteDelta; -import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.EqualDelta; import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; @@ -19,224 +25,225 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static java.util.stream.Collectors.toList; import java.util.zip.ZipFile; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class DiffUtilsTest { - @Test - public void testDiff_Insert() { - final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays. - asList("hhh", "jjj", "kkk")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof InsertDelta); - assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_Delete() { - final Patch patch = DiffUtils.diff(Arrays.asList("ddd", "fff", "ggg"), Arrays. - asList("ggg")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof DeleteDelta); - assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource()); - assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getTarget()); - } - - @Test - public void testDiff_Change() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc"); - final List changeTest_to = Arrays.asList("aaa", "zzz", "ccc"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget()); - } - - @Test - public void testDiff_EmptyList() { - final Patch patch = DiffUtils.diff(new ArrayList<>(), new ArrayList<>()); - assertNotNull(patch); - assertEquals(0, patch.getDeltas().size()); - } - - @Test - public void testDiff_EmptyListWithNonEmpty() { - final Patch patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa")); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof InsertDelta); - } - - @Test - public void testDiffInline() { - final Patch patch = DiffUtils.diffInline("", "test"); - assertEquals(1, patch.getDeltas().size()); - assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); - assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); - assertEquals("test", patch.getDeltas().get(0).getTarget().getLines().get(0)); - } - - @Test - public void testDiffInline2() { - final Patch patch = DiffUtils.diffInline("es", "fest"); - assertEquals(2, patch.getDeltas().size()); - assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); - assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); - assertEquals(2, patch.getDeltas().get(1).getSource().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); - assertEquals(0, patch.getDeltas().get(1).getSource().getLines().size()); - assertEquals("f", patch.getDeltas().get(0).getTarget().getLines().get(0)); - assertEquals("t", patch.getDeltas().get(1).getTarget().getLines().get(0)); - } - - @Test - public void testDiffIntegerList() { - List original = Arrays.asList(1, 2, 3, 4, 5); - List revised = Arrays.asList(2, 3, 4, 6); - - final Patch patch = DiffUtils.diff(original, revised); - - for (AbstractDelta delta : patch.getDeltas()) { - System.out.println(delta); - } - - assertEquals(2, patch.getDeltas().size()); - assertEquals("[DeleteDelta, position: 0, lines: [1]]", patch.getDeltas().get(0).toString()); - assertEquals("[ChangeDelta, position: 4, lines: [5] to [6]]", patch.getDeltas().get(1).toString()); - } - - @Test - public void testDiffMissesChangeForkDnaumenkoIssue31() { - List original = Arrays.asList("line1", "line2", "line3"); - List revised = Arrays.asList("line1", "line2-2", "line4"); - - Patch patch = DiffUtils.diff(original, revised); - assertEquals(1, patch.getDeltas().size()); - assertEquals("[ChangeDelta, position: 1, lines: [line2, line3] to [line2-2, line4]]", patch.getDeltas().get(0).toString()); - } - - /** - * To test this, the greedy Myer algorithm is not suitable. - */ - @Test - @Disabled - public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException { - ZipFile zip = new ZipFile(TestConstants.MOCK_FOLDER + "/large_dataset1.zip"); - - Patch patch = DiffUtils.diff( - readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))), - readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb")))); - - assertEquals(1, patch.getDeltas().size()); - } - - public static List readStringListFromInputStream(InputStream is) throws IOException { - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { - - return reader.lines().collect(toList()); - } - } - - @Test - public void testDiffMyersExample1() { - final Patch patch = DiffUtils.diff(Arrays.asList("A", "B", "C", "A", "B", "B", "A"), Arrays.asList("C", "B", "A", "B", "A", "C")); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiff_Equal() { - final Patch patch = DiffUtils.diff( - Arrays.asList("hhh", "jjj", "kkk"), - Arrays.asList("hhh", "jjj", "kkk"), true); - assertNotNull(patch); - assertEquals(1, patch.getDeltas().size()); - final AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_InsertWithEqual() { - final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays. - asList("hhh", "jjj", "kkk"), true); - assertNotNull(patch); - assertEquals(2, patch.getDeltas().size()); - - AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getTarget()); - - delta = patch.getDeltas().get(1); - assertTrue(delta instanceof InsertDelta); - assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); - } - - @Test - public void testDiff_ProblemIssue42() { - final Patch patch = DiffUtils.diff( - Arrays.asList("The", "dog", "is", "brown"), - Arrays.asList("The", "fox", "is", "down"), true); - - System.out.println(patch); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - - - assertThat(patch.getDeltas()).extracting(d -> d.getType().name()) - .containsExactly("EQUAL", "CHANGE", "EQUAL", "CHANGE"); - - AbstractDelta delta = patch.getDeltas().get(0); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getSource()); - assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getTarget()); - - delta = patch.getDeltas().get(1); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(1, Arrays.asList("dog")), delta.getSource()); - assertEquals(new Chunk<>(1, Arrays.asList("fox")), delta.getTarget()); - - delta = patch.getDeltas().get(2); - assertTrue(delta instanceof EqualDelta); - assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getSource()); - assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getTarget()); - - delta = patch.getDeltas().get(3); - assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); - assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); - } - - @Test - public void testDiffPatchIssue189Problem() throws IOException { - String original = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt"))); - String revised = new String(Files.readAllBytes(Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt"))); - - Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); - - assertEquals(1, patch.getDeltas().size()); - } + @Test + public void testDiff_Insert() { + final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof InsertDelta); + assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_Delete() { + final Patch patch = DiffUtils.diff(Arrays.asList("ddd", "fff", "ggg"), Arrays.asList("ggg")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof DeleteDelta); + assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource()); + assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getTarget()); + } + + @Test + public void testDiff_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc"); + final List changeTest_to = Arrays.asList("aaa", "zzz", "ccc"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget()); + } + + @Test + public void testDiff_EmptyList() { + final Patch patch = DiffUtils.diff(new ArrayList<>(), new ArrayList<>()); + assertNotNull(patch); + assertEquals(0, patch.getDeltas().size()); + } + + @Test + public void testDiff_EmptyListWithNonEmpty() { + final Patch patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa")); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof InsertDelta); + } + + @Test + public void testDiffInline() { + final Patch patch = DiffUtils.diffInline("", "test"); + assertEquals(1, patch.getDeltas().size()); + assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); + assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); + assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); + assertEquals("test", patch.getDeltas().get(0).getTarget().getLines().get(0)); + } + + @Test + public void testDiffInline2() { + final Patch patch = DiffUtils.diffInline("es", "fest"); + assertEquals(2, patch.getDeltas().size()); + assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); + assertEquals(0, patch.getDeltas().get(0).getSource().getPosition()); + assertEquals(2, patch.getDeltas().get(1).getSource().getPosition()); + assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size()); + assertEquals(0, patch.getDeltas().get(1).getSource().getLines().size()); + assertEquals("f", patch.getDeltas().get(0).getTarget().getLines().get(0)); + assertEquals("t", patch.getDeltas().get(1).getTarget().getLines().get(0)); + } + + @Test + public void testDiffIntegerList() { + List original = Arrays.asList(1, 2, 3, 4, 5); + List revised = Arrays.asList(2, 3, 4, 6); + + final Patch patch = DiffUtils.diff(original, revised); + + for (AbstractDelta delta : patch.getDeltas()) { + System.out.println(delta); + } + + assertEquals(2, patch.getDeltas().size()); + assertEquals( + "[DeleteDelta, position: 0, lines: [1]]", + patch.getDeltas().get(0).toString()); + assertEquals( + "[ChangeDelta, position: 4, lines: [5] to [6]]", + patch.getDeltas().get(1).toString()); + } + + @Test + public void testDiffMissesChangeForkDnaumenkoIssue31() { + List original = Arrays.asList("line1", "line2", "line3"); + List revised = Arrays.asList("line1", "line2-2", "line4"); + + Patch patch = DiffUtils.diff(original, revised); + assertEquals(1, patch.getDeltas().size()); + assertEquals( + "[ChangeDelta, position: 1, lines: [line2, line3] to [line2-2, line4]]", + patch.getDeltas().get(0).toString()); + } + + /** + * To test this, the greedy Myer algorithm is not suitable. + */ + @Test + @Disabled + public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException { + ZipFile zip = new ZipFile(TestConstants.MOCK_FOLDER + "/large_dataset1.zip"); + + Patch patch = DiffUtils.diff( + readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))), + readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb")))); + + assertEquals(1, patch.getDeltas().size()); + } + + public static List readStringListFromInputStream(InputStream is) throws IOException { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) { + + return reader.lines().collect(toList()); + } + } + + @Test + public void testDiffMyersExample1() { + final Patch patch = DiffUtils.diff( + Arrays.asList("A", "B", "C", "A", "B", "B", "A"), Arrays.asList("C", "B", "A", "B", "A", "C")); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiff_Equal() { + final Patch patch = + DiffUtils.diff(Arrays.asList("hhh", "jjj", "kkk"), Arrays.asList("hhh", "jjj", "kkk"), true); + assertNotNull(patch); + assertEquals(1, patch.getDeltas().size()); + final AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_InsertWithEqual() { + final Patch patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk"), true); + assertNotNull(patch); + assertEquals(2, patch.getDeltas().size()); + + AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getTarget()); + + delta = patch.getDeltas().get(1); + assertTrue(delta instanceof InsertDelta); + assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); + } + + @Test + public void testDiff_ProblemIssue42() { + final Patch patch = DiffUtils.diff( + Arrays.asList("The", "dog", "is", "brown"), Arrays.asList("The", "fox", "is", "down"), true); + + System.out.println(patch); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + + assertThat(patch.getDeltas()) + .extracting(d -> d.getType().name()) + .containsExactly("EQUAL", "CHANGE", "EQUAL", "CHANGE"); + + AbstractDelta delta = patch.getDeltas().get(0); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getSource()); + assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getTarget()); + + delta = patch.getDeltas().get(1); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(1, Arrays.asList("dog")), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("fox")), delta.getTarget()); + + delta = patch.getDeltas().get(2); + assertTrue(delta instanceof EqualDelta); + assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getSource()); + assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getTarget()); + + delta = patch.getDeltas().get(3); + assertTrue(delta instanceof ChangeDelta); + assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource()); + assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget()); + } + + @Test + public void testDiffPatchIssue189Problem() throws IOException { + String original = new String(Files.readAllBytes( + Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt"))); + String revised = new String(Files.readAllBytes( + Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt"))); + + Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + assertEquals(1, patch.getDeltas().size()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java index 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/MyersDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 1e233a8c..f315f2b9 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -15,13 +15,14 @@ */ package com.github.difflib.algorithm.myers; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; /** @@ -30,43 +31,48 @@ */ public class MyersDiffTest { - @Test - public void testDiffMyersExample1Forward() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiffMyersExample1ForwardWithListener() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - - List logdata = new ArrayList<>(); - final Patch patch = Patch.generate(original, revised, - new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = + Patch.generate(original, revised, new MyersDiff().computeDiff(original, revised, null)); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate( + original, revised, new MyersDiff().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } - @Override - public void diffEnd() { - logdata.add("end"); - } - })); - assertNotNull(patch); - assertEquals(4, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - System.out.println(logdata); - assertEquals(8, logdata.size()); - } + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + assertEquals(4, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + System.out.println(logdata); + assertEquals(8, logdata.size()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java index b63876ac..87f80134 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpaceTest.java @@ -15,77 +15,83 @@ */ package com.github.difflib.algorithm.myers; +import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.Assertions.*; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.stream.Collectors.toList; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; /** * * @author tw */ public class MyersDiffWithLinearSpaceTest { - - @Test - public void testDiffMyersExample1Forward() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - final Patch patch = Patch.generate(original, revised, new MyersDiffWithLinearSpace().computeDiff(original, revised, null)); - assertNotNull(patch); - System.out.println(patch); - assertEquals(5, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - } - - @Test - public void testDiffMyersExample1ForwardWithListener() { - List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); - List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); - - List logdata = new ArrayList<>(); - final Patch patch = Patch.generate(original, revised, - new MyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { - @Override - public void diffStart() { - logdata.add("start"); - } - @Override - public void diffStep(int value, int max) { - logdata.add(value + " - " + max); - } + @Test + public void testDiffMyersExample1Forward() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + final Patch patch = Patch.generate( + original, revised, new MyersDiffWithLinearSpace().computeDiff(original, revised, null)); + assertNotNull(patch); + System.out.println(patch); + assertEquals(5, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + } + + @Test + public void testDiffMyersExample1ForwardWithListener() { + List original = Arrays.asList("A", "B", "C", "A", "B", "B", "A"); + List revised = Arrays.asList("C", "B", "A", "B", "A", "C"); + + List logdata = new ArrayList<>(); + final Patch patch = Patch.generate( + original, + revised, + new MyersDiffWithLinearSpace().computeDiff(original, revised, new DiffAlgorithmListener() { + @Override + public void diffStart() { + logdata.add("start"); + } + + @Override + public void diffStep(int value, int max) { + logdata.add(value + " - " + max); + } + + @Override + public void diffEnd() { + logdata.add("end"); + } + })); + assertNotNull(patch); + System.out.println(patch); + assertEquals(5, patch.getDeltas().size()); + assertEquals( + "Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", + patch.toString()); + System.out.println(logdata); + assertEquals(11, logdata.size()); + } + + @Test + public void testPerformanceProblemsIssue124() { + List old = Arrays.asList("abcd"); + List newl = + IntStream.range(0, 90000).boxed().map(i -> i.toString()).collect(toList()); - @Override - public void diffEnd() { - logdata.add("end"); - } - })); - assertNotNull(patch); - System.out.println(patch); - assertEquals(5, patch.getDeltas().size()); - assertEquals("Patch{deltas=[[InsertDelta, position: 0, lines: [C]], [DeleteDelta, position: 0, lines: [A]], [DeleteDelta, position: 2, lines: [C]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}", patch.toString()); - System.out.println(logdata); - assertEquals(11, logdata.size()); - } - - - @Test - public void testPerformanceProblemsIssue124() { - List old = Arrays.asList("abcd"); - List newl = IntStream.range(0, 90000) - .boxed() - .map(i -> i.toString()) - .collect(toList()); - - long start = System.currentTimeMillis(); - Patch diff = DiffUtils.diff(old, newl, new MyersDiffWithLinearSpace()); - long end = System.currentTimeMillis(); - System.out.println("Finished in " + (end - start) + "ms and resulted " + diff.getDeltas().size() + " deltas"); - } + long start = System.currentTimeMillis(); + Patch diff = DiffUtils.diff(old, newl, new MyersDiffWithLinearSpace()); + long end = System.currentTimeMillis(); + System.out.println("Finished in " + (end - start) + "ms and resulted " + + diff.getDeltas().size() + " deltas"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java index 522eff58..92668ce8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/algorithm/myers/WithMyersDiffWithLinearSpacePatchTest.java @@ -1,10 +1,11 @@ package com.github.difflib.algorithm.myers; -import com.github.difflib.patch.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; +import com.github.difflib.DiffUtils; +import com.github.difflib.patch.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -17,378 +18,378 @@ import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; - import org.junit.jupiter.api.Test; -import com.github.difflib.DiffUtils; - public class WithMyersDiffWithLinearSpacePatchTest { - @Test - public void testPatch_Insert() { - final List insertTest_from = Arrays.asList("hhh"); - final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); - - final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @Test - public void testPatch_Delete() { - final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); - final List deleteTest_to = Arrays.asList("ggg"); - - final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @Test - public void testPatch_Change() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - // region testPatch_fuzzyApply utils - - private List intRange(int count) { - return IntStream.range(0, count) - .mapToObj(Integer::toString) - .collect(Collectors.toList()); - } - - @SafeVarargs - private final List join(List... lists) { - return Arrays.stream(lists).flatMap(Collection::stream).collect(Collectors.toList()); - } - - private static class FuzzyApplyTestPair { - public final List from; - public final List to; - public final int requiredFuzz; - - private FuzzyApplyTestPair(List from, List to, int requiredFuzz) { - this.from = from; - this.to = to; - this.requiredFuzz = requiredFuzz; - } - } - - // endregion - - @Test - public void fuzzyApply() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(6, deltaFrom), - new Chunk<>(6, deltaTo))); - - //noinspection unchecked - List[] moves = new List[] { - intRange(6), // no patch move - intRange(3), // forward patch move - intRange(9), // backward patch move - intRange(0), // apply to the first - }; - - for (FuzzyApplyTestPair pair : FUZZY_APPLY_TEST_PAIRS) { - for (List move : moves) { - List from = join(move, pair.from); - List to = join(move, pair.to); - - for (int i = 0; i < pair.requiredFuzz; i++) { - int maxFuzz = i; - assertThrows(PatchFailedException.class, () -> - patch.applyFuzzy(from, maxFuzz), - () -> "fail for " + from + " -> " + to + " for fuzz " + maxFuzz + " required " + pair.requiredFuzz); - } - for (int i = pair.requiredFuzz; i < 4; i++) { - int maxFuzz = i; - assertEquals(to, patch.applyFuzzy(from, maxFuzz), - () -> "with " + maxFuzz); - } - } - } - } - - @Test - public void fuzzyApplyTwoSideBySidePatches() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(0, deltaFrom), - new Chunk<>(0, deltaTo))); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(6, deltaFrom), - new Chunk<>(6, deltaTo))); - - - assertEquals(join(deltaTo, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom), 0)); - } - - @Test - public void fuzzyApplyToNearest() throws PatchFailedException { - Patch patch = new Patch<>(); - List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); - List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(0, deltaFrom), - new Chunk<>(0, deltaTo))); - patch.addDelta(new ChangeDelta<>( - new Chunk<>(10, deltaFrom), - new Chunk<>(10, deltaTo))); - - assertEquals(join(deltaTo, deltaFrom, deltaTo), - patch.applyFuzzy(join(deltaFrom, deltaFrom, deltaFrom), 0)); - assertEquals(join(intRange(1), deltaTo, deltaFrom, deltaTo), - patch.applyFuzzy(join(intRange(1), deltaFrom, deltaFrom, deltaFrom), 0)); - } - - @Test - public void testPatch_Serializable() throws IOException, ClassNotFoundException { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(baos); - out.writeObject(patch); - out.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bais); - Patch result = (Patch) in.readObject(); - in.close(); - - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - - } - - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); - - changeTest_from.set(2, "CDC"); - - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(11, data.size()); - - assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); - - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - static class FuzzyApplyTestDataGenerator { - private static String createList(List values) { - return values.stream() - .map(x -> '"' + x + '"') - .collect(Collectors.joining(", ", "Arrays.asList(", ")")); - } - - public static void main(String[] args) { - String[] deltaFrom = new String[] { "aaa", "bbb", "ccc", "ddd", "eee", "fff" }; - String[] deltaTo = new String[] { "aaa", "bbb", "cxc", "dxd", "eee", "fff" }; - - List pairs = new ArrayList<>(); - - // create test data. - // Brute-force search - String[] changedValue = new String[]{"axa", "bxb", "czc", "dzd", "exe", "fxf"}; - for (int i = 0; i < 1 << 6; i++) { - if ((i & 0b001100) != 0 && (i & 0b001100) != 0b001100) { - continue; - } - - String[] from = deltaFrom.clone(); - String[] to = deltaTo.clone(); - for (int j = 0; j < 6; j++) { - if ((i & (1 << j)) != 0) { - from[j] = changedValue[j]; - to[j] = changedValue[j]; - } - } - - int requiredFuzz; - if ((i & 0b001100) != 0) { - requiredFuzz = 3; - } else if ((i & 0b010010) != 0) { - requiredFuzz = 2; - } else if ((i & 0b100001) != 0) { - requiredFuzz = 1; - } else { - requiredFuzz = 0; - } - - pairs.add(new FuzzyApplyTestPair(Arrays.asList(from), Arrays.asList(to), requiredFuzz)); - } - pairs.sort(Comparator.comparingInt(a -> a.requiredFuzz)); - System.out.println("FuzzyApplyTestPair[] pairs = new FuzzyApplyTestPair[] {"); - for (FuzzyApplyTestPair pair : pairs) { - System.out.println(" new FuzzyApplyTestPair("); - System.out.println(" " + createList(pair.from) + ","); - System.out.println(" " + createList(pair.to) + ","); - System.out.println(" " + pair.requiredFuzz + "),"); - } - System.out.println("};"); - } - } - - private static final FuzzyApplyTestPair[] FUZZY_APPLY_TEST_PAIRS = new FuzzyApplyTestPair[] { - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"), - 0), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fff"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fxf"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fxf"), - 1), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fff"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fff"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fff"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fxf"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fxf"), - Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fxf"), - 2), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), - Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), - 3), - new FuzzyApplyTestPair( - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), - Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), - 3), - }; + @Test + public void testPatch_Insert() { + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = + DiffUtils.diff(insertTest_from, insertTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Delete() { + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = + DiffUtils.diff(deleteTest_from, deleteTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + // region testPatch_fuzzyApply utils + + private List intRange(int count) { + return IntStream.range(0, count).mapToObj(Integer::toString).collect(Collectors.toList()); + } + + @SafeVarargs + private final List join(List... lists) { + return Arrays.stream(lists).flatMap(Collection::stream).collect(Collectors.toList()); + } + + private static class FuzzyApplyTestPair { + public final List from; + public final List to; + public final int requiredFuzz; + + private FuzzyApplyTestPair(List from, List to, int requiredFuzz) { + this.from = from; + this.to = to; + this.requiredFuzz = requiredFuzz; + } + } + + // endregion + + @Test + public void fuzzyApply() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(6, deltaFrom), new Chunk<>(6, deltaTo))); + + //noinspection unchecked + List[] moves = new List[] { + intRange(6), // no patch move + intRange(3), // forward patch move + intRange(9), // backward patch move + intRange(0), // apply to the first + }; + + for (FuzzyApplyTestPair pair : FUZZY_APPLY_TEST_PAIRS) { + for (List move : moves) { + List from = join(move, pair.from); + List to = join(move, pair.to); + + for (int i = 0; i < pair.requiredFuzz; i++) { + int maxFuzz = i; + assertThrows( + PatchFailedException.class, + () -> patch.applyFuzzy(from, maxFuzz), + () -> "fail for " + from + " -> " + to + " for fuzz " + maxFuzz + " required " + + pair.requiredFuzz); + } + for (int i = pair.requiredFuzz; i < 4; i++) { + int maxFuzz = i; + assertEquals(to, patch.applyFuzzy(from, maxFuzz), () -> "with " + maxFuzz); + } + } + } + } + + @Test + public void fuzzyApplyTwoSideBySidePatches() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(0, deltaFrom), new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>(new Chunk<>(6, deltaFrom), new Chunk<>(6, deltaTo))); + + assertEquals(join(deltaTo, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom), 0)); + } + + @Test + public void fuzzyApplyToNearest() throws PatchFailedException { + Patch patch = new Patch<>(); + List deltaFrom = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"); + List deltaTo = Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"); + patch.addDelta(new ChangeDelta<>(new Chunk<>(0, deltaFrom), new Chunk<>(0, deltaTo))); + patch.addDelta(new ChangeDelta<>(new Chunk<>(10, deltaFrom), new Chunk<>(10, deltaTo))); + + assertEquals(join(deltaTo, deltaFrom, deltaTo), patch.applyFuzzy(join(deltaFrom, deltaFrom, deltaFrom), 0)); + assertEquals( + join(intRange(1), deltaTo, deltaFrom, deltaTo), + patch.applyFuzzy(join(intRange(1), deltaFrom, deltaFrom, deltaFrom), 0)); + } + + @Test + public void testPatch_Serializable() throws IOException, ClassNotFoundException { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = + DiffUtils.diff(changeTest_from, changeTest_to, new MyersDiffWithLinearSpace()); + + changeTest_from.set(2, "CDC"); + + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); + + assertEquals( + Arrays.asList( + "aaa", + "bxb", + "cxc", + "<<<<<< HEAD", + "bbb", + "CDC", + "======", + "bbb", + "ccc", + ">>>>>>> PATCH", + "ddd"), + data); + + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + static class FuzzyApplyTestDataGenerator { + private static String createList(List values) { + return values.stream().map(x -> '"' + x + '"').collect(Collectors.joining(", ", "Arrays.asList(", ")")); + } + + public static void main(String[] args) { + String[] deltaFrom = new String[] {"aaa", "bbb", "ccc", "ddd", "eee", "fff"}; + String[] deltaTo = new String[] {"aaa", "bbb", "cxc", "dxd", "eee", "fff"}; + + List pairs = new ArrayList<>(); + + // create test data. + // Brute-force search + String[] changedValue = new String[] {"axa", "bxb", "czc", "dzd", "exe", "fxf"}; + for (int i = 0; i < 1 << 6; i++) { + if ((i & 0b001100) != 0 && (i & 0b001100) != 0b001100) { + continue; + } + + String[] from = deltaFrom.clone(); + String[] to = deltaTo.clone(); + for (int j = 0; j < 6; j++) { + if ((i & (1 << j)) != 0) { + from[j] = changedValue[j]; + to[j] = changedValue[j]; + } + } + + int requiredFuzz; + if ((i & 0b001100) != 0) { + requiredFuzz = 3; + } else if ((i & 0b010010) != 0) { + requiredFuzz = 2; + } else if ((i & 0b100001) != 0) { + requiredFuzz = 1; + } else { + requiredFuzz = 0; + } + + pairs.add(new FuzzyApplyTestPair(Arrays.asList(from), Arrays.asList(to), requiredFuzz)); + } + pairs.sort(Comparator.comparingInt(a -> a.requiredFuzz)); + System.out.println("FuzzyApplyTestPair[] pairs = new FuzzyApplyTestPair[] {"); + for (FuzzyApplyTestPair pair : pairs) { + System.out.println(" new FuzzyApplyTestPair("); + System.out.println(" " + createList(pair.from) + ","); + System.out.println(" " + createList(pair.to) + ","); + System.out.println(" " + pair.requiredFuzz + "),"); + } + System.out.println("};"); + } + } + + private static final FuzzyApplyTestPair[] FUZZY_APPLY_TEST_PAIRS = new FuzzyApplyTestPair[] { + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fff"), + 0), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fff"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "eee", "fxf"), + 1), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fff"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fff"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "eee", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "ccc", "ddd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "cxc", "dxd", "exe", "fxf"), + 2), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fff"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "eee", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bbb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("aaa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + new FuzzyApplyTestPair( + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + Arrays.asList("axa", "bxb", "czc", "dzd", "exe", "fxf"), + 3), + }; } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java b/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java index 4eca7d36..48259260 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/ApplyPatch.java @@ -12,19 +12,19 @@ public class ApplyPatch { - private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "issue10_base.txt"; - private static final String PATCH = TestConstants.MOCK_FOLDER + "issue10_patch.txt"; + private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "issue10_base.txt"; + private static final String PATCH = TestConstants.MOCK_FOLDER + "issue10_patch.txt"; - public static void main(String[] args) throws PatchFailedException, IOException { - List original = Files.readAllLines(new File(ORIGINAL).toPath()); - List patched = Files.readAllLines(new File(PATCH).toPath()); + public static void main(String[] args) throws PatchFailedException, IOException { + List original = Files.readAllLines(new File(ORIGINAL).toPath()); + List patched = Files.readAllLines(new File(PATCH).toPath()); - // At first, parse the unified diff file and get the patch - Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patched); + // At first, parse the unified diff file and get the patch + Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patched); - // Then apply the computed patch to the given text - List result = DiffUtils.patch(original, patch); - System.out.println(result); - // / Or we can call patch.applyTo(original). There is no difference. - } + // Then apply the computed patch to the given text + List result = DiffUtils.patch(original, patch); + System.out.println(result); + // / Or we can call patch.applyTo(original). There is no difference. + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java index e8b25437..ffae731b 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/ComputeDifference.java @@ -11,18 +11,18 @@ public class ComputeDifference { - private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "original.txt"; - private static final String REVISED = TestConstants.MOCK_FOLDER + "revised.txt"; + private static final String ORIGINAL = TestConstants.MOCK_FOLDER + "original.txt"; + private static final String REVISED = TestConstants.MOCK_FOLDER + "revised.txt"; - public static void main(String[] args) throws IOException { - List original = Files.readAllLines(new File(ORIGINAL).toPath()); - List revised = Files.readAllLines(new File(REVISED).toPath()); + public static void main(String[] args) throws IOException { + List original = Files.readAllLines(new File(ORIGINAL).toPath()); + List revised = Files.readAllLines(new File(REVISED).toPath()); - // Compute diff. Get the Patch object. Patch is the container for computed deltas. - Patch patch = DiffUtils.diff(original, revised); + // Compute diff. Get the Patch object. Patch is the container for computed deltas. + Patch patch = DiffUtils.diff(original, revised); - for (AbstractDelta delta : patch.getDeltas()) { - System.out.println(delta); - } - } + for (AbstractDelta delta : patch.getDeltas()) { + System.out.println(delta); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java index 17283b4d..a3f0b7b9 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/examples/OriginalAndDiffTest.java @@ -1,59 +1,58 @@ package com.github.difflib.examples; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.TestConstants; import com.github.difflib.UnifiedDiffUtils; -import org.junit.jupiter.api.Test; - import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; - -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; public class OriginalAndDiffTest { - @Test - public void testGenerateOriginalAndDiff() { - List origLines = null; - List revLines = null; - try { - origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); - revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); - } catch (IOException e) { - fail(e.getMessage()); - } - - List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); - System.out.println(originalAndDiff.stream().collect(joining("\n"))); - } - - @Test - public void testGenerateOriginalAndDiffFirstLineChange() { - List origLines = null; - List revLines = null; - try { - origLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_original.txt"); - revLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_revised.txt"); - } catch (IOException e) { - fail(e.getMessage()); - } - - List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); - System.out.println(originalAndDiff.stream().collect(joining("\n"))); - } - - public static List fileToLines(String filename) throws FileNotFoundException, IOException { - List lines = new ArrayList<>(); - String line = ""; - try (BufferedReader in = new BufferedReader(new FileReader(filename))) { - while ((line = in.readLine()) != null) { - lines.add(line); - } - } - return lines; - } + @Test + public void testGenerateOriginalAndDiff() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + + @Test + public void testGenerateOriginalAndDiffFirstLineChange() { + List origLines = null; + List revLines = null; + try { + origLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_original.txt"); + revLines = fileToLines(TestConstants.MOCK_FOLDER + "issue_170_revised.txt"); + } catch (IOException e) { + fail(e.getMessage()); + } + + List originalAndDiff = UnifiedDiffUtils.generateOriginalAndDiff(origLines, revLines); + System.out.println(originalAndDiff.stream().collect(joining("\n"))); + } + + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java index 4816f221..46eb727b 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/ChunkTest.java @@ -1,43 +1,37 @@ package com.github.difflib.patch; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; class ChunkTest { - @Test - void verifyChunk() throws PatchFailedException { - Chunk chunk = new Chunk<>(7, toCharList("test")); - - // normal check - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix test suffix"))); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix es suffix"), 0, 7)); - - // position - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("short test suffix"), 0, 6)); - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("loonger test suffix"), 0, 8)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix test suffix"), 0, 6)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix test suffix"), 0, 8)); - - // fuzz - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix test suffix"), 1, 7)); - assertEquals(VerifyChunk.OK, - chunk.verifyChunk(toCharList("prefix es suffix"), 1, 7)); - assertEquals(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, - chunk.verifyChunk(toCharList("prefix suffix"), 1, 7)); - } - - private List toCharList(String str) { - return str.chars().mapToObj(x -> (char) x).collect(Collectors.toList()); - } + @Test + void verifyChunk() throws PatchFailedException { + Chunk chunk = new Chunk<>(7, toCharList("test")); + + // normal check + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix test suffix"))); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix es suffix"), 0, 7)); + + // position + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("short test suffix"), 0, 6)); + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("loonger test suffix"), 0, 8)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix test suffix"), 0, 6)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix test suffix"), 0, 8)); + + // fuzz + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix test suffix"), 1, 7)); + assertEquals(VerifyChunk.OK, chunk.verifyChunk(toCharList("prefix es suffix"), 1, 7)); + assertEquals( + VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, chunk.verifyChunk(toCharList("prefix suffix"), 1, 7)); + } + + private List toCharList(String str) { + return str.chars().mapToObj(x -> (char) x).collect(Collectors.toList()); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java index a9731514..a14ba520 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithAllDiffAlgorithmsTest.java @@ -3,6 +3,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import com.github.difflib.DiffUtils; +import com.github.difflib.algorithm.DiffAlgorithmFactory; +import com.github.difflib.algorithm.myers.MyersDiff; +import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -10,12 +14,6 @@ import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.List; - - -import com.github.difflib.DiffUtils; -import com.github.difflib.algorithm.DiffAlgorithmFactory; -import com.github.difflib.algorithm.myers.MyersDiff; -import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.params.ParameterizedTest; @@ -24,87 +22,85 @@ public class PatchWithAllDiffAlgorithmsTest { - private static Stream provideAlgorithms() { - return Stream.of(Arguments.of(MyersDiff.factory()), - Arguments.of(MyersDiffWithLinearSpace.factory())); - } - - @AfterAll - public static void afterAll() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Insert(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List insertTest_from = Arrays.asList("hhh"); - final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); - - final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); - try { - assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Delete(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); - final List deleteTest_to = Arrays.asList("ggg"); - - final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); - try { - assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Change(DiffAlgorithmFactory factory) { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - @ParameterizedTest - @MethodSource("provideAlgorithms") - public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { - DiffUtils.withDefaultDiffAlgorithmFactory(factory); - - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(baos); - out.writeObject(patch); - out.close(); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - ObjectInputStream in = new ObjectInputStream(bais); - Patch result = (Patch) in.readObject(); - in.close(); - - try { - assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - - } + private static Stream provideAlgorithms() { + return Stream.of(Arguments.of(MyersDiff.factory()), Arguments.of(MyersDiffWithLinearSpace.factory())); + } + + @AfterAll + public static void afterAll() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Insert(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List insertTest_from = Arrays.asList("hhh"); + final List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + final Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); + try { + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Delete(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + final List deleteTest_to = Arrays.asList("ggg"); + + final Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); + try { + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Change(DiffAlgorithmFactory factory) { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + @ParameterizedTest + @MethodSource("provideAlgorithms") + public void testPatch_Serializable(DiffAlgorithmFactory factory) throws IOException, ClassNotFoundException { + DiffUtils.withDefaultDiffAlgorithmFactory(factory); + + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(baos); + out.writeObject(patch); + out.close(); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ObjectInputStream in = new ObjectInputStream(bais); + Patch result = (Patch) in.readObject(); + in.close(); + + try { + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, result)); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java index 97dc20fb..0dab69f3 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffTest.java @@ -15,13 +15,14 @@ */ package com.github.difflib.patch; -import com.github.difflib.DiffUtils; import static com.github.difflib.patch.Patch.CONFLICT_PRODUCES_MERGE_CONFLICT; -import java.util.Arrays; -import java.util.List; import static java.util.stream.Collectors.joining; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; + +import com.github.difflib.DiffUtils; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.Test; /** @@ -30,39 +31,40 @@ */ public class PatchWithMyerDiffTest { - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - changeTest_from.set(2, "CDC"); + changeTest_from.set(2, "CDC"); - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(9, data.size()); + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(9, data.size()); - assertEquals(Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals( + Arrays.asList("aaa", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), + data); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } - @Test - public void testPatchThreeWayIssue138() throws PatchFailedException { - List base = Arrays.asList("Imagine there's no heaven".split("\\s+")); - List left = Arrays.asList("Imagine there's no HEAVEN".split("\\s+")); - List right = Arrays.asList("IMAGINE there's no heaven".split("\\s+")); + @Test + public void testPatchThreeWayIssue138() throws PatchFailedException { + List base = Arrays.asList("Imagine there's no heaven".split("\\s+")); + List left = Arrays.asList("Imagine there's no HEAVEN".split("\\s+")); + List right = Arrays.asList("IMAGINE there's no heaven".split("\\s+")); - Patch rightPatch = DiffUtils.diff(base, right) - .withConflictOutput(CONFLICT_PRODUCES_MERGE_CONFLICT); + Patch rightPatch = DiffUtils.diff(base, right).withConflictOutput(CONFLICT_PRODUCES_MERGE_CONFLICT); - List applied = rightPatch.applyTo(left); + List applied = rightPatch.applyTo(left); - assertEquals("IMAGINE there's no HEAVEN", applied.stream().collect(joining(" "))); - } + assertEquals("IMAGINE there's no HEAVEN", applied.stream().collect(joining(" "))); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java index 2cc334ad..f04a1c93 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/patch/PatchWithMyerDiffWithLinearSpaceTest.java @@ -15,14 +15,15 @@ */ package com.github.difflib.patch; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.myers.MyersDiff; import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.AfterAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -32,35 +33,48 @@ */ public class PatchWithMyerDiffWithLinearSpaceTest { - @BeforeAll - public static void setupClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.factory()); - } + @BeforeAll + public static void setupClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiffWithLinearSpace.factory()); + } - @AfterAll - public static void resetClass() { - DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); - } + @AfterAll + public static void resetClass() { + DiffUtils.withDefaultDiffAlgorithmFactory(MyersDiff.factory()); + } - @Test - public void testPatch_Change_withExceptionProcessor() { - final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); - final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + @Test + public void testPatch_Change_withExceptionProcessor() { + final List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + final List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); - final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); - changeTest_from.set(2, "CDC"); + changeTest_from.set(2, "CDC"); - patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); + patch.withConflictOutput(Patch.CONFLICT_PRODUCES_MERGE_CONFLICT); - try { - List data = DiffUtils.patch(changeTest_from, patch); - assertEquals(11, data.size()); + try { + List data = DiffUtils.patch(changeTest_from, patch); + assertEquals(11, data.size()); - assertEquals(Arrays.asList("aaa", "bxb", "cxc", "<<<<<< HEAD", "bbb", "CDC", "======", "bbb", "ccc", ">>>>>>> PATCH", "ddd"), data); + assertEquals( + Arrays.asList( + "aaa", + "bxb", + "cxc", + "<<<<<< HEAD", + "bbb", + "CDC", + "======", + "bbb", + "ccc", + ">>>>>>> PATCH", + "ddd"), + data); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java new file mode 100644 index 00000000..c71f455b --- /dev/null +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorEqualitiesTest.java @@ -0,0 +1,92 @@ +package com.github.difflib.text; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class DiffRowGeneratorEqualitiesTest { + + @Test + public void testDefaultEqualityProcessingLeavesTextUnchanged() { + DiffRowGenerator generator = + DiffRowGenerator.create().showInlineDiffs(false).build(); + + List rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello world")); + + assertEquals(1, rows.size()); + assertEquals("hello world", rows.get(0).getOldLine()); + assertEquals("hello world", rows.get(0).getNewLine()); + assertEquals(DiffRow.Tag.EQUAL, rows.get(0).getTag()); + } + + @Test + public void testCustomEqualityProcessingIsApplied() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .processEqualities(text -> "[" + text + "]") + .build(); + + List rows = generator.generateDiffRows(Arrays.asList("A", "B"), Arrays.asList("A", "B")); + + assertEquals(2, rows.size()); + assertEquals("[A]", rows.get(0).getOldLine()); + assertEquals("[B]", rows.get(1).getOldLine()); + } + + /** + * Verifies that processEqualities can be used to HTML-escape unchanged + * lines while still working together with the default HTML-oriented + * lineNormalizer. + */ + @Test + public void testHtmlEscapingEqualitiesWorksWithDefaultNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .processEqualities(s -> s.replace("<", "<").replace(">", ">")) + .build(); + + // both lines are equal -> Tag.EQUAL, processEqualities is invoked + List rows = generator.generateDiffRows(Arrays.asList("hello "), Arrays.asList("hello ")); + + DiffRow row = rows.get(0); + + assertTrue(row.getOldLine().contains("<world>")); + assertTrue(row.getNewLine().contains("<world>")); + } + + /** + * Ensures equalities are processed while inline diff markup is still + * present somewhere in the line. + */ + @Test + public void testEqualitiesProcessedButInlineDiffStillPresent() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .processEqualities(s -> "(" + s + ")") + .build(); + + List rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello there")); + + DiffRow row = rows.get(0); + + System.out.println("OLD = " + row.getOldLine()); + System.out.println("NEW = " + row.getNewLine()); + + // Row must be CHANGE + assertEquals(DiffRow.Tag.CHANGE, row.getTag()); + + // Inline diff markup must appear + assertTrue( + row.getOldLine().contains("span") || row.getNewLine().contains("span"), + "Expected inline diff markup in old or new line"); + + // Equalities inside CHANGE row must NOT be wrapped by processEqualities + // Option 3 does NOT modify inline equalities + assertTrue(row.getOldLine().startsWith("hello "), "Equal (unchanged) inline segment should remain unchanged"); + } +} diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index f881c474..3b1120c4 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -1,7 +1,16 @@ package com.github.difflib.text; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace; +import com.github.difflib.patch.AbstractDelta; +import com.github.difflib.text.deltamerge.DeltaMergeUtils; +import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; @@ -12,806 +21,884 @@ 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 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 testIssue188HangOnExamples() throws IOException, URISyntaxException { - try (FileSystem zipFs = FileSystems.newFileSystem(Paths.get("target/test-classes/com/github/difflib/text/test.zip"), null);) { - List original = Files.readAllLines(zipFs.getPath("old.html")); - List revised = Files.readAllLines(zipFs.getPath("new.html")); - - DiffRowGenerator generator = DiffRowGenerator.create() - .lineNormalizer(line -> line) - .showInlineDiffs(true) - .mergeOriginalRevised(true) - .inlineDiffByWord(true) - .decompressDeltas(true) - .oldTag(f -> f ? "" : "") - .newTag(f -> f ? "" : "") - .build(); - - //List rows = generator.generateDiffRows(original, revised); - List rows = generator.generateDiffRows(original, DiffUtils.diff(original, revised, new MyersDiffWithLinearSpace<>() )); - - System.out.println(rows); - } - } + @Test + public void testGenerator_Default() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + } + + /** + * Test of normalize method, of class StringUtils. + */ + @Test + public void testNormalize_List() { + DiffRowGenerator generator = DiffRowGenerator.create().build(); + assertEquals( + Collections.singletonList(" test"), generator.normalizeLines(Collections.singletonList("\ttest"))); + } + + @Test + public void testGenerator_Default2() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(0) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + } + + @Test + public void testGenerator_InlineDiff() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertTrue(rows.get(0).getOldLine().indexOf(" 0); + } + + @Test + public void testGenerator_IgnoreWhitespaces() { + String first = "anything \n \nother\nmore lines"; + String second = "anything\n\nother\nsome more lines"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .ignoreWhiteSpaces(true) + .columnWidth(Integer.MAX_VALUE) // do not wrap + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(4, rows.size()); + assertEquals(rows.get(0).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(1).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(2).getTag(), DiffRow.Tag.EQUAL); + assertEquals(rows.get(3).getTag(), DiffRow.Tag.CHANGE); + } + + private List split(String content) { + return Arrays.asList(content.split("\n")); + } + + private void print(List diffRows) { + for (DiffRow row : diffRows) { + System.out.println(row); + } + } + + @Test + public void testGeneratorWithWordWrap() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create().columnWidth(5).build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals("[CHANGE,anyth
ing ,anyth
ing]", rows.get(0).toString()); + assertEquals("[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorWithMerge() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals( + "[CHANGE,anything ,anything]", + rows.get(0).toString()); + assertEquals( + "[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorWithMerge2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Tester,ester]", + rows.get(0).toString()); + } + + @Test + public void testGeneratorWithMerge3() { + String first = "test\nanything \n \nother"; + String second = "anything\n\nother\ntest\ntest2"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(6, rows.size()); + assertEquals( + "[CHANGE,test,anything]", + rows.get(0).toString()); + assertEquals( + "[CHANGE,anything ,]", + rows.get(1).toString()); + assertEquals( + "[DELETE, ,]", rows.get(2).toString()); + assertEquals("[EQUAL,other,other]", rows.get(3).toString()); + assertEquals( + "[INSERT,test,test]", + rows.get(4).toString()); + assertEquals( + "[INSERT,test2,test2]", + rows.get(5).toString()); + } + + @Test + public void testGeneratorWithMergeByWord4() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("Test"), Arrays.asList("ester")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Testester,ester]", + rows.get(0).toString()); + } + + @Test + public void testGeneratorWithMergeByWord5() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .columnWidth(80) + .build(); + List rows = + generator.generateDiffRows(Arrays.asList("Test feature"), Arrays.asList("ester feature best")); + print(rows); + + assertEquals(1, rows.size()); + assertEquals( + "[CHANGE,Testester
feature best,ester feature best]", + rows.get(0).toString()); + } + + @Test + public void testSplitString() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test,test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + assertEquals(3, list.size()); + assertEquals("[test, ,, test2]", list.toString()); + } + + @Test + public void testSplitString2() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test , test2", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + System.out.println(list); + assertEquals(5, list.size()); + assertEquals("[test, , ,, , test2]", list.toString()); + } + + @Test + public void testSplitString3() { + List list = + DiffRowGenerator.splitStringPreserveDelimiter("test,test2,", DiffRowGenerator.SPLIT_BY_WORD_PATTERN); + System.out.println(list); + assertEquals(4, list.size()); + assertEquals("[test, ,, test2, ,]", list.toString()); + } + + @Test + public void testGeneratorExample1() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence."), Arrays.asList("This is a test for diffutils.")); + + System.out.println(rows.get(0).getOldLine()); + + assertEquals(1, rows.size()); + assertEquals("This is a test ~senctence~**for diffutils**.", rows.get(0).getOldLine()); + } + + @Test + public void testGeneratorExample2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."), + Arrays.asList("This is a test for diffutils.", "This is the second line.")); + + System.out.println("|original|new|"); + System.out.println("|--------|---|"); + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); + } + + assertEquals(3, rows.size()); + assertEquals("This is a test ~senctence~.", rows.get(0).getOldLine()); + assertEquals("This is a test **for diffutils**.", rows.get(0).getNewLine()); + } + + @Test + public void testGeneratorUnchanged() { + String first = "anything \n \nother"; + String second = "anything\n\nother"; + + DiffRowGenerator generator = DiffRowGenerator.create() + .columnWidth(5) + .reportLinesUnchanged(true) + .build(); + List rows = generator.generateDiffRows(split(first), split(second)); + print(rows); + + assertEquals(3, rows.size()); + assertEquals("[CHANGE,anything ,anything]", rows.get(0).toString()); + assertEquals("[CHANGE, ,]", rows.get(1).toString()); + assertEquals("[EQUAL,other,other]", rows.get(2).toString()); + } + + @Test + public void testGeneratorIssue14() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffBySplitter(line -> DiffRowGenerator.splitStringPreserveDelimiter(line, Pattern.compile(","))) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows( + Arrays.asList("J. G. Feldstein, Chair"), Arrays.asList("T. P. Pastor, Chair")); + + System.out.println(rows.get(0).getOldLine()); + + assertEquals(1, rows.size()); + assertEquals("~J. G. Feldstein~**T. P. Pastor**, Chair", rows.get(0).getOldLine()); + } + + @Test + public void testGeneratorIssue15() throws IOException { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) // show the ~ ~ and ** ** symbols on each difference + .inlineDiffByWord(true) // show the ~ ~ and ** ** around each different word instead of each letter + // .reportLinesUnchanged(true) //experiment + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + + List listOne = Files.lines(new File("target/test-classes/mocks/issue15_1.txt").toPath()) + .collect(toList()); + + List listTwo = Files.lines(new File("target/test-classes/mocks/issue15_2.txt").toPath()) + .collect(toList()); + + List rows = generator.generateDiffRows(listOne, listTwo); + + assertEquals(9, rows.size()); + + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "| " + row.getNewLine() + " |"); + if (!row.getOldLine().startsWith("TABLE_NAME")) { + assertTrue(row.getNewLine().startsWith("**ACTIONS_C16913**")); + assertTrue(row.getOldLine().startsWith("~ACTIONS_C1700")); + } + } + } + + @Test + public void testGeneratorIssue22() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test senctence."; + String bb = "This is a test for diffutils.\nThis is the second line."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**]]", + rows.toString()); + + System.out.println("|original|new|"); + System.out.println("|--------|---|"); + for (DiffRow row : rows) { + System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|"); + } + } + + @Test + public void testGeneratorIssue22_2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test for diffutils.\nThis is the second line."; + String bb = "This is a test senctence."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~for diffutils~.,This is a test **senctence**.], [DELETE,~This is the second line.~,]]", + rows.toString()); + } + + @Test + public void testGeneratorIssue22_3() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + String aa = "This is a test senctence."; + String bb = "This is a test for diffutils.\nThis is the second line.\nAnd one more."; + List rows = generator.generateDiffRows(Arrays.asList(aa.split("\n")), Arrays.asList(bb.split("\n"))); + + assertEquals( + "[[CHANGE,This is a test ~senctence~.,This is a test **for diffutils**.], [INSERT,,**This is the second line.**], [INSERT,,**And one more.**]]", + rows.toString()); + } + + @Test + public void testGeneratorIssue41DefaultNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create().build(); + List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); + assertEquals("[[EQUAL,<,<]]", rows.toString()); + } + + @Test + public void testGeneratorIssue41UserNormalizer() { + DiffRowGenerator generator = DiffRowGenerator.create() + .lineNormalizer(str -> str.replace("\t", " ")) + .build(); + List rows = generator.generateDiffRows(Arrays.asList("<"), Arrays.asList("<")); + assertEquals("[[EQUAL,<,<]]", rows.toString()); + rows = generator.generateDiffRows(Arrays.asList("\t<"), Arrays.asList("<")); + assertEquals("[[CHANGE, <,<]]", rows.toString()); + } + + @Test + public void testGenerationIssue44reportLinesUnchangedProblem() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList("
To do
"), Arrays.asList("
Done
")); + assertEquals("[[CHANGE,
~~T~~o~~ do~~
,
**D**o**ne**
]]", rows.toString()); + } + + @Test + public void testIgnoreWhitespaceIssue66() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + // CHECKSTYLE:OFF + List rows = + generator.generateDiffRows(Arrays.asList("This\tis\ta\ttest."), Arrays.asList("This is a test")); + // CHECKSTYLE:ON + + assertEquals("This is a test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testIgnoreWhitespaceIssue66_2() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + List rows = + generator.generateDiffRows(Arrays.asList("This is a test."), Arrays.asList("This is a test")); + + assertEquals("This is a test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testIgnoreWhitespaceIssue64() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .build(); + + // compute the differences for two test texts. + List rows = generator.generateDiffRows( + Arrays.asList("test\n\ntestline".split("\n")), + Arrays.asList("A new text line\n\nanother one".split("\n"))); + + assertThat(rows) + .extracting(item -> item.getOldLine()) + .containsExactly("~test~**A new text line**", "", "~testline~**another one**"); + } + + @Test + public void testReplaceDiffsIssue63() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .mergeOriginalRevised(true) + .oldTag(f -> "~") // introduce markdown style for strikethrough + .newTag(f -> "**") // introduce markdown style for bold + .processDiffs(str -> str.replace(" ", "/")) + .build(); + + // compute the differences for two test texts. + List rows = + generator.generateDiffRows(Arrays.asList("This is a test."), Arrays.asList("This is a test")); + + assertEquals("This~//~**/**is~//~**/**a~//~**/**test~.~", rows.get(0).getOldLine()); + } + + @Test + public void testProblemTooManyDiffRowsIssue65() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(false) + .replaceOriginalLinefeedInChangesWithSpaces(true) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + print(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_NoMerge() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(false) + .inlineDiffByWord(false) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_DiffByWord() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testProblemTooManyDiffRowsIssue65_NoInlineDiff() { + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .reportLinesUnchanged(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .mergeOriginalRevised(true) + .inlineDiffByWord(false) + .build(); + + List diffRows = generator.generateDiffRows( + Arrays.asList("Ich möchte nicht mit einem Bot sprechen.", "Ich soll das schon wieder wiederholen?"), + Arrays.asList( + "Ich möchte nicht mehr mit dir sprechen. Leite mich weiter.", + "Kannst du mich zum Kundendienst weiterleiten?")); + + System.out.println(diffRows); + + assertThat(diffRows).hasSize(2); + } + + @Test + public void testLinefeedInStandardTagsWithLineWidthIssue81() { + List original = + Arrays.asList(("American bobtail jaguar. American bobtail bombay but turkish angora and tomcat.\n" + + "Russian blue leopard. Lion. Tabby scottish fold for russian blue, so savannah yet lynx. Tomcat singapura, cheetah.\n" + + "Bengal tiger panther but singapura but bombay munchkin for cougar.") + .split("\n")); + List revised = Arrays.asList(("bobtail jaguar. American bobtail turkish angora and tomcat.\n" + + "Russian blue leopard. Lion. Tabby scottish folded for russian blue, so savannah yettie? lynx. Tomcat singapura, cheetah.\n" + + "Bengal tiger panther but singapura but bombay munchkin for cougar. And more.") + .split("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .ignoreWhiteSpaces(true) + .columnWidth(100) + .build(); + List deltas = generator.generateDiffRows(original, revised); + + System.out.println(deltas); + } + + @Test + public void testIssue86WrongInlineDiff() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_original.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_86_revised.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testCorrectChangeIssue114() throws IOException { + List original = Arrays.asList("A", "B", "C", "D", "E"); + List revised = Arrays.asList("a", "C", "", "E"); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(false) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows(original, revised); + + for (DiffRow diff : rows) { + System.out.println(diff); + } + + assertThat(rows) + .extracting(item -> item.getTag().name()) + .containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); + } + + @Test + public void testCorrectChangeIssue114_2() throws IOException { + List original = Arrays.asList("A", "B", "C", "D", "E"); + List revised = Arrays.asList("a", "C", "", "E"); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = generator.generateDiffRows(original, revised); + + for (DiffRow diff : rows) { + System.out.println(diff); + } + + assertThat(rows) + .extracting(item -> item.getTag().name()) + .containsExactly("CHANGE", "DELETE", "EQUAL", "CHANGE", "EQUAL"); + assertThat(rows.get(1).toString()).isEqualTo("[DELETE,~B~,]"); + } + + @Test + public void testIssue119WrongContextLength() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_original.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue_119_revised.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testIssue129WithDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = DiffRowGenerator.create() + .showInlineDiffs(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); + // .forEachOrdered(row -> { + // System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, + // row.getTag(), row.getOldLine(), row.getNewLine()); + // }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL"); + } + + @Test + public void testIssue129SkipDeltaDecompression() { + List lines1 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein abc to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + List lines2 = Arrays.asList( + "apple1", + "apple2", + "apple3", + "A man named Frankenstein", + "xyz", + "to Switzerland for cookies!", + "banana1", + "banana2", + "banana3"); + int[] entry = {1}; + String txt = DiffRowGenerator.create() + .showInlineDiffs(true) + .decompressDeltas(false) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build() + .generateDiffRows(lines1, lines2) + .stream() + .map(row -> row.getTag().toString()) + .collect(joining(" ")); + // .forEachOrdered(row -> { + // System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++, + // row.getTag(), row.getOldLine(), row.getNewLine()); + // }); + + assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL"); + } + + @Test + public void testIssue129SkipWhitespaceChanges() throws IOException { + String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")) + .collect(joining("\n")); + String revised = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_2.txt")) + .collect(joining("\n")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .ignoreWhiteSpaces(true) + .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==") + .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==") + .build(); + List rows = + generator.generateDiffRows(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n"))); + + assertThat(rows).hasSize(13); + + rows.stream().filter(item -> item.getTag() != DiffRow.Tag.EQUAL).forEach(System.out::println); + } + + @Test + public void testGeneratorWithWhitespaceDeltaMerge() { + final DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") // + .lineNormalizer(StringUtils::htmlEntites) // do not replace tabs + .inlineDeltaMerger(DiffRowGenerator.WHITESPACE_EQUALITIES_MERGER) + .build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult( + generator, + " x whitespace before diff", + " y whitespace before diff", + " ~x~**y** whitespace before diff"); + assertInlineDiffResult( + generator, "Whitespace after diff x ", "Whitespace after diff y ", "Whitespace after diff ~x~**y** "); + assertInlineDiffResult(generator, "Diff x x between", "Diff y y between", "Diff ~x x~**y y** between"); + assertInlineDiffResult(generator, "Hello \t world", "Hi \t universe", "~Hello \t world~**Hi \t universe**"); + assertInlineDiffResult( + generator, + "The quick brown fox jumps over the lazy dog", + "A lazy dog jumps over a fox", + "~The quick brown fox ~**A lazy dog **jumps over ~the lazy dog~**a fox**"); + } + + @Test + public void testGeneratorWithMergingDeltasForShortEqualities() { + final Function>> shortEqualitiesMerger = + deltaMergeInfo -> DeltaMergeUtils.mergeInlineDeltas( + deltaMergeInfo, + equalities -> + equalities.stream().mapToInt(String::length).sum() < 6); + + final DiffRowGenerator generator = DiffRowGenerator.create() + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .inlineDeltaMerger(shortEqualitiesMerger) + .build(); + + assertInlineDiffResult(generator, "No diff", "No diff", "No diff"); + assertInlineDiffResult(generator, "aaa bbb ccc", "xxx bbb zzz", "~aaa bbb ccc~**xxx bbb zzz**"); + assertInlineDiffResult(generator, "aaa bbbb ccc", "xxx bbbb zzz", "~aaa~**xxx** bbbb ~ccc~**zzz**"); + } + + private void assertInlineDiffResult(DiffRowGenerator generator, String original, String revised, String expected) { + final List rows = generator.generateDiffRows(Arrays.asList(original), Arrays.asList(revised)); + print(rows); + + assertEquals(1, rows.size()); + assertEquals(expected, rows.get(0).getOldLine().toString()); + } + + @Test + public void testIssue188HangOnExamples() throws IOException, URISyntaxException { + try (FileSystem zipFs = FileSystems.newFileSystem( + Paths.get("target/test-classes/com/github/difflib/text/test.zip"), (ClassLoader) null); ) { + List original = Files.readAllLines(zipFs.getPath("old.html")); + List revised = Files.readAllLines(zipFs.getPath("new.html")); + + DiffRowGenerator generator = DiffRowGenerator.create() + .lineNormalizer(line -> line) + .showInlineDiffs(true) + .mergeOriginalRevised(true) + .inlineDiffByWord(true) + .decompressDeltas(true) + .oldTag(f -> f ? "" : "") + .newTag(f -> f ? "" : "") + .build(); + + // List rows = generator.generateDiffRows(original, revised); + List rows = generator.generateDiffRows( + original, DiffUtils.diff(original, revised, new MyersDiffWithLinearSpace<>())); + + System.out.println(rows); + } + } } - diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java index 68670723..b2e06de0 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/text/StringUtilsTest.java @@ -15,8 +15,9 @@ */ package com.github.difflib.text; -import org.junit.jupiter.api.Assertions; import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; /** @@ -25,38 +26,36 @@ */ public class StringUtilsTest { - /** - * Test of htmlEntites method, of class StringUtils. - */ - @Test - public void testHtmlEntites() { - assertEquals("<test>", StringUtils.htmlEntites("")); - } - - /** - * Test of normalize method, of class StringUtils. - */ - @Test - public void testNormalize_String() { - assertEquals(" test", StringUtils.normalize("\ttest")); - } + /** + * Test of htmlEntites method, of class StringUtils. + */ + @Test + public void testHtmlEntites() { + assertEquals("<test>", StringUtils.htmlEntites("")); + } - /** - * Test of wrapText method, of class StringUtils. - */ - @Test - public void testWrapText_String_int() { - assertEquals("te
st", StringUtils.wrapText("test", 2)); - assertEquals("tes
t", StringUtils.wrapText("test", 3)); - assertEquals("test", StringUtils.wrapText("test", 10)); - assertEquals(".\uD800\uDC01
.", StringUtils.wrapText(".\uD800\uDC01.", 2)); - assertEquals("..
\uD800\uDC01", StringUtils.wrapText("..\uD800\uDC01", 3)); - } + /** + * Test of normalize method, of class StringUtils. + */ + @Test + public void testNormalize_String() { + assertEquals(" test", StringUtils.normalize("\ttest")); + } - @Test - public void testWrapText_String_int_zero() { - Assertions.assertThrows(IllegalArgumentException.class, - () -> StringUtils.wrapText("test", -1)); - } + /** + * Test of wrapText method, of class StringUtils. + */ + @Test + public void testWrapText_String_int() { + assertEquals("te
st", StringUtils.wrapText("test", 2)); + assertEquals("tes
t", StringUtils.wrapText("test", 3)); + assertEquals("test", StringUtils.wrapText("test", 10)); + assertEquals(".\uD800\uDC01
.", StringUtils.wrapText(".\uD800\uDC01.", 2)); + assertEquals("..
\uD800\uDC01", StringUtils.wrapText("..\uD800\uDC01", 3)); + } + @Test + public void testWrapText_String_int_zero() { + Assertions.assertThrows(IllegalArgumentException.class, () -> StringUtils.wrapText("test", -1)); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java index 3d851561..fc272edb 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffReaderTest.java @@ -15,14 +15,15 @@ */ package com.github.difflib.unifieddiff; -import com.github.difflib.patch.AbstractDelta; -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.github.difflib.patch.AbstractDelta; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.jupiter.api.Test; /** @@ -31,419 +32,475 @@ */ public class UnifiedDiffReaderTest { - @Test - public void testSimpleParse() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + @Test + public void testSimpleParse() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } + + @Test + public void testParseDiffBlock() { + String[] files = UnifiedDiffReader.parseFileNames( + "diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); + assertThat(files) + .containsExactly( + "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java", + "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); + } + + @Test + public void testChunkHeaderParsing() { + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher( + "@@ -189,6 +189,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */"); + + assertTrue(matcher.find()); + assertEquals("189", matcher.group(1)); + assertEquals("189", matcher.group(3)); + } - System.out.println(diff); + @Test + public void testChunkHeaderParsing2() { + // "^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@"); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertTrue(matcher.find()); + assertEquals("189", matcher.group(1)); + assertEquals("189", matcher.group(3)); + } - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + @Test + public void testChunkHeaderParsing3() { + // "^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" + Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; + Matcher matcher = pattern.matcher("@@ -1,27 +1,27 @@"); - assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); - } + assertTrue(matcher.find()); + assertEquals("1", matcher.group(1)); + assertEquals("1", matcher.group(3)); + } - @Test - public void testParseDiffBlock() { - String[] files = UnifiedDiffReader.parseFileNames("diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); - assertThat(files).containsExactly("src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java", "src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java"); - } + @Test + public void testSimpleParse2() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); - @Test - public void testChunkHeaderParsing() { - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */"); + System.out.println(diff); - assertTrue(matcher.find()); - assertEquals("189", matcher.group(1)); - assertEquals("189", matcher.group(3)); - } + assertThat(diff.getFiles().size()).isEqualTo(2); - @Test - public void testChunkHeaderParsing2() { - //"^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -189,6 +189,7 @@"); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); - assertTrue(matcher.find()); - assertEquals("189", matcher.group(1)); - assertEquals("189", matcher.group(3)); - } + AbstractDelta first = file1.getPatch().getDeltas().get(0); - @Test - public void testChunkHeaderParsing3() { - //"^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@.*$" - Pattern pattern = UnifiedDiffReader.UNIFIED_DIFF_CHUNK_REGEXP; - Matcher matcher = pattern.matcher("@@ -1,27 +1,27 @@"); + assertThat(first.getSource().size()).isGreaterThan(0); + assertThat(first.getTarget().size()).isGreaterThan(0); - assertTrue(matcher.find()); - assertEquals("1", matcher.group(1)); - assertEquals("1", matcher.group(3)); - } + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } - @Test - public void testSimpleParse2() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); + @Test + public void testParseIssue201() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("jsqlparser_patch_1.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(3); - AbstractDelta first = file1.getPatch().getDeltas().get(0); + AbstractDelta first = file1.getPatch().getDeltas().get(0); - assertThat(first.getSource().size()).isGreaterThan(0); - assertThat(first.getTarget().size()).isGreaterThan(0); + assertThat(first.getSource().size()).isGreaterThan(0); + assertThat(first.getTarget().size()).isGreaterThan(0); - assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); - } + assertThat(diff.getTail()).isEqualTo("2.17.1.windows.2\n"); + } - @Test - public void testSimplePattern() { - Pattern pattern = Pattern.compile("^\\+\\+\\+\\s"); + @Test + public void testSimplePattern() { + Pattern pattern = Pattern.compile("^\\+\\+\\+\\s"); - Matcher m = pattern.matcher("+++ revised.txt"); - assertTrue(m.find()); - } + Matcher m = pattern.matcher("+++ revised.txt"); + assertTrue(m.find()); + } - @Test - public void testParseIssue46() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue46.diff")); + @Test + public void testParseIssue46() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue46.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("a.vhd"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("a.vhd"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - } + assertThat(diff.getTail()).isNull(); + } - @Test - public void testParseIssue33() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue33.diff")); + @Test + public void testParseIssue33() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue33.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Main.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Main.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - assertThat(diff.getHeader()).isNull(); - } + assertThat(diff.getTail()).isNull(); + assertThat(diff.getHeader()).isNull(); + } - @Test - public void testParseIssue51() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue51.diff")); + @Test + public void testParseIssue51() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue51.diff")); - System.out.println(diff); + System.out.println(diff); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("f1"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("f1"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - UnifiedDiffFile file2 = diff.getFiles().get(1); - assertThat(file2.getFromFile()).isEqualTo("f2"); - assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file2 = diff.getFiles().get(1); + assertThat(file2.getFromFile()).isEqualTo("f2"); + assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isNull(); - } + assertThat(diff.getTail()).isNull(); + } - @Test - public void testParseIssue79() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue79.diff")); + @Test + public void testParseIssue79() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue79.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("test/Issue.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(0); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("test/Issue.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(0); - assertThat(diff.getTail()).isNull(); - assertThat(diff.getHeader()).isNull(); - } + assertThat(diff.getTail()).isNull(); + assertThat(diff.getHeader()).isNull(); + } - @Test - public void testParseIssue84() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue84.diff")); + @Test + public void testParseIssue84() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue84.diff")); - assertThat(diff.getFiles().size()).isEqualTo(2); + assertThat(diff.getFiles().size()).isEqualTo(2); - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("config/ant-phase-verify.xml"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("config/ant-phase-verify.xml"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - UnifiedDiffFile file2 = diff.getFiles().get(1); - assertThat(file2.getFromFile()).isEqualTo("/dev/null"); - assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); + UnifiedDiffFile file2 = diff.getFiles().get(1); + assertThat(file2.getFromFile()).isEqualTo("/dev/null"); + assertThat(file2.getPatch().getDeltas().size()).isEqualTo(1); - assertThat(diff.getTail()).isEqualTo("2.7.4"); - assertThat(diff.getHeader()).startsWith("From b53e612a2ab5ff15d14860e252f84c0f343fe93a Mon Sep 17 00:00:00 2001"); - } + assertThat(diff.getTail()).isEqualTo("2.7.4"); + assertThat(diff.getHeader()) + .startsWith("From b53e612a2ab5ff15d14860e252f84c0f343fe93a Mon Sep 17 00:00:00 2001"); + } - @Test - public void testParseIssue85() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue85.diff")); + @Test + public void testParseIssue85() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue85.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertEquals(1, diff.getFiles().size()); + assertEquals(1, diff.getFiles().size()); - final UnifiedDiffFile file1 = diff.getFiles().get(0); - assertEquals("diff -r 83e41b73d115 -r a4438263b228 tests/test-check-pyflakes.t", - file1.getDiffCommand()); - assertEquals("tests/test-check-pyflakes.t", file1.getFromFile()); - assertEquals("tests/test-check-pyflakes.t", file1.getToFile()); - assertEquals(1, file1.getPatch().getDeltas().size()); + final UnifiedDiffFile file1 = diff.getFiles().get(0); + assertEquals("diff -r 83e41b73d115 -r a4438263b228 tests/test-check-pyflakes.t", file1.getDiffCommand()); + assertEquals("tests/test-check-pyflakes.t", file1.getFromFile()); + assertEquals("tests/test-check-pyflakes.t", file1.getToFile()); + assertEquals(1, file1.getPatch().getDeltas().size()); - assertNull(diff.getTail()); - } + assertNull(diff.getTail()); + } - @Test - public void testTimeStampRegexp() { - assertThat("2019-04-18 13:49:39.516149751 +0200").matches(UnifiedDiffReader.TIMESTAMP_REGEXP); - } + @Test + public void testTimeStampRegexp() { + assertThat("2019-04-18 13:49:39.516149751 +0200").matches(UnifiedDiffReader.TIMESTAMP_REGEXP); + } - @Test - public void testParseIssue98() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue98.diff")); + @Test + public void testParseIssue98() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue98.diff")); - assertThat(diff.getFiles().size()).isEqualTo(1); + assertThat(diff.getFiles().size()).isEqualTo(1); - assertEquals(1, diff.getFiles().size()); + assertEquals(1, diff.getFiles().size()); - final UnifiedDiffFile file1 = diff.getFiles().get(0); - assertEquals("100644", - file1.getDeletedFileMode()); - assertEquals("src/test/java/se/bjurr/violations/lib/model/ViolationTest.java", file1.getFromFile()); - assertThat(diff.getTail()).isEqualTo("2.25.1"); - } + final UnifiedDiffFile file1 = diff.getFiles().get(0); + assertEquals("100644", file1.getDeletedFileMode()); + assertEquals("src/test/java/se/bjurr/violations/lib/model/ViolationTest.java", file1.getFromFile()); + assertThat(diff.getTail()).isEqualTo("2.25.1"); + } - @Test - public void testParseIssue104() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue104.diff")); + @Test + public void testParseIssue104() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue104.diff")); - assertThat(diff.getFiles().size()).isEqualTo(6); + assertThat(diff.getFiles().size()).isEqualTo(6); - final UnifiedDiffFile file = diff.getFiles().get(2); - assertThat(file.getFromFile()).isEqualTo("/dev/null"); - assertThat(file.getToFile()).isEqualTo("doc/samba_data_tool_path.xml.in"); + final UnifiedDiffFile file = diff.getFiles().get(2); + assertThat(file.getFromFile()).isEqualTo("/dev/null"); + assertThat(file.getToFile()).isEqualTo("doc/samba_data_tool_path.xml.in"); - assertThat(file.getPatch().toString()).isEqualTo("Patch{deltas=[[ChangeDelta, position: 0, lines: [] to [@SAMBA_DATA_TOOL@]]]}"); + assertThat(file.getPatch().toString()) + .isEqualTo("Patch{deltas=[[ChangeDelta, position: 0, lines: [] to [@SAMBA_DATA_TOOL@]]]}"); - assertThat(diff.getTail()).isEqualTo("2.14.4"); - } - - @Test - public void testParseIssue107BazelDiff() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("01-bazel-strip-unused.patch_issue107.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(450); - - final UnifiedDiffFile file = diff.getFiles().get(0); - assertThat(file.getFromFile()).isEqualTo("./src/main/java/com/amazonaws/AbortedException.java"); - assertThat(file.getToFile()).isEqualTo("/home/greg/projects/bazel/third_party/aws-sdk-auth-lite/src/main/java/com/amazonaws/AbortedException.java"); - - assertThat(diff.getFiles().stream() - .filter(f -> f.isNoNewLineAtTheEndOfTheFile()) - .count()) - .isEqualTo(48); - } - - @Test - public void testParseIssue107_2() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Main.java"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - - } - - @Test - public void testParseIssue107_3() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_3.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(1); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - assertThat(file1.getFromFile()).isEqualTo("Billion laughs attack.md"); - assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); - - } - - @Test - public void testParseIssue107_4() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_4.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(27); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("README.md"); - } - - @Test - public void testParseIssue107_5() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_5.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(22); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); - } - - @Test - public void testParseIssue110() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("0001-avahi-python-Use-the-agnostic-DBM-interface.patch")); - - assertThat(diff.getFiles().size()).isEqualTo(5); - - final UnifiedDiffFile file = diff.getFiles().get(4); - assertThat(file.getSimilarityIndex()).isEqualTo(87); - assertThat(file.getRenameFrom()).isEqualTo("service-type-database/build-db.in"); - assertThat(file.getRenameTo()).isEqualTo("service-type-database/build-db"); - - assertThat(file.getFromFile()).isEqualTo("service-type-database/build-db.in"); - assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); - } - - @Test - public void testParseIssue117() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue117.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getSource().getChangePosition()) - .containsExactly(24, 27); - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(0).getTarget().getChangePosition()) - .containsExactly(24, 27); - - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getSource().getChangePosition()) - .containsExactly(64); - assertThat(diff.getFiles().get(0).getPatch().getDeltas().get(1).getTarget().getChangePosition()) - .containsExactly(64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74); - -// diff.getFiles().forEach(f -> { -// System.out.println("File: " + f.getFromFile()); -// f.getPatch().getDeltas().forEach(delta -> { -// -// System.out.println(delta); -// System.out.println("Source: "); -// System.out.println(delta.getSource().getPosition()); -// System.out.println(delta.getSource().getChangePosition()); -// -// System.out.println("Target: "); -// System.out.println(delta.getTarget().getPosition()); -// System.out.println(delta.getTarget().getChangePosition()); -// }); -// }); - } - - @Test - public void testParseIssue122() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(1); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); - } - - @Test - public void testParseIssue123() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); - - assertThat(diff.getFiles().size()).isEqualTo(2); - - assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); - } - - @Test - public void testParseIssue141() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue141.diff")); - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getFromFile()).isEqualTo("a.txt"); - assertThat(file1.getToFile()).isEqualTo("a1.txt"); - } - - @Test - public void testParseIssue182add() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryAdded()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182delete() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryDeleted()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182edit() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getBinaryEdited()).isEqualTo("some-image.png"); - } - - @Test - public void testParseIssue182mode() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getOldMode()).isEqualTo("100644"); - assertThat(file1.getNewMode()).isEqualTo("100755"); - } - - @Test - public void testParseIssue193Copy() throws IOException { - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( - UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue193.diff")); - - UnifiedDiffFile file1 = diff.getFiles().get(0); - - assertThat(file1.getCopyFrom()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.pcf"); - assertThat(file1.getCopyTo()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf"); - } + assertThat(diff.getTail()).isEqualTo("2.14.4"); + } + + @Test + public void testParseIssue107BazelDiff() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("01-bazel-strip-unused.patch_issue107.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(450); + + final UnifiedDiffFile file = diff.getFiles().get(0); + assertThat(file.getFromFile()).isEqualTo("./src/main/java/com/amazonaws/AbortedException.java"); + assertThat(file.getToFile()) + .isEqualTo( + "/home/greg/projects/bazel/third_party/aws-sdk-auth-lite/src/main/java/com/amazonaws/AbortedException.java"); + + assertThat(diff.getFiles().stream() + .filter(f -> f.isNoNewLineAtTheEndOfTheFile()) + .count()) + .isEqualTo(48); + } + + @Test + public void testParseIssue107_2() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Main.java"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + } + + @Test + public void testParseIssue107_3() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_3.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(1); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + assertThat(file1.getFromFile()).isEqualTo("Billion laughs attack.md"); + assertThat(file1.getPatch().getDeltas().size()).isEqualTo(1); + } + + @Test + public void testParseIssue107_4() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_4.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(27); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("README.md"); + } + + @Test + public void testParseIssue107_5() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue107_5.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(22); + + assertThat(diff.getFiles()) + .extracting(f -> f.getFromFile()) + .contains( + "rt/management/src/test/java/org/apache/cxf/management/jmx/MBServerConnectorFactoryTest.java"); + } + + @Test + public void testParseIssue110() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(UnifiedDiffReaderTest.class.getResourceAsStream( + "0001-avahi-python-Use-the-agnostic-DBM-interface.patch")); + + assertThat(diff.getFiles().size()).isEqualTo(5); + + final UnifiedDiffFile file = diff.getFiles().get(4); + assertThat(file.getSimilarityIndex()).isEqualTo(87); + assertThat(file.getRenameFrom()).isEqualTo("service-type-database/build-db.in"); + assertThat(file.getRenameTo()).isEqualTo("service-type-database/build-db"); + + assertThat(file.getFromFile()).isEqualTo("service-type-database/build-db.in"); + assertThat(file.getToFile()).isEqualTo("service-type-database/build-db"); + } + + @Test + public void testParseIssue117() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue117.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(0) + .getSource() + .getChangePosition()) + .containsExactly(24, 27); + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(0) + .getTarget() + .getChangePosition()) + .containsExactly(24, 27); + + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(1) + .getSource() + .getChangePosition()) + .containsExactly(64); + assertThat(diff.getFiles() + .get(0) + .getPatch() + .getDeltas() + .get(1) + .getTarget() + .getChangePosition()) + .containsExactly(64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74); + + // diff.getFiles().forEach(f -> { + // System.out.println("File: " + f.getFromFile()); + // f.getPatch().getDeltas().forEach(delta -> { + // + // System.out.println(delta); + // System.out.println("Source: "); + // System.out.println(delta.getSource().getPosition()); + // System.out.println(delta.getSource().getChangePosition()); + // + // System.out.println("Target: "); + // System.out.println(delta.getTarget().getPosition()); + // System.out.println(delta.getTarget().getChangePosition()); + // }); + // }); + } + + @Test + public void testParseIssue122() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue122.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(1); + + assertThat(diff.getFiles()).extracting(f -> f.getFromFile()).contains("coders/wpg.c"); + } + + @Test + public void testParseIssue123() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue123.diff")); + + assertThat(diff.getFiles().size()).isEqualTo(2); + + assertThat(diff.getFiles()) + .extracting(f -> f.getFromFile()) + .contains("src/java/main/org/apache/zookeeper/server/FinalRequestProcessor.java"); + } + + @Test + public void testParseIssue141() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue141.diff")); + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getFromFile()).isEqualTo("a.txt"); + assertThat(file1.getToFile()).isEqualTo("a1.txt"); + } + + @Test + public void testParseIssue182add() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_add.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryAdded()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182delete() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_delete.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryDeleted()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182edit() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_edit.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getBinaryEdited()).isEqualTo("some-image.png"); + } + + @Test + public void testParseIssue182mode() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_issue182_mode.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getOldMode()).isEqualTo("100644"); + assertThat(file1.getNewMode()).isEqualTo("100755"); + } + + @Test + public void testParseIssue193Copy() throws IOException { + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff( + UnifiedDiffReaderTest.class.getResourceAsStream("problem_diff_parsing_issue193.diff")); + + UnifiedDiffFile file1 = diff.getFiles().get(0); + + assertThat(file1.getCopyFrom()).isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.pcf"); + assertThat(file1.getCopyTo()) + .isEqualTo("modules/configuration/config/web/pcf/account/AccountContactCV.default.pcf"); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java index 725e0e2a..22d192d8 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripNewLineTest.java @@ -15,32 +15,33 @@ */ package com.github.difflib.unifieddiff; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.github.difflib.patch.PatchFailedException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @Disabled("for next release") public class UnifiedDiffRoundTripNewLineTest { - @Test - public void testIssue135MissingNoNewLineInPatched() throws IOException, PatchFailedException { - String beforeContent = "rootProject.name = \"sample-repo\""; - String afterContent = "rootProject.name = \"sample-repo\"\n"; - String patch = "diff --git a/settings.gradle b/settings.gradle\n" + - "index ef3b8e2..ab30124 100644\n" + - "--- a/settings.gradle\n" + - "+++ b/settings.gradle\n" + - "@@ -1 +1 @@\n" + - "-rootProject.name = \"sample-repo\"\n" + - "\\ No newline at end of file\n" + - "+rootProject.name = \"sample-repo\"\n"; - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(patch.getBytes())); - String unifiedAfterContent = unifiedDiff.getFiles().get(0).getPatch() - .applyTo(Arrays.asList(beforeContent.split("\n"))).stream().collect(joining("\n")); - assertEquals(afterContent, unifiedAfterContent); - } + @Test + public void testIssue135MissingNoNewLineInPatched() throws IOException, PatchFailedException { + String beforeContent = "rootProject.name = \"sample-repo\""; + String afterContent = "rootProject.name = \"sample-repo\"\n"; + String patch = "diff --git a/settings.gradle b/settings.gradle\n" + "index ef3b8e2..ab30124 100644\n" + + "--- a/settings.gradle\n" + + "+++ b/settings.gradle\n" + + "@@ -1 +1 @@\n" + + "-rootProject.name = \"sample-repo\"\n" + + "\\ No newline at end of file\n" + + "+rootProject.name = \"sample-repo\"\n"; + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(patch.getBytes())); + String unifiedAfterContent = + unifiedDiff.getFiles().get(0).getPatch().applyTo(Arrays.asList(beforeContent.split("\n"))).stream() + .collect(joining("\n")); + assertEquals(afterContent, unifiedAfterContent); + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java index d1892771..bb8bdcb5 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffRoundTripTest.java @@ -1,5 +1,9 @@ package com.github.difflib.unifieddiff; +import static java.util.stream.Collectors.joining; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import com.github.difflib.DiffUtils; import com.github.difflib.TestConstants; import com.github.difflib.patch.Patch; @@ -13,154 +17,152 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.util.stream.Collectors.joining; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class UnifiedDiffRoundTripTest { - public static List fileToLines(String filename) throws FileNotFoundException, IOException { - List lines = new ArrayList<>(); - String line = ""; - try (BufferedReader in = new BufferedReader(new FileReader(filename))) { - while ((line = in.readLine()) != null) { - lines.add(line); - } - } - return lines; - } - - @Test - public void testGenerateUnified() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); - - verify(origLines, revLines, "original.txt", "revised.txt"); - } - - @Test - public void testGenerateUnifiedWithOneDelta() throws IOException { - List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); - List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); - - verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); - } - - @Test - public void testGenerateUnifiedDiffWithoutAnyDeltas() throws IOException { - List test = Arrays.asList("abc"); - Patch patch = DiffUtils.diff(test, test); - StringWriter writer = new StringWriter(); - - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("abc", "abc", patch)), - name -> test, - writer, 0); - - System.out.println(writer); - } - - @Test - public void testDiff_Issue10() throws IOException { - final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); - final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( - new ByteArrayInputStream(patchLines.stream().collect(joining("\n")).getBytes()) - ); - - final Patch p = unifiedDiff.getFiles().get(0).getPatch(); - try { - DiffUtils.patch(baseLines, p); - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } - - /** - * Issue 12 - */ - @Test - @Disabled - public void testPatchWithNoDeltas() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); - verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); - } - - @Test - public void testDiff5() throws IOException { - final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); - final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); - verify(lines1, lines2, "5A.txt", "5B.txt"); - } - - /** - * Issue 19 - */ - @Test - public void testDiffWithHeaderLineInText() throws IOException { - List original = new ArrayList<>(); - List revised = new ArrayList<>(); - - original.add("test line1"); - original.add("test line2"); - original.add("test line 4"); - original.add("test line 5"); - - revised.add("test line1"); - revised.add("test line2"); - revised.add("@@ -2,6 +2,7 @@"); - revised.add("test line 4"); - revised.add("test line 5"); - - Patch patch = DiffUtils.diff(original, revised); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("original", "revised", patch)), - name -> original, - writer, 10); - - System.out.println(writer.toString()); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(writer.toString().getBytes())); - } - - private void verify(List origLines, List revLines, - String originalFile, String revisedFile) throws IOException { - Patch patch = DiffUtils.diff(origLines, revLines); - - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write( - UnifiedDiff.from("header", "tail", UnifiedDiffFile.from(originalFile, revisedFile, patch)), - name -> origLines, - writer, 10); - - System.out.println(writer.toString()); - - UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(writer.toString().getBytes())); - - List patchedLines; - try { -// if (unifiedDiff.getFiles().isEmpty()) { -// patchedLines = new ArrayList<>(origLines); -// } else { -// Patch fromUnifiedPatch = unifiedDiff.getFiles().get(0).getPatch(); -// patchedLines = fromUnifiedPatch.applyTo(origLines); -// } - patchedLines = unifiedDiff.applyPatchTo(file -> originalFile.equals(file), origLines); - assertEquals(revLines.size(), patchedLines.size()); - for (int i = 0; i < revLines.size(); i++) { - String l1 = revLines.get(i); - String l2 = patchedLines.get(i); - if (!l1.equals(l2)) { - fail("Line " + (i + 1) + " of the patched file did not match the revised original"); - } - } - } catch (PatchFailedException e) { - fail(e.getMessage()); - } - } + public static List fileToLines(String filename) throws FileNotFoundException, IOException { + List lines = new ArrayList<>(); + String line = ""; + try (BufferedReader in = new BufferedReader(new FileReader(filename))) { + while ((line = in.readLine()) != null) { + lines.add(line); + } + } + return lines; + } + + @Test + public void testGenerateUnified() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "revised.txt"); + + verify(origLines, revLines, "original.txt", "revised.txt"); + } + + @Test + public void testGenerateUnifiedWithOneDelta() throws IOException { + List origLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_original.txt"); + List revLines = fileToLines(TestConstants.MOCK_FOLDER + "one_delta_test_revised.txt"); + + verify(origLines, revLines, "one_delta_test_original.txt", "one_delta_test_revised.txt"); + } + + @Test + public void testGenerateUnifiedDiffWithoutAnyDeltas() throws IOException { + List test = Arrays.asList("abc"); + Patch patch = DiffUtils.diff(test, test); + StringWriter writer = new StringWriter(); + + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("abc", "abc", patch)), name -> test, writer, 0); + + System.out.println(writer); + } + + @Test + public void testDiff_Issue10() throws IOException { + final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); + final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream( + patchLines.stream().collect(joining("\n")).getBytes())); + + final Patch p = unifiedDiff.getFiles().get(0).getPatch(); + try { + DiffUtils.patch(baseLines, p); + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } + + /** + * Issue 12 + */ + @Test + @Disabled + public void testPatchWithNoDeltas() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_1.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "issue11_2.txt"); + verify(lines1, lines2, "issue11_1.txt", "issue11_2.txt"); + } + + @Test + public void testDiff5() throws IOException { + final List lines1 = fileToLines(TestConstants.MOCK_FOLDER + "5A.txt"); + final List lines2 = fileToLines(TestConstants.MOCK_FOLDER + "5B.txt"); + verify(lines1, lines2, "5A.txt", "5B.txt"); + } + + /** + * Issue 19 + */ + @Test + public void testDiffWithHeaderLineInText() throws IOException { + List original = new ArrayList<>(); + List revised = new ArrayList<>(); + + original.add("test line1"); + original.add("test line2"); + original.add("test line 4"); + original.add("test line 5"); + + revised.add("test line1"); + revised.add("test line2"); + revised.add("@@ -2,6 +2,7 @@"); + revised.add("test line 4"); + revised.add("test line 5"); + + Patch patch = DiffUtils.diff(original, revised); + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from("original", "revised", patch)), + name -> original, + writer, + 10); + + System.out.println(writer.toString()); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( + new ByteArrayInputStream(writer.toString().getBytes())); + } + + private void verify(List origLines, List revLines, String originalFile, String revisedFile) + throws IOException { + Patch patch = DiffUtils.diff(origLines, revLines); + + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write( + UnifiedDiff.from("header", "tail", UnifiedDiffFile.from(originalFile, revisedFile, patch)), + name -> origLines, + writer, + 10); + + System.out.println(writer.toString()); + + UnifiedDiff unifiedDiff = UnifiedDiffReader.parseUnifiedDiff( + new ByteArrayInputStream(writer.toString().getBytes())); + + List patchedLines; + try { + // if (unifiedDiff.getFiles().isEmpty()) { + // patchedLines = new ArrayList<>(origLines); + // } else { + // Patch fromUnifiedPatch = unifiedDiff.getFiles().get(0).getPatch(); + // patchedLines = fromUnifiedPatch.applyTo(origLines); + // } + patchedLines = unifiedDiff.applyPatchTo(file -> originalFile.equals(file), origLines); + assertEquals(revLines.size(), patchedLines.size()); + for (int i = 0; i < revLines.size(); i++) { + String l1 = revLines.get(i); + String l2 = patchedLines.get(i); + if (!l1.equals(l2)) { + fail("Line " + (i + 1) + " of the patched file did not match the revised original"); + } + } + } catch (PatchFailedException e) { + fail(e.getMessage()); + } + } } diff --git a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java index af48d485..ccbff82d 100644 --- a/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java +++ b/java-diff-utils/src/test/java/com/github/difflib/unifieddiff/UnifiedDiffWriterTest.java @@ -15,6 +15,8 @@ */ package com.github.difflib.unifieddiff; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.github.difflib.DiffUtils; import com.github.difflib.patch.Patch; import java.io.ByteArrayInputStream; @@ -28,7 +30,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; /** @@ -37,49 +38,51 @@ */ public class UnifiedDiffWriterTest { - public UnifiedDiffWriterTest() { - } + public UnifiedDiffWriterTest() {} + + @Test + public void testWrite() throws URISyntaxException, IOException { + String str = readFile( + UnifiedDiffReaderTest.class + .getResource("jsqlparser_patch_1.diff") + .toURI(), + Charset.defaultCharset()); + UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(str.getBytes())); + + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write(diff, f -> Collections.emptyList(), writer, 5); + System.out.println(writer.toString()); + } + + /** + * Issue 47 + */ + @Test + public void testWriteWithNewFile() throws URISyntaxException, IOException { + + List original = new ArrayList<>(); + List revised = new ArrayList<>(); - @Test - public void testWrite() throws URISyntaxException, IOException { - String str = readFile(UnifiedDiffReaderTest.class.getResource("jsqlparser_patch_1.diff").toURI(), Charset.defaultCharset()); - UnifiedDiff diff = UnifiedDiffReader.parseUnifiedDiff(new ByteArrayInputStream(str.getBytes())); + revised.add("line1"); + revised.add("line2"); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write(diff, f -> Collections.emptyList(), writer, 5); - System.out.println(writer.toString()); - } - - /** - * Issue 47 - */ - @Test - public void testWriteWithNewFile() throws URISyntaxException, IOException { - - List original = new ArrayList<>(); - List revised = new ArrayList<>(); + Patch patch = DiffUtils.diff(original, revised); + UnifiedDiff diff = new UnifiedDiff(); + diff.addFile(UnifiedDiffFile.from(null, "revised", patch)); - revised.add("line1"); - revised.add("line2"); + StringWriter writer = new StringWriter(); + UnifiedDiffWriter.write(diff, f -> original, writer, 5); + System.out.println(writer.toString()); - Patch patch = DiffUtils.diff(original, revised); - UnifiedDiff diff = new UnifiedDiff(); - diff.addFile( UnifiedDiffFile.from(null, "revised", patch) ); + String[] lines = writer.toString().split("\\n"); - StringWriter writer = new StringWriter(); - UnifiedDiffWriter.write(diff, f -> original, writer, 5); - System.out.println(writer.toString()); - - String[] lines = writer.toString().split("\\n"); - - assertEquals("--- /dev/null", lines[0]); - assertEquals("+++ revised", lines[1]); - assertEquals("@@ -0,0 +1,2 @@", lines[2]); - } + assertEquals("--- /dev/null", lines[0]); + assertEquals("+++ revised", lines[1]); + assertEquals("@@ -0,0 +1,2 @@", lines[2]); + } - static String readFile(URI path, Charset encoding) - throws IOException { - byte[] encoded = Files.readAllBytes(Paths.get(path)); - return new String(encoded, encoding); - } + static String readFile(URI path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } } diff --git a/pom.xml b/pom.xml index f5d42c12..12c736d1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,246 +1,283 @@ - 4.0.0 - io.github.java-diff-utils - java-diff-utils-parent - 4.15 - 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 - java-diff-utils-parent-4.15 - - - 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.11.3 - test - - - org.assertj - assertj-core - 3.26.3 - test - + + com.puppycrawl.tools + checkstyle + 8.29 + - - + + + verify-style + + check + + process-classes + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + + + **/LR*.java + + + + + com.diffplug.spotless + spotless-maven-plugin + 2.30.0 + + + + + true + 2 + + + + + false + + + + + + + apply + + validate + + + + + 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 - sign-release-artifacts + f22e0543 - - - org.apache.felix - maven-bundle-plugin - 3.5.1 - - - bundle-manifest - process-classes - - manifest - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.1.1 - - ${javadoc.opts} - none - - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.6.0 - - - verify-style - process-classes - - check - - - - - true - true - ${project.build.sourceDirectory} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - com.puppycrawl.tools - checkstyle - 8.29 - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.2 - - - **/LR*.java - - - + + + + + + + + doclint-java8-disable + + [1.8,) + + + -Xdoclint:none + + + + long-running-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + xxx + + + - - - - sign-release-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - f22e0543 - - - - - - - - - doclint-java8-disable - - [1.8,) - - - -Xdoclint:none - - - - long-running-tests - - - - org.apache.maven.plugins - maven-surefire-plugin - - - xxx - - - - - - - + + +