diff --git a/src/main/java/difflib/DiffUtils.java b/src/main/java/difflib/DiffUtils.java index 121d258..2671cdb 100644 --- a/src/main/java/difflib/DiffUtils.java +++ b/src/main/java/difflib/DiffUtils.java @@ -22,18 +22,13 @@ package difflib; import difflib.algorithm.DiffAlgorithm; import difflib.algorithm.DiffException; import difflib.algorithm.myers.MyersDiff; -import difflib.patch.ChangeDelta; -import difflib.patch.Chunk; import difflib.patch.Delta; import difflib.patch.Equalizer; import difflib.patch.Patch; import difflib.patch.PatchFailedException; -import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static java.util.stream.Collectors.joining; /** @@ -41,13 +36,9 @@ import static java.util.stream.Collectors.joining; * * @author Dmitry Naumenko * @version 0.4.1 - * @param T The type of the compared elements in the 'lines'. */ public final class DiffUtils { - private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern - .compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@$"); - /** * Computes the difference between the original and revised list of elements with default diff * algorithm @@ -163,281 +154,6 @@ public final class DiffUtils { return patch.restore(revised); } - /** - * Parse the given text in unified format and creates the list of deltas for it. - * - * @param diff the text in unified format - * @return the patch with deltas. - */ - public static Patch parseUnifiedDiff(List diff) { - boolean inPrelude = true; - List rawChunk = new ArrayList<>(); - Patch patch = new Patch<>(); - - int old_ln = 0, new_ln = 0; - String tag; - String rest; - for (String line : diff) { - // Skip leading lines until after we've seen one starting with '+++' - if (inPrelude) { - if (line.startsWith("+++")) { - inPrelude = false; - } - continue; - } - Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); - if (m.find()) { - // Process the lines in the previous chunk - if (!rawChunk.isEmpty()) { - List oldChunkLines = new ArrayList<>(); - List newChunkLines = new ArrayList<>(); - - for (String[] raw_line : rawChunk) { - tag = raw_line[0]; - rest = raw_line[1]; - if (tag.equals(" ") || tag.equals("-")) { - oldChunkLines.add(rest); - } - if (tag.equals(" ") || tag.equals("+")) { - newChunkLines.add(rest); - } - } - patch.addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, oldChunkLines), new Chunk<>( - new_ln - 1, newChunkLines))); - rawChunk.clear(); - } - // Parse the @@ header - old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); - new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); - - if (old_ln == 0) { - old_ln += 1; - } - if (new_ln == 0) { - new_ln += 1; - } - } else { - if (line.length() > 0) { - tag = line.substring(0, 1); - rest = line.substring(1); - if (tag.equals(" ") || tag.equals("+") || tag.equals("-")) { - rawChunk.add(new String[]{tag, rest}); - } - } else { - rawChunk.add(new String[]{" ", ""}); - } - } - } - - // Process the lines in the last chunk - if (!rawChunk.isEmpty()) { - List oldChunkLines = new ArrayList<>(); - List newChunkLines = new ArrayList<>(); - - for (String[] raw_line : rawChunk) { - tag = raw_line[0]; - rest = raw_line[1]; - if (tag.equals(" ") || tag.equals("-")) { - oldChunkLines.add(rest); - } - if (tag.equals(" ") || tag.equals("+")) { - newChunkLines.add(rest); - } - } - - patch.addDelta(new ChangeDelta<>(new Chunk<>( - old_ln - 1, oldChunkLines), new Chunk<>(new_ln - 1, - newChunkLines))); - rawChunk.clear(); - } - - return patch; - } - - /** - * 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 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, - int contextSize) { - if (!patch.getDeltas().isEmpty()) { - List ret = new ArrayList<>(); - ret.add("--- " + original); - ret.add("+++ " + revised); - - List> patchDeltas = new ArrayList<>( - patch.getDeltas()); - - // code outside the if block also works for single-delta issues. - List> deltas = new ArrayList<>(); // current - // list - // of - // Delta's to - // process - Delta 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 - // the - // current - // position - // of - // the first Delta - - // 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)) { - deltas.add(nextDelta); - } else { - // if it isn't, output the current set, - // then create a new set and add the current Delta to - // it. - List curBlock = processDeltas(originalLines, - deltas, contextSize); - ret.addAll(curBlock); - deltas.clear(); - deltas.add(nextDelta); - } - delta = nextDelta; - } - - } - // don't forget to process the last set of Deltas - List curBlock = processDeltas(originalLines, deltas, - contextSize); - ret.addAll(curBlock); - return ret; - } - return new ArrayList<>(); - } - - /** - * processDeltas takes a list of Deltas and outputs them together in a single block of - * Unified-Diff-format text. - * - * @param origLines - the lines of the original file - * @param deltas - the Deltas to be output as a single block - * @param contextSize - the number of lines of context to place around block - * @return - * @author Bill James (tankerbay@gmail.com) - */ - private static List processDeltas(List origLines, - 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); - - // NOTE: +1 to overcome the 0-offset Position - int origStart = curDelta.getOriginal().getPosition() + 1 - contextSize; - if (origStart < 1) { - origStart = 1; - } - - int revStart = curDelta.getRevised().getPosition() + 1 - contextSize; - if (revStart < 1) { - revStart = 1; - } - - // find the start of the wrapper context code - int contextStart = curDelta.getOriginal().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++) { // - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // output the first Delta - buffer.addAll(getDeltaText(curDelta)); - origTotal += curDelta.getOriginal().getLines().size(); - revTotal += curDelta.getRevised().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() - .getPosition(); line++) { - // output the code between the last Delta and this one - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - buffer.addAll(getDeltaText(nextDelta)); // output the Delta - origTotal += nextDelta.getOriginal().getLines().size(); - revTotal += nextDelta.getRevised().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(); - for (line = contextStart; (line < (contextStart + contextSize)) - && (line < origLines.size()); line++) { - buffer.add(" " + origLines.get(line)); - origTotal++; - revTotal++; - } - - // Create and insert the block header, conforming to the Unified Diff - // standard - StringBuffer header = new StringBuffer(); - header.append("@@ -"); - header.append(origStart); - header.append(","); - header.append(origTotal); - header.append(" +"); - header.append(revStart); - header.append(","); - header.append(revTotal); - header.append(" @@"); - buffer.add(0, header.toString()); - - return buffer; - } - - /** - * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter - * - * @param delta - the Delta to output - * @return list of String lines of code. - * @author Bill James (tankerbay@gmail.com) - */ - private static List getDeltaText(Delta delta) { - List buffer = new ArrayList<>(); - for (String line : delta.getOriginal().getLines()) { - buffer.add("-" + line); - } - for (String line : delta.getRevised().getLines()) { - buffer.add("+" + line); - } - return buffer; - } - private DiffUtils() { } } diff --git a/src/main/java/difflib/UnifiedDiffUtils.java b/src/main/java/difflib/UnifiedDiffUtils.java new file mode 100644 index 0000000..003385b --- /dev/null +++ b/src/main/java/difflib/UnifiedDiffUtils.java @@ -0,0 +1,312 @@ +/* + * Copyright 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. + */ +package difflib; + +import difflib.patch.ChangeDelta; +import difflib.patch.Chunk; +import difflib.patch.Delta; +import difflib.patch.Patch; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author toben + */ +public final class UnifiedDiffUtils { + private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern + .compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@$"); + + /** + * Parse the given text in unified format and creates the list of deltas for it. + * + * @param diff the text in unified format + * @return the patch with deltas. + */ + public static Patch parseUnifiedDiff(List diff) { + boolean inPrelude = true; + List rawChunk = new ArrayList<>(); + Patch patch = new Patch<>(); + + int old_ln = 0, new_ln = 0; + String tag; + String rest; + for (String line : diff) { + // Skip leading lines until after we've seen one starting with '+++' + if (inPrelude) { + if (line.startsWith("+++")) { + inPrelude = false; + } + continue; + } + Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line); + if (m.find()) { + // Process the lines in the previous chunk + if (!rawChunk.isEmpty()) { + List oldChunkLines = new ArrayList<>(); + List newChunkLines = new ArrayList<>(); + + for (String[] raw_line : rawChunk) { + tag = raw_line[0]; + rest = raw_line[1]; + if (tag.equals(" ") || tag.equals("-")) { + oldChunkLines.add(rest); + } + if (tag.equals(" ") || tag.equals("+")) { + newChunkLines.add(rest); + } + } + patch.addDelta(new ChangeDelta<>(new Chunk<>( + old_ln - 1, oldChunkLines), new Chunk<>( + new_ln - 1, newChunkLines))); + rawChunk.clear(); + } + // Parse the @@ header + old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); + new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); + + if (old_ln == 0) { + old_ln += 1; + } + if (new_ln == 0) { + new_ln += 1; + } + } else { + if (line.length() > 0) { + tag = line.substring(0, 1); + rest = line.substring(1); + if (tag.equals(" ") || tag.equals("+") || tag.equals("-")) { + rawChunk.add(new String[]{tag, rest}); + } + } else { + rawChunk.add(new String[]{" ", ""}); + } + } + } + + // Process the lines in the last chunk + if (!rawChunk.isEmpty()) { + List oldChunkLines = new ArrayList<>(); + List newChunkLines = new ArrayList<>(); + + for (String[] raw_line : rawChunk) { + tag = raw_line[0]; + rest = raw_line[1]; + if (tag.equals(" ") || tag.equals("-")) { + oldChunkLines.add(rest); + } + if (tag.equals(" ") || tag.equals("+")) { + newChunkLines.add(rest); + } + } + + patch.addDelta(new ChangeDelta<>(new Chunk<>( + old_ln - 1, oldChunkLines), new Chunk<>(new_ln - 1, + newChunkLines))); + rawChunk.clear(); + } + + return patch; + } + + /** + * 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 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, + int contextSize) { + if (!patch.getDeltas().isEmpty()) { + List ret = new ArrayList<>(); + ret.add("--- " + original); + ret.add("+++ " + revised); + + List> patchDeltas = new ArrayList<>( + patch.getDeltas()); + + // code outside the if block also works for single-delta issues. + List> deltas = new ArrayList<>(); // current + // list + // of + // Delta's to + // process + Delta 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 + // the + // current + // position + // of + // the first Delta + + // 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)) { + deltas.add(nextDelta); + } else { + // if it isn't, output the current set, + // then create a new set and add the current Delta to + // it. + List curBlock = processDeltas(originalLines, + deltas, contextSize); + ret.addAll(curBlock); + deltas.clear(); + deltas.add(nextDelta); + } + delta = nextDelta; + } + + } + // don't forget to process the last set of Deltas + List curBlock = processDeltas(originalLines, deltas, + contextSize); + ret.addAll(curBlock); + return ret; + } + return new ArrayList<>(); + } + + /** + * processDeltas takes a list of Deltas and outputs them together in a single block of + * Unified-Diff-format text. + * + * @param origLines - the lines of the original file + * @param deltas - the Deltas to be output as a single block + * @param contextSize - the number of lines of context to place around block + * @return + * @author Bill James (tankerbay@gmail.com) + */ + private static List processDeltas(List origLines, + 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); + + // NOTE: +1 to overcome the 0-offset Position + int origStart = curDelta.getOriginal().getPosition() + 1 - contextSize; + if (origStart < 1) { + origStart = 1; + } + + int revStart = curDelta.getRevised().getPosition() + 1 - contextSize; + if (revStart < 1) { + revStart = 1; + } + + // find the start of the wrapper context code + int contextStart = curDelta.getOriginal().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++) { // + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // output the first Delta + buffer.addAll(getDeltaText(curDelta)); + origTotal += curDelta.getOriginal().getLines().size(); + revTotal += curDelta.getRevised().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() + .getPosition(); line++) { + // output the code between the last Delta and this one + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + buffer.addAll(getDeltaText(nextDelta)); // output the Delta + origTotal += nextDelta.getOriginal().getLines().size(); + revTotal += nextDelta.getRevised().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(); + for (line = contextStart; (line < (contextStart + contextSize)) + && (line < origLines.size()); line++) { + buffer.add(" " + origLines.get(line)); + origTotal++; + revTotal++; + } + + // Create and insert the block header, conforming to the Unified Diff + // standard + StringBuffer header = new StringBuffer(); + header.append("@@ -"); + header.append(origStart); + header.append(","); + header.append(origTotal); + header.append(" +"); + header.append(revStart); + header.append(","); + header.append(revTotal); + header.append(" @@"); + buffer.add(0, header.toString()); + + return buffer; + } + + /** + * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter + * + * @param delta - the Delta to output + * @return list of String lines of code. + * @author Bill James (tankerbay@gmail.com) + */ + private static List getDeltaText(Delta delta) { + List buffer = new ArrayList<>(); + for (String line : delta.getOriginal().getLines()) { + buffer.add("-" + line); + } + for (String line : delta.getRevised().getLines()) { + buffer.add("+" + line); + } + return buffer; + } + + private UnifiedDiffUtils() { + } +} diff --git a/src/test/java/difflib/GenerateUnifiedDiffTest.java b/src/test/java/difflib/GenerateUnifiedDiffTest.java index 72d9aec..7ee7e4d 100644 --- a/src/test/java/difflib/GenerateUnifiedDiffTest.java +++ b/src/test/java/difflib/GenerateUnifiedDiffTest.java @@ -1,6 +1,5 @@ package difflib; -import difflib.DiffUtils; import difflib.algorithm.DiffException; import difflib.patch.Patch; import difflib.patch.PatchFailedException; @@ -62,14 +61,14 @@ public class GenerateUnifiedDiffTest { public void testGenerateUnifiedDiffWithoutAnyDeltas() throws DiffException { List test = Arrays.asList("abc"); Patch patch = DiffUtils.diff(test, test); - DiffUtils.generateUnifiedDiff("abc", "abc", test, patch, 0); + UnifiedDiffUtils.generateUnifiedDiff("abc", "abc", test, patch, 0); } @Test public void testDiff_Issue10() { final List baseLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_base.txt"); final List patchLines = fileToLines(TestConstants.MOCK_FOLDER + "issue10_patch.txt"); - final Patch p = DiffUtils.parseUnifiedDiff(patchLines); + final Patch p = UnifiedDiffUtils.parseUnifiedDiff(patchLines); try { DiffUtils.patch(baseLines, p); } catch (PatchFailedException e) { @@ -114,18 +113,18 @@ public class GenerateUnifiedDiffTest { revised.add("test line 5"); Patch patch = DiffUtils.diff(original, revised); - List udiff = DiffUtils.generateUnifiedDiff("original", "revised", + List udiff = UnifiedDiffUtils.generateUnifiedDiff("original", "revised", original, patch, 10); - DiffUtils.parseUnifiedDiff(udiff); + UnifiedDiffUtils.parseUnifiedDiff(udiff); } private void verify(List origLines, List revLines, String originalFile, String revisedFile) throws DiffException { Patch patch = DiffUtils.diff(origLines, revLines); - List unifiedDiff = DiffUtils.generateUnifiedDiff(originalFile, revisedFile, + List unifiedDiff = UnifiedDiffUtils.generateUnifiedDiff(originalFile, revisedFile, origLines, patch, 10); - Patch fromUnifiedPatch = DiffUtils.parseUnifiedDiff(unifiedDiff); + Patch fromUnifiedPatch = UnifiedDiffUtils.parseUnifiedDiff(unifiedDiff); List patchedLines; try { patchedLines = (List) fromUnifiedPatch.applyTo(origLines); diff --git a/src/test/java/difflib/examples/ApplyPatch.java b/src/test/java/difflib/examples/ApplyPatch.java index db798ea..231bb07 100644 --- a/src/test/java/difflib/examples/ApplyPatch.java +++ b/src/test/java/difflib/examples/ApplyPatch.java @@ -4,6 +4,7 @@ import difflib.DiffUtils; import difflib.patch.Patch; import difflib.patch.PatchFailedException; import difflib.TestConstants; +import difflib.UnifiedDiffUtils; import java.util.List; public class ApplyPatch extends Example { @@ -16,7 +17,7 @@ public class ApplyPatch extends Example { List patched = fileToLines(PATCH); // At first, parse the unified diff file and get the patch - Patch patch = DiffUtils.parseUnifiedDiff(patched); + Patch patch = UnifiedDiffUtils.parseUnifiedDiff(patched); // Then apply the computed patch to the given text List result = DiffUtils.patch(original, patch);