Skip to content
Open
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