diff --git a/README.md b/README.md index bc5e0f0..8945661 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ ## Status ## [![Build Status](https://travis-ci.org/wumpz/java-diff-utils.svg?branch=master)](https://travis-ci.org/wumpz/java-diff-utils) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/7eba77f10bed4c2a8d08ac8dc8da4a86)](https://www.codacy.com/app/wumpz/java-diff-utils?utm_source=github.com&utm_medium=referral&utm_content=wumpz/java-diff-utils&utm_campaign=Badge_Grade) -**After a while using it, at least for me, this one seems to be feature complete. If someone finds bugs or has improvement ideas, please file an issue. I wonder why that this project is a zero issue project.** - ## 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. @@ -54,6 +52,8 @@ But it can easily replaced by any other which is better for handing your texts. ### Changelog ### * Version 2.3-SNAPSHOT + * Introduced a process listener to diff algorithms. For long running + diffs one could implement some progress information. * Version 2.2 * released at maven central * included checkstyle source code conventions diff --git a/src/main/java/com/github/difflib/DiffUtils.java b/src/main/java/com/github/difflib/DiffUtils.java index 807d7b4..86861be 100644 --- a/src/main/java/com/github/difflib/DiffUtils.java +++ b/src/main/java/com/github/difflib/DiffUtils.java @@ -20,6 +20,7 @@ limitations under the License. package com.github.difflib; import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.algorithm.myers.MyersDiff; import com.github.difflib.patch.Delta; @@ -37,7 +38,6 @@ import static java.util.stream.Collectors.joining; * Implements the difference and patching engine * * @author Dmitry Naumenko - * @version 0.4.1 */ public final class DiffUtils { @@ -46,17 +46,23 @@ public final class DiffUtils { * * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. + * @param progress progress listener * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + * @throws com.github.difflib.algorithm.DiffException */ + public static Patch diff(List original, List revised, DiffAlgorithmListener progress) throws DiffException { + return DiffUtils.diff(original, revised, new MyersDiff<>(), progress); + } + public static Patch diff(List original, List revised) throws DiffException { - return DiffUtils.diff(original, revised, new MyersDiff<>()); + return DiffUtils.diff(original, revised, new MyersDiff<>(), null); } /** * Computes the difference between the original and revised text. */ - public static Patch diff(String originalText, String revisedText) throws DiffException { - return DiffUtils.diff(Arrays.asList(originalText.split("\n")), Arrays.asList(revisedText.split("\n"))); + public static Patch diff(String originalText, String revisedText, DiffAlgorithmListener progress) throws DiffException { + return DiffUtils.diff(Arrays.asList(originalText.split("\n")), Arrays.asList(revisedText.split("\n")), progress); } /** @@ -84,16 +90,30 @@ public final class DiffUtils { * @param original The original text. Must not be {@code null}. * @param revised The revised text. Must not be {@code null}. * @param algorithm The diff algorithm. Must not be {@code null}. + * @param progress The diff algorithm listener. * @return The patch describing the difference between the original and revised sequences. Never {@code null}. */ public static Patch diff(List original, List revised, - DiffAlgorithm algorithm) throws DiffException { + DiffAlgorithm algorithm, DiffAlgorithmListener progress) throws DiffException { 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.diff(original, revised)); + return Patch.generate(original, revised, algorithm.diff(original, revised, progress)); } + + /** + * Computes the difference between the original and revised list of elements with default diff algorithm + * + * @param original The original text. Must not be {@code null}. + * @param revised The revised text. Must not be {@code null}. + * @param algorithm The diff algorithm. Must not be {@code null}. + * @return The patch describing the difference between the original and revised sequences. Never {@code null}. + */ + public static Patch diff(List original, List revised, + DiffAlgorithm algorithm) throws DiffException { + 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 diff --git a/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java b/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java index b97a15a..c6f2698 100644 --- a/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java +++ b/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java @@ -38,8 +38,8 @@ public interface DiffAlgorithm { * @param revised The revised sequence. Must not be {@code null}. * @return The patch representing the diff of the given sequences. Never {@code null}. */ - public default List diff(T[] original, T[] revised) throws DiffException { - return diff(Arrays.asList(original), Arrays.asList(revised)); + default List diff(T[] original, T[] revised, DiffAlgorithmListener progress) throws DiffException { + return diff(Arrays.asList(original), Arrays.asList(revised), progress); } /** @@ -50,5 +50,5 @@ public interface DiffAlgorithm { * @param revised The revised sequence. Must not be {@code null}. * @return The patch representing the diff of the given sequences. Never {@code null}. */ - public List diff(List original, List revised) throws DiffException; + List diff(List original, List revised, DiffAlgorithmListener progress) throws DiffException; } diff --git a/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java b/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java new file mode 100644 index 0000000..37d5181 --- /dev/null +++ b/src/main/java/com/github/difflib/algorithm/DiffAlgorithmListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 2018 java-diff-utils. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.github.difflib.algorithm; + +/** + * + * @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(); +} diff --git a/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java b/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java index 6ca8010..3029f6c 100644 --- a/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -17,6 +17,7 @@ package com.github.difflib.algorithm.jgit; import com.github.difflib.algorithm.Change; import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.patch.DeltaType; import java.util.ArrayList; @@ -28,18 +29,22 @@ import org.eclipse.jgit.diff.Sequence; import org.eclipse.jgit.diff.SequenceComparator; /** - * HistorgramDiff using JGit - Library. This one is much more performant than the orginal Myers implementation. + * HistorgramDiff using JGit - Library. This one is much more performant than the orginal Myers + * implementation. * * @author toben */ public class HistogramDiff implements DiffAlgorithm { @Override - public List diff(List original, List revised) throws DiffException { + public List diff(List original, List revised, DiffAlgorithmListener progress) throws DiffException { Objects.requireNonNull(original, "original list must not be null"); Objects.requireNonNull(revised, "revised 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<>(), new DataList<>(original), new DataList<>(revised))); + diffList.addAll(new org.eclipse.jgit.diff.HistogramDiff().diff(new DataListComparator<>(progress), new DataList<>(original), new DataList<>(revised))); List patch = new ArrayList<>(); for (Edit edit : diffList) { DeltaType type = DeltaType.EQUAL; @@ -56,14 +61,26 @@ public class HistogramDiff implements DiffAlgorithm { } 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; + + 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)); } diff --git a/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java b/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java index ec76ec3..e3a3d69 100644 --- a/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -21,6 +21,7 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.algorithm.DifferentiationFailedException; import com.github.difflib.patch.DeltaType; @@ -53,24 +54,31 @@ public final class MyersDiff implements DiffAlgorithm { * Return empty diff if get the error while procession the difference. */ @Override - public List diff(final List original, final List revised) throws DiffException { + public List diff(final List original, final List revised, DiffAlgorithmListener progress) throws DiffException { Objects.requireNonNull(original, "original list must not be null"); Objects.requireNonNull(revised, "revised list must not be null"); - PathNode path = buildPath(original, revised); - return buildRevision(path, original, revised); + if (progress != null) { + progress.diffStart(); + } + PathNode path = buildPath(original, revised, progress); + List result = buildRevision(path, original, revised); + 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. + * 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) + private PathNode buildPath(final List orig, final List rev, DiffAlgorithmListener progress) throws DifferentiationFailedException { Objects.requireNonNull(orig, "original sequence is null"); Objects.requireNonNull(rev, "revised sequence is null"); @@ -86,6 +94,9 @@ public final class MyersDiff implements DiffAlgorithm { 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; @@ -135,7 +146,8 @@ public final class MyersDiff implements DiffAlgorithm { * @param orig The original sequence. * @param rev The revised sequence. * @return A {@link Patch} script corresponding to the path. - * @throws DifferentiationFailedException if a {@link Patch} could not be built from the given path. + * @throws DifferentiationFailedException if a {@link Patch} could not be built from the given + * path. */ private List buildRevision(PathNode actualPath, List orig, List rev) { Objects.requireNonNull(actualPath, "path is null"); diff --git a/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java b/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java index ec44ade..0d6a3e4 100644 --- a/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java +++ b/src/test/java/com/github/difflib/algorithm/jgit/HistogramDiffTest.java @@ -15,9 +15,11 @@ */ package com.github.difflib.algorithm.jgit; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; 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 org.junit.After; @@ -59,7 +61,7 @@ public class HistogramDiffTest { public void testDiff() throws DiffException, 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().diff(orgList, revList)); + final Patch patch = Patch.generate(orgList, revList, new HistogramDiff().diff(orgList, revList, null)); System.out.println(patch); assertNotNull(patch); assertEquals(3, patch.getDeltas().size()); @@ -68,4 +70,38 @@ public class HistogramDiffTest { List patched = patch.applyTo(orgList); assertEquals(revList, patched); } + + @Test + public void testDiffWithListener() throws DiffException, 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().diff(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 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); + + System.out.println(logdata); + assertEquals(17, logdata.size()); + } } diff --git a/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java b/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java index 93e2950..54311be 100644 --- a/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java +++ b/src/test/java/com/github/difflib/algorithm/jgit/LRHistogramDiffTest.java @@ -17,10 +17,12 @@ package com.github.difflib.algorithm.jgit; import static com.github.difflib.DiffUtilsTest.readStringListFromInputStream; import com.github.difflib.TestConstants; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.zip.ZipFile; import org.junit.After; @@ -61,12 +63,30 @@ public class LRHistogramDiffTest { List original = readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))); List revised = readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb"))); - Patch patch = Patch.generate(original, revised, new HistogramDiff().diff(original, revised)); + List logdata = new ArrayList<>(); + Patch patch = Patch.generate(original, revised, new HistogramDiff().diff(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"); + } + })); assertEquals(34, patch.getDeltas().size()); List created = patch.applyTo(original); assertArrayEquals(revised.toArray(), created.toArray()); + + assertEquals(50, logdata.size()); } } diff --git a/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java b/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java index 9b911ff..b9d12ce 100644 --- a/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java +++ b/src/test/java/com/github/difflib/algorithm/myers/MyersDiffTest.java @@ -15,8 +15,10 @@ */ package com.github.difflib.algorithm.myers; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.patch.Patch; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @@ -32,10 +34,40 @@ public class MyersDiffTest { public void testDiffMyersExample1Forward() throws DiffException { 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().diff(original, revised)); + final Patch patch = Patch.generate(original, revised, new MyersDiff().diff(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() throws DiffException { + 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().diff(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); + 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()); + } }