From 483cc2ee69672b1b49635e304058b71a1d814b94 Mon Sep 17 00:00:00 2001 From: "dm.naumenko@gmail.com" Date: Tue, 15 Sep 2009 19:36:19 +0000 Subject: [PATCH] Repo init --- src/difflib/ChangeDelta.java | 36 +++ src/difflib/Chunk.java | 130 ++++++++++ src/difflib/DeleteDelta.java | 31 +++ src/difflib/Delta.java | 57 ++++ src/difflib/DiffAlgorithm.java | 33 +++ src/difflib/DiffRow.java | 56 ++++ src/difflib/DiffUtils.java | 155 +++++++++++ src/difflib/InsertDelta.java | 31 +++ src/difflib/Patch.java | 42 +++ src/difflib/myers/DiffException.java | 81 ++++++ src/difflib/myers/DiffNode.java | 57 ++++ .../myers/DifferentiationFailedException.java | 83 ++++++ src/difflib/myers/MyersDiff.java | 245 ++++++++++++++++++ src/difflib/myers/PathNode.java | 146 +++++++++++ src/difflib/myers/Snake.java | 97 +++++++ src/difflib/myers/package.html | 80 ++++++ test/testcase/diffutills/DiffTest.java | 56 ++++ test/testcase/diffutills/PatchTest.java | 32 +++ 18 files changed, 1448 insertions(+) create mode 100644 src/difflib/ChangeDelta.java create mode 100644 src/difflib/Chunk.java create mode 100644 src/difflib/DeleteDelta.java create mode 100644 src/difflib/Delta.java create mode 100644 src/difflib/DiffAlgorithm.java create mode 100644 src/difflib/DiffRow.java create mode 100644 src/difflib/DiffUtils.java create mode 100644 src/difflib/InsertDelta.java create mode 100644 src/difflib/Patch.java create mode 100644 src/difflib/myers/DiffException.java create mode 100644 src/difflib/myers/DiffNode.java create mode 100644 src/difflib/myers/DifferentiationFailedException.java create mode 100644 src/difflib/myers/MyersDiff.java create mode 100644 src/difflib/myers/PathNode.java create mode 100644 src/difflib/myers/Snake.java create mode 100644 src/difflib/myers/package.html create mode 100644 test/testcase/diffutills/DiffTest.java create mode 100644 test/testcase/diffutills/PatchTest.java diff --git a/src/difflib/ChangeDelta.java b/src/difflib/ChangeDelta.java new file mode 100644 index 0000000..c9a2e99 --- /dev/null +++ b/src/difflib/ChangeDelta.java @@ -0,0 +1,36 @@ +package difflib; + +import java.util.List; + +/** + * Describes the change-delta between original and revised texts. + * + * @author Dmitry Naumenko + */ +public class ChangeDelta extends Delta { + + /** + * {@inheritDoc} + */ + public ChangeDelta(Chunk original, Chunk revised) { + super(original, revised); + } + + /** + * {@inheritDoc} + */ + @Override + public void applyTo(List target) { + int position = getOriginal().getPosition(); + int originalSize = getOriginal().getSize(); + for (int i = 0; i < originalSize; i++) { + target.remove(position); + } + int i = 0; + for (Object line: getRevised().getLines()) { + target.add(position + i, line); + i++; + } + } + +} diff --git a/src/difflib/Chunk.java b/src/difflib/Chunk.java new file mode 100644 index 0000000..be2d217 --- /dev/null +++ b/src/difflib/Chunk.java @@ -0,0 +1,130 @@ +package difflib; + +import java.util.*; + +/** + * Holds the information about the part of text involved in the diff process + * + *

Text is represented as Object[] because + * the diff engine is capable of handling more than plain ascci. In fact, + * arrays or lists of any type that implements + * {@link java.lang.Object#hashCode hashCode()} and + * {@link java.lang.Object#equals equals()} + * correctly can be subject to differencing using this + * library.

+ * + * @author Dmitry Naumenko + */ +public class DeleteDelta extends Delta { + + /** + * {@inheritDoc} + */ + public DeleteDelta(Chunk original, Chunk revised) { + super(original, revised); + } + + /** + * {@inheritDoc} + */ + @Override + public void applyTo(List target) { + int position = getOriginal().getPosition(); + int size = getOriginal().getSize(); + for (int i = 0; i < size; i++) { + target.remove(position); + } + } + +} diff --git a/src/difflib/Delta.java b/src/difflib/Delta.java new file mode 100644 index 0000000..c6731cb --- /dev/null +++ b/src/difflib/Delta.java @@ -0,0 +1,57 @@ +package difflib; + +import java.util.*; + +/** + * Describes the delta between original and revised texts. + * + * @author Dmitry Naumenko + */ +public abstract class Delta { + private Chunk original; + private Chunk revised; + + /** + * Construct the delta for original and revised chunks + * + * @param original chunk describes the original text + * @param revised chunk describes the revised text + */ + public Delta(Chunk original, Chunk revised) { + this.original = original; + this.revised = revised; + } + + /** + * Applies this delta as the patch for a given target + * + * @param target the given target + */ + public abstract void applyTo(List target); + + /** + * @return the Chunk describing the original text + */ + public Chunk getOriginal() { + return original; + } + /** + * @param original the Chunk describing the original text to set + */ + public void setOriginal(Chunk original) { + this.original = original; + } + + /** + * @return the Chunk describing the revised text + */ + public Chunk getRevised() { + return revised; + } + /** + * @param revised the Chunk describing the revised text to set + */ + public void setRevised(Chunk revised) { + this.revised = revised; + } +} diff --git a/src/difflib/DiffAlgorithm.java b/src/difflib/DiffAlgorithm.java new file mode 100644 index 0000000..f49a804 --- /dev/null +++ b/src/difflib/DiffAlgorithm.java @@ -0,0 +1,33 @@ +package difflib; + +import java.util.*; + +/** + * The general interface for computing diffs between two texts + * + * @author Dmitry Naumenko + */ +public interface DiffAlgorithm { + + /** + * Computes the difference between the original + * sequence and the revised sequence and returns it + * as a {@link difflib.Patch Patch} object. + * + * @param original the original text + * @param revised the revised text + * @return the patch + */ + public Patch diff(Object[] original, Object[] revised); + + /** + * Computes the difference between the original + * sequence and the revised sequence and returns it + * as a {@link difflib.Patch Patch} object. + * + * @param original the original text + * @param revised the revised text + * @return the patch + */ + public Patch diff(List original, List revised); +} diff --git a/src/difflib/DiffRow.java b/src/difflib/DiffRow.java new file mode 100644 index 0000000..a8d3aeb --- /dev/null +++ b/src/difflib/DiffRow.java @@ -0,0 +1,56 @@ +package difflib; + +/** + * Describes the diff row in form [tag, oldLine, newLine) for showing the difference between two + * texts + * + * @author Dmitry Naumenko + */ +public class DiffRow { + private Tag tag; + private String oldLine; + private String newLine; + + public enum Tag { + INSERT, DELETE, CHANGE + } + + /** + * @return the tag + */ + public Tag getTag() { + return tag; + } + /** + * @param tag the tag to set + */ + public void setTag(Tag tag) { + this.tag = tag; + } + + /** + * @return the oldLine + */ + public String getOldLine() { + return oldLine; + } + /** + * @param oldLine the oldLine to set + */ + public void setOldLine(String oldLine) { + this.oldLine = oldLine; + } + + /** + * @return the newLine + */ + public String getNewLine() { + return newLine; + } + /** + * @param newLine the newLine to set + */ + public void setNewLine(String newLine) { + this.newLine = newLine; + } +} diff --git a/src/difflib/DiffUtils.java b/src/difflib/DiffUtils.java new file mode 100644 index 0000000..0302c7c --- /dev/null +++ b/src/difflib/DiffUtils.java @@ -0,0 +1,155 @@ +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 + */ +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 + */ + public static List patch(List original, Patch patch) { + 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 String unpatch(String revised, Patch patch) { + // TODO: Not-implemented + return null; + } + + /** + * Parse the given text in unified format and creates the list of deltas for it. + * + * @param diff the text in unified format + */ + 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 = Integer.parseInt(m.group(1)); + old_n = Integer.parseInt(m.group(2)); + new_ln = Integer.parseInt(m.group(3)); + new_n = Integer.parseInt(m.group(4)); + } else { + 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; + } + + /** + * Get the DiffRows describing the difference between original and revised texts. Useful for + * displaying side-by-side diff. Computes the Patch before generate the diffs. + * + * @param original the original text + * @param revised the revised text + * @return the DiffRows between original and revised texts + */ + public static List getDiffRows(List original, List revised) { + return null; + } +} diff --git a/src/difflib/InsertDelta.java b/src/difflib/InsertDelta.java new file mode 100644 index 0000000..3c30475 --- /dev/null +++ b/src/difflib/InsertDelta.java @@ -0,0 +1,31 @@ +package difflib; + +import java.util.List; + +/** + * Describes the add-delta between original and revised texts. + * + * @author Dmitry Naumenko + */ +public class InsertDelta extends Delta { + + /** + * {@inheritDoc} + */ + public InsertDelta(Chunk original, Chunk revised) { + super(original, revised); + } + + /** + * {@inheritDoc} + */ + @Override + public void applyTo(List target) { + int position = this.getOriginal().getPosition(); + List lines = this.getRevised().getLines(); + for (int i = 0; i < lines.size(); i++) { + target.add(position + i, lines.get(i)); + } + } + +} diff --git a/src/difflib/Patch.java b/src/difflib/Patch.java new file mode 100644 index 0000000..379b8d7 --- /dev/null +++ b/src/difflib/Patch.java @@ -0,0 +1,42 @@ +package difflib; + +import java.util.*; + +/** + * Describes the patch holding all deltas between the original and revised texts. + * + * @author Dmitry Naumenko + */ +public class Patch { + private List deltas = new LinkedList(); + + public List applyTo(List target) { + List result = new LinkedList(target); + for (Delta delta: deltas) { + delta.applyTo(result); + } + return result; + } + + /** + * Add the given delta to this patch + * @param delta the given delta + */ + public void addDelta(Delta delta) { + deltas.add(delta); + } + + /** + * @param deltas the deltas to set + */ + public void setDeltas(List deltas) { + this.deltas = deltas; + } + + /** + * @return the deltas + */ + public List getDeltas() { + return deltas; + } +} diff --git a/src/difflib/myers/DiffException.java b/src/difflib/myers/DiffException.java new file mode 100644 index 0000000..7c78adf --- /dev/null +++ b/src/difflib/myers/DiffException.java @@ -0,0 +1,81 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package difflib.myers; + +/** + * Base class for all exceptions emanating from this package. + * + * @version $Revision: 69 $ $Date: 2003-10-13 11:00:44 +0300 (Пнд, 13 Окт 2003) $ + * + * @author Juanco Anez + */ +public class DiffException extends Exception +{ + + private static final long serialVersionUID = 1L; + + public DiffException() + { + } + + public DiffException(String msg) + { + super(msg); + } +} + diff --git a/src/difflib/myers/DiffNode.java b/src/difflib/myers/DiffNode.java new file mode 100644 index 0000000..92c3330 --- /dev/null +++ b/src/difflib/myers/DiffNode.java @@ -0,0 +1,57 @@ +package difflib.myers; + +/** + *

Title:

+ *

Description:

+ *

Copyright: Copyright (c) 2002

+ *

Company:

+ * @author not attributable + * @version 1.0 + */ + +/** + * A diffnode in a diffpath. + *

+ * A DiffNode and its previous node mark a delta between + * two input sequences, that is, two differing subsequences + * between (possibly zero length) matching sequences. + * + * {@link DiffNode DiffNodes} and {@link Snake Snakes} allow for compression + * of diffpaths, as each snake is represented by a single {@link Snake Snake} + * node and each contiguous series of insertions and deletions is represented + * by a single {@link DiffNode DiffNodes}. + * + * @version $Revision: 60 $ $Date: 2003-05-10 21:56:10 +0300 (Суб, 10 Май 2003) $ + * @author Juanco Anez + * + */ +public final class DiffNode + extends PathNode +{ + /** + * Constructs a DiffNode. + *

+ * DiffNodes are compressed. That means that + * the path pointed to by the prev parameter + * will be followed using {@link PathNode#previousSnake} + * until a non-diff node is found. + * + * @param the position in the original sequence + * @param the position in the revised sequence + * @param prev the previous node in the path. + */ + public DiffNode(int i, int j, PathNode prev) + { + super(i, j, (prev == null ? null : prev.previousSnake()) ); + } + + /** + * {@inheritDoc} + * @return false, always + */ + public boolean isSnake() + { + return false; + } + +} \ No newline at end of file diff --git a/src/difflib/myers/DifferentiationFailedException.java b/src/difflib/myers/DifferentiationFailedException.java new file mode 100644 index 0000000..131a075 --- /dev/null +++ b/src/difflib/myers/DifferentiationFailedException.java @@ -0,0 +1,83 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package difflib.myers; + +/** + * Thrown whenever the differencing engine cannot produce the differences + * between two revisions of ta text. + * + * @version $Revision: 69 $ $Date: 2003-10-13 11:00:44 +0300 (Пнд, 13 Окт 2003) $ + * + * @author Juanco Anez + * @see Diff + * @see DiffAlgorithm + */ +public class DifferentiationFailedException extends DiffException +{ + private static final long serialVersionUID = 1L; + + public DifferentiationFailedException() + { + } + + public DifferentiationFailedException(String msg) + { + super(msg); + } +} + diff --git a/src/difflib/myers/MyersDiff.java b/src/difflib/myers/MyersDiff.java new file mode 100644 index 0000000..4130814 --- /dev/null +++ b/src/difflib/myers/MyersDiff.java @@ -0,0 +1,245 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package difflib.myers; + +import java.util.Arrays; +import java.util.List; + +import difflib.*; + +/** + * A clean-room implementation of + * Eugene Myers differencing algorithm. + * + *

See the paper at + * http://www.cs.arizona.edu/people/gene/PAPERS/diff.ps

+ * + * @author Juanco Anez + */ +public class MyersDiff + implements DiffAlgorithm +{ + /** + * Constructs an instance of the Myers differencing algorithm. + */ + public MyersDiff() + { + } + + /** + * {@inheritDoc} + * + * Return empty diff if get the error while procession the difference. + */ + @Override + public Patch diff(List original, List revised) { + return diff(original.toArray(), revised.toArray()); + } + + /** + * {@inheritDoc} + * + * Return empty diff if get the error while procession the difference. + */ + @Override + public Patch diff(Object[] orig, Object[] rev) + { + PathNode path; + try { + path = buildPath(orig, rev); + return buildRevision(path, orig, rev); + } catch (DifferentiationFailedException e) { + e.printStackTrace(); + } + return new Patch(); + } + + /** + * 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. + */ + public static PathNode buildPath(Object[] orig, Object[] rev) + throws DifferentiationFailedException + { + if (orig == null) + throw new IllegalArgumentException("original sequence is null"); + if (rev == null) + throw new IllegalArgumentException("revised sequence is null"); + + // these are local constants + final int N = orig.length; + final int M = rev.length; + + final int MAX = N + M + 1; + final int size = 1 + 2 * MAX; + final int middle = (size + 1) / 2; + final PathNode diagonal[] = new PathNode[size]; + + diagonal[middle + 1] = new Snake(0, -1, null); + for (int d = 0; d < MAX; d++) + { + for (int k = -d; k <= d; k += 2) + { + final int kmiddle = middle + k; + final int kplus = kmiddle + 1; + final int kminus = kmiddle - 1; + PathNode prev = null; + + int i; + if ( (k == -d) || + (k != d && diagonal[kminus].i < diagonal[kplus].i)) + { + i = diagonal[kplus].i; + prev = diagonal[kplus]; + } + else + { + i = diagonal[kminus].i + 1; + prev = diagonal[kminus]; + } + + diagonal[kminus] = null; // no longer used + + int j = i - k; + + PathNode node = new DiffNode(i, j, prev); + + // orig and rev are zero-based + // but the algorithm is one-based + // that's why there's no +1 when indexing the sequences + while (i < N && j < M && orig[i].equals(rev[j])) + { + i++; + j++; + } + if (i > node.i) + node = new Snake(i, j, node); + + diagonal[kmiddle] = node; + + if (i >= N && j >= M) + { + return diagonal[kmiddle]; + } + } + diagonal[middle+d-1] = null; + + } + // According to Myers, this cannot happen + throw new DifferentiationFailedException("could not find a diff path"); + } + + /** + * Constructs a {@link Revision} from a difference path. + * + * @param path The path. + * @param orig The original sequence. + * @param rev The revised sequence. + * @return A {@link Revision} script corresponding to the path. + * @throws DifferentiationFailedException if a {@link Revision} could + * not be built from the given path. + */ + public static Patch buildRevision(PathNode path, Object[] orig, Object[] rev) + { + if (path == null) + throw new IllegalArgumentException("path is null"); + if (orig == null) + throw new IllegalArgumentException("original sequence is null"); + if (rev == null) + throw new IllegalArgumentException("revised sequence is null"); + + Patch patch = new Patch(); + if (path.isSnake()) + path = path.prev; + while (path != null && path.prev != null && path.prev.j >= 0) + { + if(path.isSnake()) + throw new IllegalStateException("bad diffpath: found snake when looking for diff"); + int i = path.i; + int j = path.j; + + path = path.prev; + int ianchor = path.i; + int janchor = path.j; + + Chunk original = new Chunk(ianchor, i - ianchor, Arrays.copyOfRange(orig, ianchor, i)); + Chunk revised = new Chunk(janchor, j - janchor, Arrays.copyOfRange(rev, janchor, j)); + Delta delta = null; + if (original.getSize() == 0 && revised.getSize() != 0) { + delta = new InsertDelta(original, revised); + } else if (original.getSize() > 0 && revised.getSize() == 0) { + delta = new DeleteDelta(original, revised); + } else { + delta = new ChangeDelta(original, revised); + } + + + patch.addDelta(delta); + if (path.isSnake()) + path = path.prev; + } + return patch; + } + +} diff --git a/src/difflib/myers/PathNode.java b/src/difflib/myers/PathNode.java new file mode 100644 index 0000000..4e98082 --- /dev/null +++ b/src/difflib/myers/PathNode.java @@ -0,0 +1,146 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package difflib.myers; + +/** + * A node in a diffpath. + * + * @version $Revision: 69 $ $Date: 2003-10-13 11:00:44 +0300 (Пнд, 13 Окт 2003) $ + * @author Juanco Anez + * + * @see DiffNode + * @see Snake + * + */ +public abstract class PathNode +{ + /** Position in the original sequence. */ + public final int i; + /** Position in the revised sequence. */ + public final int j; + /** The previous node in the path. */ + public final PathNode prev; + + /** + * Concatenates a new path node with an existing diffpath. + * @param i The position in the original sequence for the new node. + * @param j The position in the revised sequence for the new node. + * @param prev The previous node in the path. + */ + public PathNode(int i, int j, PathNode prev) + { + this.i = i; + this.j = j; + this.prev = prev; + } + + /** + * Is this node a {@link Snake Snake node}? + * @return true if this is a {@link Snake Snake node} + */ + public abstract boolean isSnake(); + + /** + * Is this a bootstrap node? + *

+ * In bottstrap nodes one of the two corrdinates is + * less than zero. + * @return tru if this is a bootstrap node. + */ + public boolean isBootstrap() + { + return i < 0 || j < 0; + } + + /** + * Skips sequences of {@link DiffNode DiffNodes} until a + * {@link Snake} or bootstrap node is found, or the end + * of the path is reached. + * @return The next first {@link Snake} or bootstrap node in the path, or + * null + * if none found. + */ + public final PathNode previousSnake() + { + if (isBootstrap()) + return null; + if (!isSnake() && prev != null) + return prev.previousSnake(); + return this; + } + + /** + * {@inheritDoc} + */ + public String toString() + { + StringBuffer buf = new StringBuffer("["); + PathNode node = this; + while (node != null) + { + buf.append("("); + buf.append(Integer.toString(node.i)); + buf.append(","); + buf.append(Integer.toString(node.j)); + buf.append(")"); + node = node.prev; + } + buf.append("]"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/src/difflib/myers/Snake.java b/src/difflib/myers/Snake.java new file mode 100644 index 0000000..b19b17e --- /dev/null +++ b/src/difflib/myers/Snake.java @@ -0,0 +1,97 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 The Apache Software Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgement may appear in the software itself, + * if and wherever such third-party acknowledgements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package difflib.myers; + +/** + * Represents a snake in a diffpath. + *

+ * + * {@link DiffNode DiffNodes} and {@link Snake Snakes} allow for compression + * of diffpaths, as each snake is represented by a single {@link Snake Snake} + * node and each contiguous series of insertions and deletions is represented + * by a single {@link DiffNode DiffNodes}. + * + * @version $Revision: 69 $ $Date: 2003-10-13 11:00:44 +0300 (Пнд, 13 Окт 2003) $ + * @author Juanco Anez + * + */ +public final class Snake + extends PathNode +{ + /** + * Constructs a snake node. + * + * @param the position in the original sequence + * @param the position in the revised sequence + * @param prev the previous node in the path. + */ + public Snake(int i, int j, PathNode prev) + { + super(i, j, prev); + } + + /** + * {@inheritDoc} + * @return true always + */ + public boolean isSnake() + { + return true; + } + +} \ No newline at end of file diff --git a/src/difflib/myers/package.html b/src/difflib/myers/package.html new file mode 100644 index 0000000..eb148c7 --- /dev/null +++ b/src/difflib/myers/package.html @@ -0,0 +1,80 @@ + + + + + + + + +

+ The {@link org.apache.maven.jrcs.diff.myers diff.myers} package + implements Gene Myers' + differencing algorithm. +

+

+ Myer's algorithm produces optimum results (minimum diffs), but + consumes considerably more memory than SimpleDiff, so its not + suitable for very large files. +

+@author Juanco Anez +@version $Id: package.html 60 2003-05-10 18:56:10Z juanco $ +@see Diff +@see org.apache.maven.jrcs.rcs.Archive + + diff --git a/test/testcase/diffutills/DiffTest.java b/test/testcase/diffutills/DiffTest.java new file mode 100644 index 0000000..4e8450c --- /dev/null +++ b/test/testcase/diffutills/DiffTest.java @@ -0,0 +1,56 @@ +package testcase.diffutills; + +import java.util.*; + +import junit.framework.TestCase; +import difflib.*; + +public class DiffTest extends TestCase { + private List changeTest_from = Arrays.asList("aaa", "bbb", "ccc"); + private List changeTest_to = Arrays.asList("aaa", "zzz", "ccc"); + + private List deleteTest_from = Arrays.asList("ddd", "fff", "ggg"); + private List deleteTest_to = Arrays.asList("ggg"); + + private List insertTest_from = Arrays.asList("hhh"); + private List insertTest_to = Arrays.asList("hhh", "jjj", "kkk"); + + @SuppressWarnings("serial") + public void testDiff_Insert() { + Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); + assertNotNull(patch); + assertEquals(patch.getDeltas().size(), 1); + Delta delta = patch.getDeltas().get(0); + assertEquals(InsertDelta.class, delta.getClass()); + assertEquals(new Chunk(1, 0, new LinkedList()), + delta.getOriginal()); + assertEquals(new Chunk(1, 2, new LinkedList() {{ add("jjj"); add("kkk"); }}), + delta.getRevised()); + } + + @SuppressWarnings("serial") + public void testDiff_Delete() { + Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); + assertNotNull(patch); + assertEquals(patch.getDeltas().size(), 1); + Delta delta = patch.getDeltas().get(0); + assertEquals(DeleteDelta.class, delta.getClass()); + assertEquals(new Chunk(0, 2, new LinkedList() {{ add("ddd"); add("fff"); }}), + delta.getOriginal()); + assertEquals(new Chunk(0, 0, new LinkedList()), + delta.getRevised()); + } + + @SuppressWarnings("serial") + public void testDiff_Change() { + Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + assertNotNull(patch); + assertEquals(patch.getDeltas().size(), 1); + Delta delta = patch.getDeltas().get(0); + assertEquals(ChangeDelta.class, delta.getClass()); + assertEquals(new Chunk(1, 1, new LinkedList() {{ add("bbb"); }}), + delta.getOriginal()); + assertEquals(new Chunk(1, 1, new LinkedList() {{ add("zzz"); }}), + delta.getRevised()); + } +} diff --git a/test/testcase/diffutills/PatchTest.java b/test/testcase/diffutills/PatchTest.java new file mode 100644 index 0000000..2f1dd93 --- /dev/null +++ b/test/testcase/diffutills/PatchTest.java @@ -0,0 +1,32 @@ +package testcase.diffutills; + +import java.util.*; + +import junit.framework.TestCase; +import difflib.*; + +public class PatchTest extends TestCase { + private List changeTest_from = Arrays.asList("aaa", "bbb", "ccc", "ddd"); + private List changeTest_to = Arrays.asList("aaa", "bxb", "cxc", "ddd"); + + private List deleteTest_from = Arrays.asList("ddd", "fff", "ggg", "hhh"); + private List deleteTest_to = Arrays.asList("ggg"); + + private List insertTest_from = Arrays.asList("hhh"); + private List insertTest_to = Arrays.asList("hhh", "jjj", "kkk", "lll"); + + public void testPatch_Insert() { + Patch patch = DiffUtils.diff(insertTest_from, insertTest_to); + assertEquals(insertTest_to, DiffUtils.patch(insertTest_from, patch)); + } + + public void testPatch_Delete() { + Patch patch = DiffUtils.diff(deleteTest_from, deleteTest_to); + assertEquals(deleteTest_to, DiffUtils.patch(deleteTest_from, patch)); + } + + public void testPatch_Change() { + Patch patch = DiffUtils.diff(changeTest_from, changeTest_to); + assertEquals(changeTest_to, DiffUtils.patch(changeTest_from, patch)); + } +}