(equalizer);
+ return new MyersDiffWithLinearSpace<>(equalizer);
}
};
}
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
index 7e55ac0d..50054074 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Chunk.java
@@ -26,7 +26,7 @@
*
*
* Text is represented as Object[]
because the diff engine is
- * capable of handling more than plain ascci. In fact, arrays or lists of any
+ * capable of handling more than plain ascii. In fact, arrays or lists of any
* type that implements {@link java.lang.Object#hashCode hashCode()} and
* {@link java.lang.Object#equals equals()} correctly can be subject to
* differencing using this library.
diff --git a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
index aaff7d94..305f2de7 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/patch/Patch.java
@@ -224,7 +224,7 @@ private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext ctx, Abstra
private ConflictOutput conflictOutput = CONFLICT_PRODUCES_EXCEPTION;
/**
- * Alter normal conflict output behaviour to e.g. inclide some conflict
+ * Alter normal conflict output behaviour to e.g. include some conflict
* statements in the result, like git does it.
*/
public Patch withConflictOutput(ConflictOutput conflictOutput) {
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..9ec50a84 100644
--- a/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java
+++ b/java-diff-utils/src/main/java/com/github/difflib/text/DiffRowGenerator.java
@@ -24,6 +24,8 @@
import com.github.difflib.patch.InsertDelta;
import com.github.difflib.patch.Patch;
import com.github.difflib.text.DiffRow.Tag;
+import com.github.difflib.text.deltamerge.DeltaMergeUtils;
+import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
@@ -75,6 +77,14 @@ public final class DiffRowGenerator {
public static final Function> SPLITTER_BY_WORD = line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN);
public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
+ public static final Function>> DEFAULT_INLINE_DELTA_MERGER = InlineDeltaMergeInfo::getDeltas;
+
+ /**
+ * Merge diffs which are separated by equalities consisting of whitespace only.
+ */
+ public static final Function>> WHITESPACE_EQUALITIES_MERGER = deltaMergeInfo -> DeltaMergeUtils
+ .mergeInlineDeltas(deltaMergeInfo, equalities -> equalities.stream().allMatch(s -> s==null || s.replaceAll("\\s+", "").equals("")));
+
public static Builder create() {
return new Builder();
}
@@ -170,6 +180,7 @@ static void wrapInTag(List sequence, int startPosition,
private final boolean reportLinesUnchanged;
private final Function lineNormalizer;
private final Function processDiffs;
+ private final Function>> inlineDeltaMerger;
private final boolean showInlineDiffs;
private final boolean replaceOriginalLinefeedInChangesWithSpaces;
@@ -194,11 +205,13 @@ private DiffRowGenerator(Builder builder) {
reportLinesUnchanged = builder.reportLinesUnchanged;
lineNormalizer = builder.lineNormalizer;
processDiffs = builder.processDiffs;
+ inlineDeltaMerger = builder.inlineDeltaMerger;
replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces;
Objects.requireNonNull(inlineDiffSplitter);
Objects.requireNonNull(lineNormalizer);
+ Objects.requireNonNull(inlineDeltaMerger);
}
/**
@@ -370,7 +383,10 @@ private List generateInlineDiffs(AbstractDelta delta) {
origList = inlineDiffSplitter.apply(joinedOrig);
revList = inlineDiffSplitter.apply(joinedRev);
- List> inlineDeltas = DiffUtils.diff(origList, revList, equalizer).getDeltas();
+ List> originalInlineDeltas = DiffUtils.diff(origList, revList, equalizer)
+ .getDeltas();
+ List> inlineDeltas = inlineDeltaMerger
+ .apply(new InlineDeltaMergeInfo(originalInlineDeltas, origList, revList));
Collections.reverse(inlineDeltas);
for (AbstractDelta inlineDelta : inlineDeltas) {
@@ -465,6 +481,7 @@ public static class Builder {
private Function processDiffs = null;
private BiPredicate equalizer = null;
private boolean replaceOriginalLinefeedInChangesWithSpaces = false;
+ private Function>> inlineDeltaMerger = DEFAULT_INLINE_DELTA_MERGER;
private Builder() {
}
@@ -673,5 +690,17 @@ public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) {
this.replaceOriginalLinefeedInChangesWithSpaces = replace;
return this;
}
+
+ /**
+ * Provide an inline delta merger for use case specific delta optimizations.
+ *
+ * @param inlineDeltaMerger
+ * @return
+ */
+ public Builder inlineDeltaMerger(
+ Function>> inlineDeltaMerger) {
+ this.inlineDeltaMerger = inlineDeltaMerger;
+ return this;
+ }
}
}
diff --git a/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java
new file mode 100644
index 00000000..b2580957
--- /dev/null
+++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/DeltaMergeUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2009-2024 java-diff-utils.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.difflib.text.deltamerge;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+import com.github.difflib.patch.AbstractDelta;
+import com.github.difflib.patch.ChangeDelta;
+import com.github.difflib.patch.Chunk;
+
+/**
+ * Provides utility features for merge inline deltas
+ *
+ * @author Christian Meier
+ */
+final public class DeltaMergeUtils {
+
+ public static List> mergeInlineDeltas(InlineDeltaMergeInfo deltaMergeInfo,
+ Predicate> replaceEquality) {
+ final List> originalDeltas = deltaMergeInfo.getDeltas();
+ if (originalDeltas.size() < 2) {
+ return originalDeltas;
+ }
+
+ final List> newDeltas = new ArrayList<>();
+ newDeltas.add(originalDeltas.get(0));
+ for (int i = 1; i < originalDeltas.size(); i++) {
+ final AbstractDelta previousDelta = newDeltas.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..cc6b399a
--- /dev/null
+++ b/java-diff-utils/src/main/java/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2009-2024 java-diff-utils.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.difflib.text.deltamerge;
+
+import java.util.List;
+
+import com.github.difflib.patch.AbstractDelta;
+
+/**
+ * Holds the information required to merge deltas originating from an inline
+ * diff
+ *
+ * @author Christian Meier
+ */
+public final class InlineDeltaMergeInfo {
+
+ private final List> deltas;
+ private final List origList;
+ private final List revList;
+
+ public InlineDeltaMergeInfo(List> deltas, List origList, List revList) {
+ this.deltas = deltas;
+ this.origList = origList;
+ this.revList = revList;
+ }
+
+ public List> getDeltas() {
+ return deltas;
+ }
+
+ public List getOrigList() {
+ return origList;
+ }
+
+ public List getRevList() {
+ return revList;
+ }
+}
diff --git a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
index e13d41aa..b8667818 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/GenerateUnifiedDiffTest.java
@@ -51,8 +51,14 @@ public void testGenerateUnifiedWithOneDelta() throws IOException {
@Test
public void testGenerateUnifiedDiffWithoutAnyDeltas() {
List test = Arrays.asList("abc");
- Patch patch = DiffUtils.diff(test, test);
- UnifiedDiffUtils.generateUnifiedDiff("abc", "abc", test, patch, 0);
+ List testRevised = Arrays.asList("abc2");
+ Patch patch = DiffUtils.diff(test, testRevised);
+ String unifiedDiffTxt = String.join("\n", UnifiedDiffUtils.generateUnifiedDiff("abc1", "abc2", test, patch, 0));
+ System.out.println(unifiedDiffTxt);
+
+ assertThat(unifiedDiffTxt)
+ .as("original filename should be abc1").contains("--- abc1")
+ .as("revised filename should be abc2").contains("+++ abc2");
}
@Test
diff --git a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
index 897a37f9..beccad8c 100644
--- a/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
+++ b/java-diff-utils/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java
@@ -1,12 +1,18 @@
package com.github.difflib.text;
+import com.github.difflib.DiffUtils;
+import com.github.difflib.algorithm.myers.MyersDiffWithLinearSpace;
import java.io.File;
import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
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;
@@ -15,6 +21,10 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
+import com.github.difflib.patch.AbstractDelta;
+import com.github.difflib.text.deltamerge.DeltaMergeUtils;
+import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo;
+
public class DiffRowGeneratorTest {
@Test
@@ -721,7 +731,7 @@ public void testIssue129WithDeltaDecompression() {
assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE INSERT INSERT EQUAL EQUAL EQUAL");
}
-
+
@Test
public void testIssue129SkipDeltaDecompression() {
List lines1 = Arrays.asList(
@@ -743,17 +753,17 @@ public void testIssue129SkipDeltaDecompression() {
"banana2",
"banana3");
int[] entry = {1};
- String txt =
- DiffRowGenerator.create()
- .showInlineDiffs(true)
- .decompressDeltas(false)
- .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==")
- .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==")
- .build()
- .generateDiffRows(lines1, lines2)
- .stream()
- .map(row -> row.getTag().toString())
- .collect(joining(" "));
+ String txt
+ = DiffRowGenerator.create()
+ .showInlineDiffs(true)
+ .decompressDeltas(false)
+ .oldTag((tag, isOpening) -> isOpening ? "==old" + tag + "==>" : "<==old==")
+ .newTag((tag, isOpening) -> isOpening ? "==new" + tag + "==>" : "<==new==")
+ .build()
+ .generateDiffRows(lines1, lines2)
+ .stream()
+ .map(row -> row.getTag().toString())
+ .collect(joining(" "));
// .forEachOrdered(row -> {
// System.out.printf("%4d %-8s %-80s %-80s\n", entry[0]++,
// row.getTag(), row.getOldLine(), row.getNewLine());
@@ -761,7 +771,7 @@ public void testIssue129SkipDeltaDecompression() {
assertThat(txt).isEqualTo("EQUAL EQUAL EQUAL CHANGE CHANGE CHANGE EQUAL EQUAL EQUAL");
}
-
+
@Test
public void testIssue129SkipWhitespaceChanges() throws IOException {
String original = Files.lines(Paths.get("target/test-classes/com/github/difflib/text/issue129_1.txt")).collect(joining("\n"));
@@ -780,9 +790,74 @@ public void testIssue129SkipWhitespaceChanges() throws IOException {
Arrays.asList(revised.split("\n")));
assertThat(rows).hasSize(13);
-
+
rows.stream()
.filter(item -> item.getTag() != DiffRow.Tag.EQUAL)
.forEach(System.out::println);
}
+
+ @Test
+ public void 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/resources/com/github/difflib/text/test.zip b/java-diff-utils/src/test/resources/com/github/difflib/text/test.zip
new file mode 100644
index 00000000..7b071fcd
Binary files /dev/null and b/java-diff-utils/src/test/resources/com/github/difflib/text/test.zip differ
diff --git a/pom.xml b/pom.xml
index 52b653da..aa1deae3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
io.github.java-diff-utils
java-diff-utils-parent
- 4.13-SNAPSHOT
+ 4.16-SNAPSHOT
java-diff-utils-parent
pom
@@ -65,13 +65,13 @@
org.junit.jupiter
junit-jupiter
- 5.7.1
+ 5.11.4
test
org.assertj
assertj-core
- 3.19.0
+ 3.27.3
test
@@ -86,12 +86,13 @@
true
false
forked-path
+ sign-release-artifacts
org.apache.felix
maven-bundle-plugin
- 3.3.0
+ 3.5.1
bundle-manifest
@@ -122,7 +123,7 @@
org.apache.maven.plugins
maven-checkstyle-plugin
- 3.1.0
+ 3.6.0
verify-style
@@ -176,7 +177,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.0.0-M4
+ 3.5.2
**/LR*.java