/* Copyright 2009 Dmitry Naumenko (dm.naumenko@gmail.com) This file is part of Java Diff Utils Library. Java Diff Utils Library is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Java Diff Utils Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Java Diff Utils Library. If not, see . */ package difflib; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import difflib.myers.*; /** * Implements the difference and patching engine * * @author Dmitry Naumenko * @version 0.4.1 */ public class DiffUtils { private static DiffAlgorithm defaultDiffAlgorithm = new MyersDiff(); private static Pattern unifiedDiffChunkRe = Pattern.compile("@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@"); /** * Compute the difference between the original and revised texts with default diff algorithm * * @param original the original text * @param revised the revised text * @return the patch describing the difference between the original and revised texts */ public static Patch diff(List original, List revised) { return DiffUtils.diff(original, revised, defaultDiffAlgorithm); } /** * Compute the difference between the original and revised texts with given diff algorithm * * @param original the original text * @param revised the revised text * @param algorithm the given algorithm * @return the patch describing the difference between the original and revised texts */ public static Patch diff(List original, List revised, DiffAlgorithm algorithm) { return algorithm.diff(original, revised); } /** * Patch the original text with given patch * * @param original the original text * @param patch the given patch * @return the revised text * @throws PatchFailedException if can't apply patch */ public static List patch(List original, Patch patch) throws PatchFailedException { return patch.applyTo(original); } /** * Unpatch the revised text for a given patch * * @param revised the revised text * @param patch the given patch * @return the original text */ public static List unpatch(List revised, Patch patch) { return patch.restore(revised); // bla-bla-bla } /** * 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, old_n = 0, new_ln = 0, new_n = 0; String tag = "", 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 = unifiedDiffChunkRe.matcher(line); if (m.find()) { // Process the lines in the previous chunk if (rawChunk.size() != 0) { List oldChunkLines = new ArrayList(); List newChunkLines = new ArrayList(); for (Object[] raw_line: rawChunk) { tag = (String)raw_line[0]; rest = (String)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, old_n, oldChunkLines), new Chunk(new_ln - 1, new_n, newChunkLines))); rawChunk.clear(); } // Parse the @@ header old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1)); old_n = m.group(2) == null ? 1 : Integer.parseInt(m.group(2)); new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); new_n = m.group(4) == null ? 1 : Integer.parseInt(m.group(4)); old_ln = Integer.parseInt(m.group(1)); 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 Object[] {tag, rest}); } } } } // Process the lines in the last chunk if (rawChunk.size() != 0) { List oldChunkLines = new ArrayList(); List newChunkLines = new ArrayList(); for (Object[] raw_line: rawChunk) { tag = (String)raw_line[0]; rest = (String)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, old_n, oldChunkLines), new Chunk(new_ln - 1, new_n, newChunkLines))); rawChunk.clear(); } return patch; } /** * generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format text representing the Patch. * @author Bill James (tankerbay@gmail.com) * * @param fname1 - Filename of the original (unrevised file) * @param fname2 - 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. */ public static List generateUnifiedDiff(String fname1, String fname2, List originalLines, Patch patch, int contextSize ) { List ret = new ArrayList(); ret.add( "--- " + fname1 ); ret.add( "+++ " + fname2 ); List cur = new ArrayList(); // current list of Delta's to process int deltact = patch.getDeltas().size(); // if there's more than 1 Delta, we may need to output them together if ( deltact > 1 ) { Delta curDelta = patch.getDelta(0); cur.add( curDelta ); // add the first Delta to the current set for ( int i = 1; i < deltact; i++ ) { int curpos = curDelta.getOriginal().getPosition(); // store the current position of the first Delta Delta nextDelta = patch.getDelta(i); // Check if the next Delta is too close to the current position if ( (curpos + curDelta.getOriginal().getSize() + contextSize) >= ( nextDelta.getOriginal().getPosition()-contextSize ) ) { cur.add( nextDelta ); // if it is, add it to the current set } else { List curBlock = processDeltas( originalLines, cur, contextSize ); ret.addAll( curBlock ); // if it isn't, output the current set, then create a new cur.clear(); // set and add the current Delta to it. cur.add( nextDelta ); } curDelta = nextDelta; } List curBlock = processDeltas( originalLines, cur, contextSize ); // don't forget to process the last set of Deltas ret.addAll( curBlock ); } return ret; } /** * processDeltas takes a list of Deltas and outputs them together in a single block of Unified-Diff-format text. * @author Bill James (tankerbay@gmail.com) * * @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 */ 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); // start with the first Delta int origStart = curDelta.getOriginal().getPosition()+1 - contextSize; // note the +1 to overcome the 0-offset Position if ( origStart < 1 ) origStart = 1; // clamp to the start of the file int revStart = curDelta.getRevised().getPosition()+1 - contextSize; // note the +1 to overcome the 0-offset Position if ( revStart < 1 ) revStart = 1; // clamp to the start of the file int contextStart = curDelta.getOriginal().getPosition() - contextSize; // find the start of the wrapper context code if ( contextStart < 0 ) contextStart = 0; // clamp to the start of the file for ( line = contextStart; line < curDelta.getOriginal().getPosition(); line++ ) { // output the context before the first Delta buffer.add( " " + origLines.get( line ) ); origTotal++; revTotal++; } buffer.addAll( getDeltaText( curDelta ) ); // output the first Delta 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++ ) { buffer.add( " " + origLines.get( line ) ); // output the code between the last Delta and this one origTotal++; revTotal++; } buffer.addAll( getDeltaText( nextDelta ) ); // output the Delta origTotal += nextDelta.getOriginal().getLines().size(); revTotal += nextDelta.getRevised().getLines().size(); curDelta = nextDelta; deltaIndex++; // increment the iterator } // 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 * @author Bill James (tankerbay@gmail.com) * * @param delta - the Delta to output * @return list of String lines of code. */ private static List getDeltaText( Delta delta ) { List buffer = new ArrayList(); for ( Object line: delta.getOriginal().getLines() ) { buffer.add( "-" + line ); } for ( Object line: delta.getRevised().getLines() ) { buffer.add( "+" + line ); } return buffer; } }