Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,14 @@ nbproject/
target/

*.iml

# Assignment docs and generated reports (not part of library source)
docs/
*.docx
*.pdf
*.py

# OS artifacts
.DS_Store
Thumbs.db
desktop.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.difflib;

import com.github.difflib.algorithm.DiffAlgorithmFactory;
import com.github.difflib.algorithm.myers.MyersDiff;

/**
* Default algorithm configuration for DiffUtils.
*/
public final class DiffAlgorithmDefaults {
public static DiffAlgorithmFactory getDefault() {
return MyersDiff.factory();
}

private DiffAlgorithmDefaults() {}
}
75 changes: 25 additions & 50 deletions java-diff-utils/src/main/java/com/github/difflib/DiffUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,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,
Expand All @@ -18,26 +18,22 @@
import com.github.difflib.algorithm.DiffAlgorithmFactory;
import com.github.difflib.algorithm.DiffAlgorithmI;
import com.github.difflib.algorithm.DiffAlgorithmListener;
import com.github.difflib.algorithm.myers.MyersDiff;
import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.Patch;
import com.github.difflib.patch.PatchFailedException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;

/**
* Utility class to implement the difference and patching engine.
* Utility class to implement the difference engine.
*/
public final class DiffUtils {

/**
* This factory generates the DEFAULT_DIFF algorithm for all these routines.
*/
static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory();
static DiffAlgorithmFactory DEFAULT_DIFF = DiffAlgorithmDefaults.getDefault();

/**
* Sets the default diff algorithm factory to be used by all diff routines.
Expand All @@ -59,7 +55,7 @@ public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory)
*/
public static <T> Patch<T> diff(
List<? extends T> original, List<? extends T> revised, DiffAlgorithmListener progress) {
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress);
return diff(original, revised, DEFAULT_DIFF.create(), progress);
}

/**
Expand All @@ -71,7 +67,7 @@ public static <T> Patch<T> diff(
* @return The patch describing the difference between the original and revised sequences. Never {@code null}.
*/
public static <T> Patch<T> diff(List<? extends T> original, List<? extends T> revised) {
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null);
return diff(original, revised, DEFAULT_DIFF.create(), null);
}

/**
Expand All @@ -84,7 +80,7 @@ public static <T> Patch<T> diff(List<? extends T> original, List<? extends T> re
* @return The patch describing the difference between the original and revised sequences. Never {@code null}.
*/
public static <T> Patch<T> diff(List<? extends T> original, List<? extends T> revised, boolean includeEqualParts) {
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts);
return diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts);
}

/**
Expand All @@ -96,7 +92,7 @@ public static <T> Patch<T> diff(List<? extends T> original, List<? extends T> re
* @return The patch describing the difference between the original and revised strings. Never {@code null}.
*/
public static Patch<String> diff(String sourceText, String targetText, DiffAlgorithmListener progress) {
return DiffUtils.diff(Arrays.asList(sourceText.split("\n")), Arrays.asList(targetText.split("\n")), progress);
return diff(Arrays.asList(sourceText.split("\n")), Arrays.asList(targetText.split("\n")), progress);
}

/**
Expand All @@ -114,9 +110,9 @@ public static Patch<String> diff(String sourceText, String targetText, DiffAlgor
public static <T> Patch<T> diff(
List<? extends T> source, List<? extends T> target, BiPredicate<? super T, ? super T> equalizer) {
if (equalizer != null) {
return DiffUtils.diff(source, target, DEFAULT_DIFF.create(equalizer));
return diff(source, target, DEFAULT_DIFF.create(equalizer));
}
return DiffUtils.diff(source, target, new MyersDiff<>());
return diff(source, target, DEFAULT_DIFF.create());
}

public static <T> Patch<T> diff(
Expand Down Expand Up @@ -168,61 +164,40 @@ public static <T> Patch<T> diff(
}

/**
* Computes the difference between the given texts inline. This one uses the
* "trick" to make out of texts lists of characters, like DiffRowGenerator
* does and merges those changes at the end together again.
* Computes the difference between the given texts inline. Splits the texts
* into tokens and delegates to the default diff algorithm.
*
* @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}.
* @param original the original text. Must not be {@code null}.
* @param revised the revised text. Must not be {@code null}.
* @return The patch describing the difference between the original and revised texts.
*/
public static Patch<String> diffInline(String original, String revised) {
List<String> origList = new ArrayList<>();
List<String> revList = new ArrayList<>();
for (Character character : original.toCharArray()) {
origList.add(character.toString());
}
for (Character character : revised.toCharArray()) {
revList.add(character.toString());
}
Patch<String> patch = DiffUtils.diff(origList, revList);
for (AbstractDelta<String> delta : patch.getDeltas()) {
delta.getSource().setLines(compressLines(delta.getSource().getLines(), ""));
delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), ""));
}
return patch;
return InlineDiffUtils.diffInline(original, revised);
}

