diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..057f67a4 --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,17 @@ +### Expected Behavior + + +### Actual Behavior + + +### Steps to Reproduce the Problem + + 1. + 1. + 1. + +### Specifications + + - Version: + - Platform: + - Subsystem: diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README.md b/README.md index be31df25..df31d84b 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ ## 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) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.wumpz/diffutils/badge.svg)](http://maven-badges.herokuapp.com/maven-central/com.github.wumpz/diffutils) + ## 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. @@ -50,7 +52,16 @@ This is a test ~senctence~**for diffutils**. 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. ### Changelog ### - * Version 2.1-SNAPSHOT + * Version 3.0-SNAPSHOT + * changed generation of inline diffes, if there are different linefeeds within one diff, then these are excluded + from the diff block. + * Due to licensing issues Delta.java and DiffAlgorithm.java were removed. + * Version 2.3-SNAPSHOT + * Introduced a process listener to diff algorithms. For long running + diffs one could implement some progress information. + * automatic module name for JDK 9 and higher usage + * Version 2.2 + * released at maven central * included checkstyle source code conventions * groupid changed to **com.github.wumpz**, due to maven central releasing * allow configurable splitting of lines to define the blocks to compare (words, characters, phrases). @@ -72,7 +83,7 @@ But it can easily replaced by any other which is better for handing your texts. ## Source Code conventions -Recently a checkstyle process was integrated into the build process. JSqlParser 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, @@ -92,13 +103,16 @@ This is a valid piece of source code: ### To Install ### -**This jar is not yet to get at maven central.** - Just add the code below to your maven dependencies: ``` com.github.wumpz diffutils -    2.0-SNAPSHOT +    2.2 ``` +or using gradle: +``` +// https://mvnrepository.com/artifact/com.github.wumpz/diffutils +compile group: 'com.github.wumpz', name: 'diffutils', version: '2.2' +``` diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..2f7efbea --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/nb-configuration.xml b/nb-configuration.xml index f5e3165a..03683cb3 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -1,22 +1,23 @@ - - - - none - false - true - LF - false - + none + false + true + LF + false + JDK_1.8 + diff --git a/pom.xml b/pom.xml index 87d136e0..081b7949 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ com.github.wumpz diffutils jar - 2.2 + 3.0 java-diff-utils The DiffUtils library for computing diffs, applying patches, generationg side-by-side view in Java. https://github.com/wumpz/java-diff-utils @@ -24,7 +24,7 @@ scm:git:https://github.com/wumpz/java-diff-utils.git scm:git:ssh://git@github.com:wumpz/java-diff-utils.git https://github.com/wumpz/java-diff-utils.git - diffutils-2.2 + diffutils-3.0 @@ -129,6 +129,10 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + com.github.wumpz.diffutils + diff --git a/src/main/java/com/github/difflib/DiffUtils.java b/src/main/java/com/github/difflib/DiffUtils.java index 807d7b4a..f2c8eb19 100644 --- a/src/main/java/com/github/difflib/DiffUtils.java +++ b/src/main/java/com/github/difflib/DiffUtils.java @@ -19,10 +19,11 @@ */ package com.github.difflib; -import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmI; +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; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import com.github.difflib.patch.PatchFailedException; import java.util.ArrayList; @@ -37,7 +38,6 @@ * Implements the difference and patching engine * * @author Dmitry Naumenko - * @version 0.4.1 */ public final class DiffUtils { @@ -46,36 +46,45 @@ 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 sourceText, String targetText, + DiffAlgorithmListener progress) throws DiffException { + 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 original The original text. Must not be {@code null}. - * @param revised The revised text. Must not be {@code null}. + * @param source The original text. Must not be {@code null}. + * @param target The revised text. Must not be {@code null}. * * @param equalizer 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 original, List revised, + public static Patch diff(List source, List target, BiPredicate equalizer) throws DiffException { if (equalizer != null) { - return DiffUtils.diff(original, revised, + return DiffUtils.diff(source, target, new MyersDiff<>(equalizer)); } - return DiffUtils.diff(original, revised, new MyersDiff<>()); + return DiffUtils.diff(source, target, new MyersDiff<>()); } /** @@ -84,16 +93,30 @@ public static Patch diff(List original, List revised, * @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 { + DiffAlgorithmI 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.computeDiff(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, + DiffAlgorithmI 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 @@ -113,9 +136,9 @@ public static Patch diffInline(String original, String revised) throws D revList.add(character.toString()); } Patch patch = DiffUtils.diff(origList, revList); - for (Delta delta : patch.getDeltas()) { - delta.getOriginal().setLines(compressLines(delta.getOriginal().getLines(), "")); - delta.getRevised().setLines(compressLines(delta.getRevised().getLines(), "")); + for (AbstractDelta delta : patch.getDeltas()) { + delta.getSource().setLines(compressLines(delta.getSource().getLines(), "")); + delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), "")); } return patch; } diff --git a/src/main/java/com/github/difflib/UnifiedDiffUtils.java b/src/main/java/com/github/difflib/UnifiedDiffUtils.java index 1395c795..fe5b6f69 100644 --- a/src/main/java/com/github/difflib/UnifiedDiffUtils.java +++ b/src/main/java/com/github/difflib/UnifiedDiffUtils.java @@ -17,7 +17,7 @@ import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; -import com.github.difflib.patch.Delta; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import java.util.ArrayList; import java.util.List; @@ -130,37 +130,37 @@ public static Patch parseUnifiedDiff(List diff) { * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format text representing * the Patch. * - * @param original - Filename of the original (unrevised file) - * @param revised - Filename of the revised file + * @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. * @author Bill James (tankerbay@gmail.com) */ - public static List generateUnifiedDiff(String original, - String revised, List originalLines, Patch patch, + public static List generateUnifiedDiff(String originalFileName, + String revisedFileName, List originalLines, Patch patch, int contextSize) { if (!patch.getDeltas().isEmpty()) { List ret = new ArrayList<>(); - ret.add("--- " + original); - ret.add("+++ " + revised); + ret.add("--- " + originalFileName); + ret.add("+++ " + revisedFileName); - List> patchDeltas = new ArrayList<>( + List> patchDeltas = new ArrayList<>( patch.getDeltas()); // code outside the if block also works for single-delta issues. - List> deltas = new ArrayList<>(); // current + List> deltas = new ArrayList<>(); // current // list // of // Delta's to // process - Delta delta = patchDeltas.get(0); + 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.getOriginal().getPosition(); // store + int position = delta.getSource().getPosition(); // store // the // current // position @@ -170,9 +170,9 @@ public static List generateUnifiedDiff(String original, // Check if the next Delta is too close to the current // position. // And if it is, add it to the current set - Delta nextDelta = patchDeltas.get(i); - if ((position + delta.getOriginal().size() + contextSize) >= (nextDelta - .getOriginal().getPosition() - contextSize)) { + 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, @@ -207,33 +207,33 @@ public static List generateUnifiedDiff(String original, * @author Bill James (tankerbay@gmail.com) */ private static List processDeltas(List origLines, - List> deltas, int contextSize) { + List> deltas, int contextSize) { 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; - Delta curDelta = deltas.get(0); + AbstractDelta curDelta = deltas.get(0); // NOTE: +1 to overcome the 0-offset Position - int origStart = curDelta.getOriginal().getPosition() + 1 - contextSize; + int origStart = curDelta.getSource().getPosition() + 1 - contextSize; if (origStart < 1) { origStart = 1; } - int revStart = curDelta.getRevised().getPosition() + 1 - contextSize; + int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; if (revStart < 1) { revStart = 1; } // find the start of the wrapper context code - int contextStart = curDelta.getOriginal().getPosition() - contextSize; + 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.getOriginal().getPosition(); line++) { // + for (line = contextStart; line < curDelta.getSource().getPosition(); line++) { // buffer.add(" " + origLines.get(line)); origTotal++; revTotal++; @@ -241,15 +241,15 @@ private static List processDeltas(List origLines, // output the first Delta buffer.addAll(getDeltaText(curDelta)); - origTotal += curDelta.getOriginal().getLines().size(); - revTotal += curDelta.getRevised().getLines().size(); + origTotal += curDelta.getSource().getLines().size(); + revTotal += curDelta.getTarget().getLines().size(); int deltaIndex = 1; while (deltaIndex < deltas.size()) { // for each of the other Deltas - Delta nextDelta = deltas.get(deltaIndex); - int intermediateStart = curDelta.getOriginal().getPosition() - + curDelta.getOriginal().getLines().size(); - for (line = intermediateStart; line < nextDelta.getOriginal() + 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)); @@ -257,15 +257,15 @@ private static List processDeltas(List origLines, revTotal++; } buffer.addAll(getDeltaText(nextDelta)); // output the Delta - origTotal += nextDelta.getOriginal().getLines().size(); - revTotal += nextDelta.getRevised().getLines().size(); + 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.getOriginal().getPosition() - + curDelta.getOriginal().getLines().size(); + contextStart = curDelta.getSource().getPosition() + + curDelta.getSource().getLines().size(); for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { buffer.add(" " + origLines.get(line)); @@ -275,7 +275,7 @@ private static List processDeltas(List origLines, // Create and insert the block header, conforming to the Unified Diff // standard - StringBuffer header = new StringBuffer(); + StringBuilder header = new StringBuilder(); header.append("@@ -"); header.append(origStart); header.append(","); @@ -297,12 +297,12 @@ private static List processDeltas(List origLines, * @return list of String lines of code. * @author Bill James (tankerbay@gmail.com) */ - private static List getDeltaText(Delta delta) { + private static List getDeltaText(AbstractDelta delta) { List buffer = new ArrayList<>(); - for (String line : delta.getOriginal().getLines()) { + for (String line : delta.getSource().getLines()) { buffer.add("-" + line); } - for (String line : delta.getRevised().getLines()) { + for (String line : delta.getTarget().getLines()) { buffer.add("+" + line); } return buffer; diff --git a/src/main/java/com/github/difflib/algorithm/Change.java b/src/main/java/com/github/difflib/algorithm/Change.java index b90e2022..57fbb788 100644 --- a/src/main/java/com/github/difflib/algorithm/Change.java +++ b/src/main/java/com/github/difflib/algorithm/Change.java @@ -19,7 +19,7 @@ /** * - * @author toben + * @author Tobias Warneke */ public class Change { diff --git a/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java b/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java deleted file mode 100644 index b97a15a4..00000000 --- a/src/main/java/com/github/difflib/algorithm/DiffAlgorithm.java +++ /dev/null @@ -1,54 +0,0 @@ -/*- - * #%L - * java-diff-utils - * %% - * Copyright (C) 2009 - 2017 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. - * #L% - */ -package com.github.difflib.algorithm; - -import com.github.difflib.patch.Patch; -import java.util.*; - -/** - * The general interface for computing diffs between two lists of elements of type T. - * - * @author Dmitry Naumenko - * @param T The type of the compared elements in the 'lines'. - */ -public interface DiffAlgorithm { - - /** - * Computes the difference between the original sequence and the revised sequence and returns it as a {@link Patch} - * object. - * - * @param original The original sequence. Must not be {@code null}. - * @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)); - } - - /** - * Computes the difference between the original sequence and the revised sequence and returns it as a {@link Patch} - * object. - * - * @param original The original sequence. Must not be {@code null}. - * @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; -} diff --git a/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java b/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java new file mode 100644 index 00000000..d9236290 --- /dev/null +++ b/src/main/java/com/github/difflib/algorithm/DiffAlgorithmI.java @@ -0,0 +1,51 @@ +/* + * 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; + +import java.util.Arrays; +import java.util.List; + +/** + * Interface of a diff algorithm. + * + * @author Tobias Warneke (t.warneke@gmx.net) + */ +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 + * @throws DiffException + */ + List computeDiff(List source, List target, DiffAlgorithmListener progress) throws DiffException; + + /** + * Simple extension to compute a changeset using arrays. + * + * @param source + * @param target + * @param progress + * @return + * @throws com.github.difflib.algorithm.DiffException + */ + default List computeDiff(T[] source, T[] target, DiffAlgorithmListener progress) throws DiffException { + return computeDiff(Arrays.asList(source), Arrays.asList(target), progress); + } +} 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 00000000..37d51813 --- /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 6ca80103..c2e941f2 100644 --- a/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java +++ b/src/main/java/com/github/difflib/algorithm/jgit/HistogramDiff.java @@ -16,7 +16,8 @@ package com.github.difflib.algorithm.jgit; import com.github.difflib.algorithm.Change; -import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmI; +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.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 { +public class HistogramDiff implements DiffAlgorithmI { @Override - public List diff(List original, List revised) throws DiffException { - Objects.requireNonNull(original, "original list must not be null"); - Objects.requireNonNull(revised, "revised list must not be null"); + public List computeDiff(List source, List target, DiffAlgorithmListener progress) throws DiffException { + 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<>(), new DataList<>(original), new DataList<>(revised))); + 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; @@ -56,14 +61,26 @@ public List diff(List original, List revised) throws DiffException } 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 ec76ec3f..00d22359 100644 --- a/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java +++ b/src/main/java/com/github/difflib/algorithm/myers/MyersDiff.java @@ -20,7 +20,8 @@ package com.github.difflib.algorithm.myers; import com.github.difflib.algorithm.Change; -import com.github.difflib.algorithm.DiffAlgorithm; +import com.github.difflib.algorithm.DiffAlgorithmI; +import com.github.difflib.algorithm.DiffAlgorithmListener; import com.github.difflib.algorithm.DiffException; import com.github.difflib.algorithm.DifferentiationFailedException; import com.github.difflib.patch.DeltaType; @@ -33,7 +34,7 @@ /** * A clean-room implementation of Eugene Myers greedy differencing algorithm. */ -public final class MyersDiff implements DiffAlgorithm { +public final class MyersDiff implements DiffAlgorithmI { private final BiPredicate DEFAULT_EQUALIZER = Object::equals; private final BiPredicate equalizer; @@ -53,24 +54,31 @@ public MyersDiff(final BiPredicate equalizer) { * Return empty diff if get the error while procession the difference. */ @Override - public List diff(final List original, final List revised) throws DiffException { - Objects.requireNonNull(original, "original list must not be null"); - Objects.requireNonNull(revised, "revised list must not be null"); + public List computeDiff(final List source, final List target, DiffAlgorithmListener progress) throws DiffException { + Objects.requireNonNull(source, "source list must not be null"); + Objects.requireNonNull(target, "target list must not be null"); - PathNode path = buildPath(original, revised); - return buildRevision(path, original, revised); + 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. + * 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 @@ private PathNode buildPath(final List orig, final List rev) 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 @@ private PathNode buildPath(final List orig, final List rev) * @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"); @@ -165,18 +177,7 @@ private List buildRevision(PathNode actualPath, List orig, List re } else { changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j)); } -// Chunk original = new Chunk<>(ianchor, copyOfRange(orig, ianchor, i)); -// Chunk revised = new Chunk<>(janchor, copyOfRange(rev, janchor, j)); -// Delta delta = null; -// if (original.size() == 0 && revised.size() != 0) { -// delta = new InsertDelta<>(original, revised); -// } else if (original.size() > 0 && revised.size() == 0) { -// delta = new DeleteDelta<>(original, revised); -// } else { -// delta = new ChangeDelta<>(original, revised); -// } -// -// patch.addDelta(delta); + if (path.isSnake()) { path = path.prev; } diff --git a/src/main/java/com/github/difflib/patch/AbstractDelta.java b/src/main/java/com/github/difflib/patch/AbstractDelta.java new file mode 100644 index 00000000..30de287c --- /dev/null +++ b/src/main/java/com/github/difflib/patch/AbstractDelta.java @@ -0,0 +1,96 @@ +/* + * 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.patch; + +import java.util.List; +import java.util.Objects; + +/** + * Abstract delta between a source and a target. + * @author Tobias Warneke (t.warneke@gmx.net) + */ +public abstract class AbstractDelta { + 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; + } + + public Chunk getSource() { + return source; + } + + public Chunk getTarget() { + return target; + } + + public DeltaType getType() { + return type; + } + + /** + * Verify the chunk of this delta, to fit the target. + * @param target + * @throws PatchFailedException + */ + protected void verifyChunk(List target) throws PatchFailedException { + getSource().verify(target); + } + + public abstract void applyTo(List target) throws PatchFailedException; + + public abstract void restore(List target); + + @Override + public int hashCode() { + int hash = 3; + hash = 61 * hash + Objects.hashCode(this.source); + hash = 61 * hash + Objects.hashCode(this.target); + hash = 61 * hash + Objects.hashCode(this.type); + return hash; + } + + @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; + } + if (this.type != other.type) { + return false; + } + return true; + } +} diff --git a/src/main/java/com/github/difflib/patch/ChangeDelta.java b/src/main/java/com/github/difflib/patch/ChangeDelta.java index 34a8bd6b..eb67606b 100644 --- a/src/main/java/com/github/difflib/patch/ChangeDelta.java +++ b/src/main/java/com/github/difflib/patch/ChangeDelta.java @@ -20,35 +20,38 @@ package com.github.difflib.patch; import java.util.List; +import java.util.Objects; /** * Describes the change-delta between original and revised texts. * * @author Dmitry Naumenko - * @param T The type of the compared elements in the 'lines'. + * @param T The type of the compared elements in the data 'lines'. */ -public final class ChangeDelta extends Delta { +public final class ChangeDelta 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}. + * @param source The source chunk. Must not be {@code null}. + * @param target The target chunk. Must not be {@code null}. */ - public ChangeDelta(Chunk original, Chunk revised) { - super(DeltaType.CHANGE, original, revised); + 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 public void applyTo(List target) throws PatchFailedException { - verify(target); - int position = getOriginal().getPosition(); - int size = getOriginal().size(); + verifyChunk(target); + int position = getSource().getPosition(); + int size = getSource().size(); for (int i = 0; i < size; i++) { target.remove(position); } int i = 0; - for (T line : getRevised().getLines()) { + for (T line : getTarget().getLines()) { target.add(position + i, line); i++; } @@ -56,13 +59,13 @@ public void applyTo(List target) throws PatchFailedException { @Override public void restore(List target) { - int position = getRevised().getPosition(); - int size = getRevised().size(); + int position = getTarget().getPosition(); + int size = getTarget().size(); for (int i = 0; i < size; i++) { target.remove(position); } int i = 0; - for (T line : getOriginal().getLines()) { + for (T line : getSource().getLines()) { target.add(position + i, line); i++; } @@ -70,7 +73,7 @@ public void restore(List target) { @Override public String toString() { - return "[ChangeDelta, position: " + getOriginal().getPosition() + ", lines: " - + getOriginal().getLines() + " to " + getRevised().getLines() + "]"; + return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + " to " + getTarget().getLines() + "]"; } } diff --git a/src/main/java/com/github/difflib/patch/Chunk.java b/src/main/java/com/github/difflib/patch/Chunk.java index 5855c3f1..3812be09 100644 --- a/src/main/java/com/github/difflib/patch/Chunk.java +++ b/src/main/java/com/github/difflib/patch/Chunk.java @@ -65,6 +65,7 @@ public Chunk(int position, T[] lines) { * 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 void verify(List target) throws PatchFailedException { if (position > target.size() || last() > target.size()) { diff --git a/src/main/java/com/github/difflib/patch/DeleteDelta.java b/src/main/java/com/github/difflib/patch/DeleteDelta.java index 0ef14e91..d7417246 100644 --- a/src/main/java/com/github/difflib/patch/DeleteDelta.java +++ b/src/main/java/com/github/difflib/patch/DeleteDelta.java @@ -27,7 +27,7 @@ * @author Dmitry Naumenko * @param T The type of the compared elements in the 'lines'. */ -public final class DeleteDelta extends Delta { +public final class DeleteDelta extends AbstractDelta { /** * Creates a change delta with the two given chunks. @@ -41,9 +41,9 @@ public DeleteDelta(Chunk original, Chunk revised) { @Override public void applyTo(List target) throws PatchFailedException { - verify(target); - int position = getOriginal().getPosition(); - int size = getOriginal().size(); + verifyChunk(target); + int position = getSource().getPosition(); + int size = getSource().size(); for (int i = 0; i < size; i++) { target.remove(position); } @@ -51,8 +51,8 @@ public void applyTo(List target) throws PatchFailedException { @Override public void restore(List target) { - int position = this.getRevised().getPosition(); - List lines = this.getOriginal().getLines(); + 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)); } @@ -60,7 +60,7 @@ public void restore(List target) { @Override public String toString() { - return "[DeleteDelta, position: " + getOriginal().getPosition() + ", lines: " - + getOriginal().getLines() + "]"; + return "[DeleteDelta, position: " + getSource().getPosition() + ", lines: " + + getSource().getLines() + "]"; } } diff --git a/src/main/java/com/github/difflib/patch/Delta.java b/src/main/java/com/github/difflib/patch/Delta.java deleted file mode 100644 index 657a8f4f..00000000 --- a/src/main/java/com/github/difflib/patch/Delta.java +++ /dev/null @@ -1,133 +0,0 @@ -/*- - * #%L - * java-diff-utils - * %% - * Copyright (C) 2009 - 2017 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. - * #L% - */ -package com.github.difflib.patch; - -import java.util.*; - -/** - * Describes the delta between original and revised texts. - * - * @author Dmitry Naumenko - * @param T The type of the compared elements in the 'lines'. - */ -public abstract class Delta { - - private final DeltaType deltaType; - private final Chunk original; - private final Chunk revised; - - /** - * Construct the delta for original and revised chunks - * - * @param original Chunk describing the original text. Must not be {@code null}. - * @param revised Chunk describing the revised text. Must not be {@code null}. - */ - public Delta(DeltaType deltaType, Chunk original, Chunk revised) { - Objects.requireNonNull(deltaType, "deltaType must not be null"); - Objects.requireNonNull(original, "original must not be null"); - Objects.requireNonNull(revised, "revised must not be null"); - - this.deltaType = deltaType; - this.original = original; - this.revised = revised; - } - - /** - * Verifies that this delta can be used to patch the given text. - * - * @param target the text to patch. - * @throws PatchFailedException if the patch cannot be applied. - */ - public void verify(List target) throws PatchFailedException { - getOriginal().verify(target); - } - - /** - * Applies this delta as the patch for a given target - * - * @param target the given target - * @throws PatchFailedException - */ - public abstract void applyTo(List target) throws PatchFailedException; - - /** - * Cancel this delta for a given revised text. The action is opposite to patch. - * - * @param target the given revised text - */ - public abstract void restore(List target); - - public final DeltaType getType() { - return deltaType; - } - - /** - * @return The Chunk describing the original text. - */ - public Chunk getOriginal() { - return original; - } - - /** - * @return The Chunk describing the revised text. - */ - public Chunk getRevised() { - return revised; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((original == null) ? 0 : original.hashCode()); - result = prime * result + ((revised == null) ? 0 : revised.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Delta other = (Delta) obj; - if (original == null) { - if (other.original != null) { - return false; - } - } else if (!original.equals(other.original)) { - return false; - } - if (revised == null) { - if (other.revised != null) { - return false; - } - } else if (!revised.equals(other.revised)) { - return false; - } - return true; - } - -} diff --git a/src/main/java/com/github/difflib/patch/InsertDelta.java b/src/main/java/com/github/difflib/patch/InsertDelta.java index d6656e02..3cb40dde 100644 --- a/src/main/java/com/github/difflib/patch/InsertDelta.java +++ b/src/main/java/com/github/difflib/patch/InsertDelta.java @@ -27,7 +27,7 @@ * @author Dmitry Naumenko * @param T The type of the compared elements in the 'lines'. */ -public final class InsertDelta extends Delta { +public final class InsertDelta extends AbstractDelta { /** * Creates an insert delta with the two given chunks. @@ -41,9 +41,9 @@ public InsertDelta(Chunk original, Chunk revised) { @Override public void applyTo(List target) throws PatchFailedException { - verify(target); - int position = this.getOriginal().getPosition(); - List lines = this.getRevised().getLines(); + verifyChunk(target); + 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)); } @@ -51,8 +51,8 @@ public void applyTo(List target) throws PatchFailedException { @Override public void restore(List target) { - int position = getRevised().getPosition(); - int size = getRevised().size(); + int position = getTarget().getPosition(); + int size = getTarget().size(); for (int i = 0; i < size; i++) { target.remove(position); } @@ -60,7 +60,7 @@ public void restore(List target) { @Override public String toString() { - return "[InsertDelta, position: " + getOriginal().getPosition() - + ", lines: " + getRevised().getLines() + "]"; + return "[InsertDelta, position: " + getSource().getPosition() + + ", lines: " + getTarget().getLines() + "]"; } } diff --git a/src/main/java/com/github/difflib/patch/Patch.java b/src/main/java/com/github/difflib/patch/Patch.java index ec7c357b..9bbb30dc 100644 --- a/src/main/java/com/github/difflib/patch/Patch.java +++ b/src/main/java/com/github/difflib/patch/Patch.java @@ -36,7 +36,7 @@ */ public final class Patch { - private final List> deltas; + private final List> deltas; public Patch() { this(10); @@ -54,9 +54,9 @@ public Patch(int estimatedPatchSize) { */ public List applyTo(List target) throws PatchFailedException { List result = new ArrayList<>(target); - ListIterator> it = getDeltas().listIterator(deltas.size()); + ListIterator> it = getDeltas().listIterator(deltas.size()); while (it.hasPrevious()) { - Delta delta = it.previous(); + AbstractDelta delta = it.previous(); delta.applyTo(result); } return result; @@ -70,9 +70,9 @@ public List applyTo(List target) throws PatchFailedException { */ public List restore(List target) { List result = new ArrayList<>(target); - ListIterator> it = getDeltas().listIterator(deltas.size()); + ListIterator> it = getDeltas().listIterator(deltas.size()); while (it.hasPrevious()) { - Delta delta = it.previous(); + AbstractDelta delta = it.previous(); delta.restore(result); } return result; @@ -83,7 +83,7 @@ public List restore(List target) { * * @param delta the given delta */ - public void addDelta(Delta delta) { + public void addDelta(AbstractDelta delta) { deltas.add(delta); } @@ -92,8 +92,8 @@ public void addDelta(Delta delta) { * * @return the deltas */ - public List> getDeltas() { - Collections.sort(deltas, comparing(d -> d.getOriginal().getPosition())); + public List> getDeltas() { + Collections.sort(deltas, comparing(d -> d.getSource().getPosition())); return deltas; } diff --git a/src/main/java/com/github/difflib/text/DiffRowGenerator.java b/src/main/java/com/github/difflib/text/DiffRowGenerator.java index 9df68481..8ded4253 100644 --- a/src/main/java/com/github/difflib/text/DiffRowGenerator.java +++ b/src/main/java/com/github/difflib/text/DiffRowGenerator.java @@ -21,10 +21,10 @@ import com.github.difflib.DiffUtils; import com.github.difflib.algorithm.DiffException; +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.Delta; import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; import com.github.difflib.text.DiffRow.Tag; @@ -47,17 +47,11 @@ * */ public class DiffRowGenerator { - public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#]"); - - public static final BiPredicate IGNORE_WHITESPACE_EQUALIZER = (original, revised) - -> original.trim().replaceAll("\\s+", " ").equals(revised.trim().replaceAll("\\s+", " ")); - + public static final BiPredicate DEFAULT_EQUALIZER = Object::equals; - - /** - * 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 BiPredicate IGNORE_WHITESPACE_EQUALIZER = (original, revised) + -> adjustWhitespace(original).equals(adjustWhitespace(revised)); /** * Splitting lines by character to achieve char by char diff checking. @@ -69,146 +63,94 @@ public class DiffRowGenerator { } return list; }; - - private final boolean showInlineDiffs; - private final boolean ignoreWhiteSpaces; - private final Function oldTag; - private final Function newTag; - private final Function> inlineDiffSplitter; - private final int columnWidth; - private final BiPredicate equalizer; - private final boolean mergeOriginalRevised; - private final boolean reportLinesUnchanged; - + public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#]"); /** - * This class used for building the DiffRowGenerator. - * - * @author dmitry - * + * Splitting lines by word to achieve word by word diff checking. */ - public static class Builder { - - private boolean showInlineDiffs = false; - private boolean ignoreWhiteSpaces = false; + public static final Function> SPLITTER_BY_WORD = line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN); + public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - private Function oldTag = f -> f ? "" : ""; - private Function newTag = f -> f ? "" : ""; + public static Builder create() { + return new Builder(); + } - private int columnWidth = 0; - private boolean mergeOriginalRevised = false; - private boolean reportLinesUnchanged = false; - private Function> inlineDiffSplitter = SPLITTER_BY_CHARACTER; + private static String adjustWhitespace(String raw) { + return WHITESPACE_PATTERN.matcher(raw.trim()).replaceAll(" "); + } - private Builder() { + 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; + } - /** - * 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; - } + /** + * 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 tag the tag name without angle brackets, just a word + * @param cssClass the optional css class + */ + static void wrapInTag(List sequence, int startPosition, + int endPosition, Function tagGenerator) { + int endPos = endPosition; - /** - * 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; - } + while (endPos >= startPosition) { - /** - * Give the originial old and new text lines to Diffrow without any additional processing. - * - * @param val the value to set. Default: false. - * @return builder with configured reportLinesUnWrapped parameter - */ - public Builder reportLinesUnchanged(final boolean val) { - reportLinesUnchanged = val; - return this; - } + //search position for end tag + while (endPos > startPosition) { + if (!"\n".equals(sequence.get(endPos - 1))) { + break; + } + endPos--; + } - /** - * Generator for Old-Text-Tags. - * - * @param tag the tag to set. Without angle brackets. Default: span. - * @return builder with configured ignoreBlankLines parameter - */ - public Builder oldTag(Function generator) { - this.oldTag = generator; - return this; - } + if (endPos == startPosition) { + break; + } - /** - * Generator for New-Text-Tags. - * - * @param generator - * @return - */ - public Builder newTag(Function generator) { - this.newTag = generator; - return this; - } + sequence.add(endPos, tagGenerator.apply(false)); + endPos--; - /** - * Set the column with of generated lines of original and revised texts. - * - * @param width the width to set. Making it < 0 doesn't have any sense. Default 80. @return builder with config - * ured ignoreBlankLines parameter - */ - public Builder columnWidth(int width) { - if (width >= 0) { - columnWidth = width; + //search position for end tag + while (endPos > startPosition) { + if ("\n".equals(sequence.get(endPos - 1))) { + break; + } + endPos--; } - 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); + sequence.add(endPos, tagGenerator.apply(true)); + endPos--; } - /** - * 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; - } - - /** - * Per default each character is separatly processed. This variant introduces processing by word, which should - * deliver no in word changes. - */ - public Builder inlineDiffByWord(boolean inlineDiffByWord) { - inlineDiffSplitter = inlineDiffByWord?SPLITTER_BY_WORD:SPLITTER_BY_CHARACTER; - return this; - } - - - public Builder inlineDiffBySplitter(Function> inlineDiffSplitter) { - this.inlineDiffSplitter = inlineDiffSplitter; - return this; - } +// sequence.add(endPosition, tagGenerator.apply(false)); +// sequence.add(startPosition, tagGenerator.apply(true)); } + private final int columnWidth; + private final BiPredicate equalizer; + private final boolean ignoreWhiteSpaces; + private final Function> inlineDiffSplitter; + private final boolean mergeOriginalRevised; + private final Function newTag; + private final Function oldTag; + private final boolean reportLinesUnchanged; - public static Builder create() { - return new Builder(); - } + private final boolean showInlineDiffs; private DiffRowGenerator(Builder builder) { showInlineDiffs = builder.showInlineDiffs; @@ -220,7 +162,7 @@ private DiffRowGenerator(Builder builder) { inlineDiffSplitter = builder.inlineDiffSplitter; equalizer = ignoreWhiteSpaces ? IGNORE_WHITESPACE_EQUALIZER : DEFAULT_EQUALIZER; reportLinesUnchanged = builder.reportLinesUnchanged; - + Objects.requireNonNull(inlineDiffSplitter); } @@ -236,42 +178,6 @@ public List generateDiffRows(List original, List revise return generateDiffRows(original, DiffUtils.diff(original, revised, equalizer)); } - private String preprocessLine(String line) { - if (columnWidth == 0) { - return StringUtils.normalize(line); - } else { - return StringUtils.wrapText(StringUtils.normalize(line), columnWidth); - } - } - - 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(true) + wrapOrg + oldTag.apply(false); - } - } - String wrapNew = preprocessLine(newline); - if (Tag.INSERT == type) { - if (mergeOriginalRevised) { - wrapOrg = newTag.apply(true) + wrapNew + newTag.apply(false); - } else if (showInlineDiffs) { - wrapNew = newTag.apply(true) + wrapNew + newTag.apply(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)); - } - /** * Generates the DiffRows describing the difference between original and revised texts using the given patch. Useful * for displaying side-by-side diff. @@ -284,11 +190,11 @@ private DiffRow buildDiffRowWithoutNormalizing(Tag type, String orgline, String public List generateDiffRows(final List original, Patch patch) throws DiffException { List diffRows = new ArrayList<>(); int endPos = 0; - final List> deltaList = patch.getDeltas(); + final List> deltaList = patch.getDeltas(); for (int i = 0; i < deltaList.size(); i++) { - Delta delta = deltaList.get(i); - Chunk orig = delta.getOriginal(); - Chunk rev = delta.getRevised(); + AbstractDelta delta = deltaList.get(i); + Chunk orig = delta.getSource(); + Chunk rev = delta.getTarget(); for (String line : original.subList(endPos, orig.getPosition())) { diffRows.add(buildDiffRow(Tag.EQUAL, line, line)); @@ -331,14 +237,42 @@ public List generateDiffRows(final List original, Patch return diffRows; } + 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(true) + wrapOrg + oldTag.apply(false); + } + } + String wrapNew = preprocessLine(newline); + if (Tag.INSERT == type) { + if (mergeOriginalRevised) { + wrapOrg = newTag.apply(true) + wrapNew + newTag.apply(false); + } else if (showInlineDiffs) { + wrapNew = newTag.apply(true) + wrapNew + newTag.apply(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)); + } + /** * Add the inline diffs for given delta * * @param delta the given delta */ - private List generateInlineDiffs(Delta delta) throws DiffException { - List orig = StringUtils.normalize(delta.getOriginal().getLines()); - List rev = StringUtils.normalize(delta.getRevised().getLines()); + private List generateInlineDiffs(AbstractDelta delta) throws DiffException { + List orig = StringUtils.normalize(delta.getSource().getLines()); + List rev = StringUtils.normalize(delta.getTarget().getLines()); List origList; List revList; String joinedOrig = String.join("\n", orig); @@ -347,26 +281,26 @@ private List generateInlineDiffs(Delta delta) throws DiffExcept origList = inlineDiffSplitter.apply(joinedOrig); revList = inlineDiffSplitter.apply(joinedRev); - List> inlineDeltas = DiffUtils.diff(origList, revList).getDeltas(); + List> inlineDeltas = DiffUtils.diff(origList, revList).getDeltas(); Collections.reverse(inlineDeltas); - for (Delta inlineDelta : inlineDeltas) { - Chunk inlineOrig = inlineDelta.getOriginal(); - Chunk inlineRev = inlineDelta.getRevised(); + for (AbstractDelta inlineDelta : inlineDeltas) { + Chunk inlineOrig = inlineDelta.getSource(); + Chunk inlineRev = inlineDelta.getTarget(); if (inlineDelta instanceof DeleteDelta) { wrapInTag(origList, inlineOrig.getPosition(), inlineOrig .getPosition() - + inlineOrig.size() + 1, oldTag); + + inlineOrig.size(), oldTag); } else if (inlineDelta instanceof InsertDelta) { if (mergeOriginalRevised) { origList.addAll(inlineOrig.getPosition(), revList.subList(inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size())); wrapInTag(origList, inlineOrig.getPosition(), inlineOrig.getPosition() - + inlineRev.size() + 1, newTag); + + inlineRev.size(), newTag); } else { wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() - + inlineRev.size() + 1, newTag); + + inlineRev.size(), newTag); } } else if (inlineDelta instanceof ChangeDelta) { if (mergeOriginalRevised) { @@ -374,14 +308,14 @@ private List generateInlineDiffs(Delta delta) throws DiffExcept revList.subList(inlineRev.getPosition(), inlineRev.getPosition() + inlineRev.size())); wrapInTag(origList, inlineOrig.getPosition() + inlineOrig.size(), inlineOrig.getPosition() + inlineOrig.size() - + inlineRev.size() + 1, newTag); + + inlineRev.size(), newTag); } else { wrapInTag(revList, inlineRev.getPosition(), inlineRev.getPosition() - + inlineRev.size() + 1, newTag); + + inlineRev.size(), newTag); } wrapInTag(origList, inlineOrig.getPosition(), inlineOrig .getPosition() - + inlineOrig.size() + 1, oldTag); + + inlineOrig.size(), oldTag); } } StringBuilder origResult = new StringBuilder(); @@ -405,36 +339,136 @@ private List generateInlineDiffs(Delta delta) throws DiffExcept return diffRows; } + private String preprocessLine(String line) { + if (columnWidth == 0) { + return StringUtils.normalize(line); + } else { + return StringUtils.wrapText(StringUtils.normalize(line), columnWidth); + } + } + /** - * Wrap the elements in the sequence with the given tag + * This class used for building the DiffRowGenerator. + * + * @author dmitry * - * @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 tag the tag name without angle brackets, just a word - * @param cssClass the optional css class */ - public static void wrapInTag(List sequence, int startPosition, - int endPosition, Function generator) { - sequence.add(startPosition, generator.apply(true)); - sequence.add(endPosition, generator.apply(false)); - } + public static class Builder { - 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)); + private boolean showInlineDiffs = false; + private boolean ignoreWhiteSpaces = false; + + private Function oldTag = f -> f ? "" : ""; + private Function newTag = f -> f ? "" : ""; + + private int columnWidth = 0; + private boolean mergeOriginalRevised = false; + private boolean reportLinesUnchanged = false; + private Function> inlineDiffSplitter = SPLITTER_BY_CHARACTER; + + 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; + } + + /** + * Give the originial old and new text lines to Diffrow without any additional processing. + * + * @param val the value to set. Default: false. + * @return builder with configured reportLinesUnWrapped parameter + */ + public Builder reportLinesUnchanged(final boolean val) { + reportLinesUnchanged = val; + return this; + } + + /** + * Generator for Old-Text-Tags. + * + * @param tag the tag to set. Without angle brackets. Default: span. + * @return builder with configured ignoreBlankLines parameter + */ + public Builder oldTag(Function generator) { + this.oldTag = generator; + return this; + } + + /** + * Generator for New-Text-Tags. + * + * @param generator + * @return + */ + public Builder newTag(Function generator) { + this.newTag = generator; + return this; + } + + /** + * Set the column with of generated lines of original and revised texts. + * + * @param width the width to set. Making it < 0 doesn't have any sense. Default 80. @return builder with config + * ured ignoreBlankLines parameter + */ + 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; + } + + /** + * Per default each character is separatly processed. This variant introduces processing by word, which should + * deliver no in word changes. + */ + public Builder inlineDiffByWord(boolean inlineDiffByWord) { + inlineDiffSplitter = inlineDiffByWord ? SPLITTER_BY_WORD : SPLITTER_BY_CHARACTER; + return this; + } + + public Builder inlineDiffBySplitter(Function> inlineDiffSplitter) { + this.inlineDiffSplitter = inlineDiffSplitter; + return this; } - return list; } } diff --git a/src/test/java/com/github/difflib/DiffUtilsTest.java b/src/test/java/com/github/difflib/DiffUtilsTest.java index d815401b..28a37219 100644 --- a/src/test/java/com/github/difflib/DiffUtilsTest.java +++ b/src/test/java/com/github/difflib/DiffUtilsTest.java @@ -4,7 +4,7 @@ import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; import com.github.difflib.patch.DeleteDelta; -import com.github.difflib.patch.Delta; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; import java.io.BufferedReader; @@ -33,10 +33,10 @@ public void testDiff_Insert() throws DiffException { asList("hhh", "jjj", "kkk")); assertNotNull(patch); assertEquals(1, patch.getDeltas().size()); - final Delta delta = patch.getDeltas().get(0); + final AbstractDelta delta = patch.getDeltas().get(0); assertTrue(delta instanceof InsertDelta); - assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getOriginal()); - assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getRevised()); + assertEquals(new Chunk<>(1, Collections.emptyList()), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget()); } @Test @@ -45,10 +45,10 @@ public void testDiff_Delete() throws DiffException { asList("ggg")); assertNotNull(patch); assertEquals(1, patch.getDeltas().size()); - final Delta delta = patch.getDeltas().get(0); + final AbstractDelta delta = patch.getDeltas().get(0); assertTrue(delta instanceof DeleteDelta); - assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getOriginal()); - assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getRevised()); + assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource()); + assertEquals(new Chunk<>(0, Collections.emptyList()), delta.getTarget()); } @Test @@ -59,10 +59,10 @@ public void testDiff_Change() throws DiffException { final Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); assertNotNull(patch); assertEquals(1, patch.getDeltas().size()); - final Delta delta = patch.getDeltas().get(0); + final AbstractDelta delta = patch.getDeltas().get(0); assertTrue(delta instanceof ChangeDelta); - assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getOriginal()); - assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getRevised()); + assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource()); + assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget()); } @Test @@ -77,7 +77,7 @@ public void testDiff_EmptyListWithNonEmpty() throws DiffException { final Patch patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa")); assertNotNull(patch); assertEquals(1, patch.getDeltas().size()); - final Delta delta = patch.getDeltas().get(0); + final AbstractDelta delta = patch.getDeltas().get(0); assertTrue(delta instanceof InsertDelta); } @@ -86,9 +86,9 @@ public void testDiffInline() throws DiffException { final Patch patch = DiffUtils.diffInline("", "test"); assertEquals(1, patch.getDeltas().size()); assertTrue(patch.getDeltas().get(0) instanceof InsertDelta); - assertEquals(0, patch.getDeltas().get(0).getOriginal().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getOriginal().getLines().size()); - assertEquals("test", patch.getDeltas().get(0).getRevised().getLines().get(0)); + 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 @@ -96,12 +96,12 @@ public void testDiffInline2() throws DiffException { 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).getOriginal().getPosition()); - assertEquals(2, patch.getDeltas().get(1).getOriginal().getPosition()); - assertEquals(0, patch.getDeltas().get(0).getOriginal().getLines().size()); - assertEquals(0, patch.getDeltas().get(1).getOriginal().getLines().size()); - assertEquals("f", patch.getDeltas().get(0).getRevised().getLines().get(0)); - assertEquals("t", patch.getDeltas().get(1).getRevised().getLines().get(0)); + 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 @@ -111,7 +111,7 @@ public void testDiffIntegerList() throws DiffException { final Patch patch = DiffUtils.diff(original, revised); - for (Delta delta : patch.getDeltas()) { + for (AbstractDelta delta : patch.getDeltas()) { System.out.println(delta); } 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 ec44adea..0009f18b 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 void tearDown() { 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().computeDiff(orgList, revList, null)); System.out.println(patch); assertNotNull(patch); assertEquals(3, patch.getDeltas().size()); @@ -68,4 +70,38 @@ public void testDiff() throws DiffException, PatchFailedException { 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().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 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 93e2950e..5f93570d 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 @@ 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 void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOExcept 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().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"); + } + })); 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 9b911ff1..ea9b0dcc 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().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() 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().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); + 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/src/test/java/com/github/difflib/examples/ComputeDifference.java b/src/test/java/com/github/difflib/examples/ComputeDifference.java index 82ff0190..bd6af94f 100644 --- a/src/test/java/com/github/difflib/examples/ComputeDifference.java +++ b/src/test/java/com/github/difflib/examples/ComputeDifference.java @@ -3,7 +3,7 @@ import com.github.difflib.DiffUtils; import com.github.difflib.TestConstants; import com.github.difflib.algorithm.DiffException; -import com.github.difflib.patch.Delta; +import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.Patch; import java.io.File; import java.io.IOException; @@ -22,7 +22,7 @@ public static void main(String[] args) throws DiffException, IOException { // Compute diff. Get the Patch object. Patch is the container for computed deltas. Patch patch = DiffUtils.diff(original, revised); - for (Delta delta : patch.getDeltas()) { + for (AbstractDelta delta : patch.getDeltas()) { System.out.println(delta); } } diff --git a/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java b/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java index 19748282..f9e5c710 100644 --- a/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java +++ b/src/test/java/com/github/difflib/text/DiffRowGeneratorTest.java @@ -1,9 +1,13 @@ package com.github.difflib.text; import com.github.difflib.algorithm.DiffException; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; +import static java.util.stream.Collectors.toList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import org.junit.Test; @@ -144,8 +148,8 @@ public void testGeneratorWithMerge3() throws DiffException { print(rows); assertEquals(6, rows.size()); - assertEquals("[CHANGE,test,anything]", rows.get(0).toString()); - assertEquals("[CHANGE,anything ,]", rows.get(1).toString()); + assertEquals("[CHANGE,test,anything]", rows.get(0).toString()); + assertEquals("[CHANGE,anything ,]", rows.get(1).toString()); assertEquals("[CHANGE, ,]", rows.get(2).toString()); assertEquals("[EQUAL,other,other]", rows.get(3).toString()); assertEquals("[INSERT,test,test]", rows.get(4).toString()); @@ -263,7 +267,7 @@ public void testGeneratorUnchanged() throws DiffException { assertEquals("[CHANGE, ,]", rows.get(1).toString()); assertEquals("[EQUAL,other,other]", rows.get(2).toString()); } - + @Test public void testGeneratorIssue14() throws DiffException { DiffRowGenerator generator = DiffRowGenerator.create() @@ -282,4 +286,93 @@ public void testGeneratorIssue14() throws DiffException { assertEquals(1, rows.size()); assertEquals("~J. G. Feldstein~**T. P. Pastor**, Chair", rows.get(0).getOldLine()); } + + @Test + public void testGeneratorIssue15() throws DiffException, 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() throws DiffException { + 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**.], [CHANGE,,**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() throws DiffException { + 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**.], [CHANGE,~This is the second line.~,]]", + rows.toString()); + } + + @Test + public void testGeneratorIssue22_3() throws DiffException { + 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**.], [CHANGE,,**This is the second line.**], [CHANGE,,**And one more.**]]", + rows.toString()); + } } diff --git a/src/test/resources/mocks/issue15_1.txt b/src/test/resources/mocks/issue15_1.txt new file mode 100644 index 00000000..fb40d61f --- /dev/null +++ b/src/test/resources/mocks/issue15_1.txt @@ -0,0 +1,9 @@ +TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, NULLABLE, +ACTIONS_C17005, ID, NUMBER, 22, 19, N, +ACTIONS_C17005, ISSUEID, NUMBER, 22, 19, Y, +ACTIONS_C17005, MODIFIED, NUMBER, 22, 10, Y, +ACTIONS_C17005, TABLE, VARCHAR2, 1020, null, Y, +ACTIONS_C17005, S_NAME, CLOB, 4000, null, Y, +ACTIONS_C17008, ID, NUMBER, 22, 19, N, +ACTIONS_C17008, ISSUEID, NUMBER, 22, 19, Y, +ACTIONS_C17008, MODIFIED, NUMBER, 22, 10, Y, \ No newline at end of file diff --git a/src/test/resources/mocks/issue15_2.txt b/src/test/resources/mocks/issue15_2.txt new file mode 100644 index 00000000..8b622651 --- /dev/null +++ b/src/test/resources/mocks/issue15_2.txt @@ -0,0 +1,9 @@ +TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, NULLABLE, +ACTIONS_C16913, ID, NUMBER, 22, 19, N, +ACTIONS_C16913, ISSUEID, NUMBER, 22, 19, Y, +ACTIONS_C16913, MODIFIED, NUMBER, 22, 10, Y, +ACTIONS_C16913, VRS, NUMBER, 22, 1, Y, +ACTIONS_C16913, ZTABS, VARCHAR2, 255, null, Y, +ACTIONS_C16913, ZTABS_S, VARCHAR2, 255, null, Y, +ACTIONS_C16913, TASK, VARCHAR2, 255, null, Y, +ACTIONS_C16913, HOURS_SPENT, VARCHAR2, 255, null, Y, \ No newline at end of file