/**
* 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.
* @param <T> the type of elements in the lists.
* @param original the original list. Must not be {@code null}.
* @param patch the patch to apply. Must not be {@code null}.
* @return the revised list.
* @throws PatchFailedException if the patch cannot be applied.
*/
public static <T> List<T> patch(List<? extends T> original, Patch<T> patch) throws PatchFailedException {
return patch.applyTo(original);
return PatchUtils.patch(original, patch);
}

/**
* Applies the given patch to the revised list and returns the original list.
* Applies the given patch in reverse 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.
* @param <T> the type of elements in the lists.
* @param revised the revised list. Must not be {@code null}.
* @param patch the patch to reverse-apply. Must not be {@code null}.
* @return the reconstructed original list.
*/
public static <T> List<T> unpatch(List<? extends T> revised, Patch<T> patch) {
return patch.restore(revised);
}

private static List<String> compressLines(List<String> lines, String delimiter) {
if (lines.isEmpty()) {
return Collections.emptyList();
}
return Collections.singletonList(String.join(delimiter, lines));
return PatchUtils.unpatch(revised, patch);
}

private DiffUtils() {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.github.difflib;

import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.Patch;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Utility class to implement inline character-level differences.
*/
public final class InlineDiffUtils {

/**
* 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<String> diffInline(String original, String revised) {
List<String> origList = new ArrayList<>();
List<String> revList = new ArrayList<>();
for (Character character : original.toCharArray()) {
origList.add(character.toString());
}
for (Character character : revised.toCharArray()) {
revList.add(character.toString());
}
Patch<String> patch = DiffUtils.diff(origList, revList);
for (AbstractDelta<String> delta : patch.getDeltas()) {
delta.getSource().setLines(compressLines(delta.getSource().getLines(), ""));
delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), ""));
}
return patch;
}

private static List<String> compressLines(List<String> lines, String delimiter) {
if (lines.isEmpty()) {
return Collections.emptyList();
}
return Collections.singletonList(String.join(delimiter, lines));
}

private InlineDiffUtils() {}
}
37 changes: 37 additions & 0 deletions java-diff-utils/src/main/java/com/github/difflib/PatchUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.github.difflib;

import com.github.difflib.patch.Patch;
import com.github.difflib.patch.PatchFailedException;
import java.util.List;

/**
* Utility class to implement the patching engine.
*/
public final class PatchUtils {

/**
* 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 Patch} representing the patch to apply.
* @return the revised list.
* @throws PatchFailedException if the patch cannot be applied.
*/
public static <T> List<T> patch(List<? extends T> original, Patch<T> 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 <T> List<T> unpatch(List<? extends T> revised, Patch<T> patch) {
return patch.restore(revised);
}

private PatchUtils() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.github.difflib.text;

import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.ChangeDelta;
import com.github.difflib.patch.Chunk;
import com.github.difflib.patch.DeleteDelta;
import com.github.difflib.patch.DeltaType;
import com.github.difflib.patch.InsertDelta;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Utility that normalises asymmetric {@link ChangeDelta}s into equal-size pairs
* so that DiffRow building stays simple.
*
* <p>When a CHANGE delta has a different number of source and target lines it is
* split into a same-size {@link ChangeDelta} followed by either an {@link InsertDelta}
* or a {@link DeleteDelta} for the surplus lines.
*/
public final class DeltaDecompressor {

private DeltaDecompressor() {}

/**
* Decompresses a {@link ChangeDelta} whose source and target sizes differ into
* a same-size {@link ChangeDelta} plus a trailing {@link InsertDelta} or
* {@link DeleteDelta}. All other delta types are returned unchanged in a
* singleton list.
*
* @param delta the delta to (possibly) decompress. Must not be {@code null}.
* @return a list containing the original delta, or the two replacement deltas.
*/
public static List<AbstractDelta<String>> decompress(AbstractDelta<String> delta) {
if (delta.getType() == DeltaType.CHANGE
&& delta.getSource().size() != delta.getTarget().size()) {
List<AbstractDelta<String>> deltas = new ArrayList<>();

int minSize = Math.min(delta.getSource().size(), delta.getTarget().size());
Chunk<String> orig = delta.getSource();
Chunk<String> rev = delta.getTarget();

deltas.add(new ChangeDelta<String>(
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<String>(
new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()),
new Chunk<>(
rev.getPosition() + minSize,
rev.getLines().subList(minSize, rev.getLines().size()))));
} else {
deltas.add(new DeleteDelta<String>(
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);
}
}
Loading
Loading