From a5eb28afa104beb10a62fcbc5363826ec5dee07f Mon Sep 17 00:00:00 2001 From: Tyler Heck Date: Fri, 27 Dec 2019 09:37:51 -0700 Subject: [PATCH 1/3] Update diff match patch with decode URI and H(null)L pattern guard --- .../neil/plaintext/diff_match_patch.java | 129 ++++++++++++++++-- 1 file changed, 117 insertions(+), 12 deletions(-) diff --git a/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java b/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java index 38cabd46..234abcfd 100644 --- a/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java +++ b/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java @@ -1432,17 +1432,20 @@ public String diff_toDelta(LinkedList diffs) { char lastEnd = 0; boolean isFirst = true; for (Diff aDiff : diffs) { + if (aDiff.text.isEmpty()) { + continue; + } char thisTop = aDiff.text.charAt(0); char thisEnd = aDiff.text.charAt(aDiff.text.length() - 1); if (Character.isHighSurrogate(thisEnd)) { + lastEnd = thisEnd; aDiff.text = aDiff.text.substring(0, aDiff.text.length() - 1); } - if (! isFirst && Character.isHighSurrogate(lastEnd) && Character.isLowSurrogate(thisTop)) { + if (!isFirst && Character.isHighSurrogate(lastEnd) && Character.isLowSurrogate(thisTop)) { aDiff.text = lastEnd + aDiff.text; } isFirst = false; - lastEnd = thisEnd; - if ( aDiff.text.isEmpty() ) { + if (aDiff.text.isEmpty()) { continue; } switch (aDiff.operation) { @@ -1472,6 +1475,92 @@ public String diff_toDelta(LinkedList diffs) { return delta; } + private int digit16(char c) throws IllegalArgumentException { + switch (c) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'A': case 'a': return 10; + case 'B': case 'b': return 11; + case 'C': case 'c': return 12; + case 'D': case 'd': return 13; + case 'E': case 'e': return 14; + case 'F': case 'f': return 15; + default: throw new IllegalArgumentException(); + } + } + + private String decodeURI(String text) throws IllegalArgumentException { + int i = 0; + StringBuilder decoded = new StringBuilder(text.length()); + while (i < text.length()) { + if (text.charAt(i) != '%') { + decoded.append(text.charAt(i++)); + continue; + } + // start a percent-sequence + int byte1 = (digit16(text.charAt(i + 1)) << 4) + digit16(text.charAt(i + 2)); + if ((byte1 & 0x80) == 0) { + decoded.append(Character.toChars(byte1)); + i += 3; + continue; + } + if (text.charAt(i + 3) != '%') { + throw new IllegalArgumentException(); + } + int byte2 = (digit16(text.charAt(i + 4)) << 4) + digit16(text.charAt(i + 5)); + if ((byte2 & 0xC0) != 0x80) { + throw new IllegalArgumentException(); + } + byte2 = byte2 & 0x3F; + if ((byte1 & 0xE0) == 0xC0) { + decoded.append(Character.toChars(((byte1 & 0x1F) << 6) | byte2)); + i += 6; + continue; + } + if (text.charAt(i + 6) != '%') { + throw new IllegalArgumentException(); + } + int byte3 = (digit16(text.charAt(i + 7)) << 4) + digit16(text.charAt(i + 8)); + if ((byte3 & 0xC0) != 0x80) { + throw new IllegalArgumentException(); + } + byte3 = byte3 & 0x3F; + if ((byte1 & 0xF0) == 0xE0) { + // unpaired surrogate are fine here + decoded.append(Character.toChars(((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3)); + i += 9; + continue; + } + if (text.charAt(i + 9) != '%') { + throw new IllegalArgumentException(); + } + int byte4 = (digit16(text.charAt(i + 10)) << 4) + digit16(text.charAt(i + 11)); + if ((byte4 & 0xC0) != 0x80) { + throw new IllegalArgumentException(); + } + byte4 = byte4 & 0x3F; + if ((byte1 & 0xF8) == 0xF0) { + int codePoint = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0C) | (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + decoded.append(Character.toChars((codePoint & 0xFFFF) >>> 10 & 0x3FF | 0xD800)); + decoded.append(Character.toChars(0xDC00 | (codePoint & 0xFFFF) & 0x3FF)); + i += 12; + continue; + } + } + throw new IllegalArgumentException(); + } + return decoded.toString(); + } + /** * Given the original text1, and an encoded string which describes the * operations required to transform text1 into text2, compute the full diff. @@ -1485,7 +1574,8 @@ public LinkedList diff_fromDelta(String text1, String delta) LinkedList diffs = new LinkedList(); int pointer = 0; // Cursor in text1 String[] tokens = delta.split("\t"); - for (String token : tokens) { + for (int x = 0; x < tokens.length; x++) { + String token = tokens[x]; if (token.length() == 0) { // Blank tokens are ok (from a trailing \t). continue; @@ -1498,10 +1588,7 @@ public LinkedList diff_fromDelta(String text1, String delta) // decode would change all "+" to " " param = param.replace("+", "%2B"); try { - param = URLDecoder.decode(param, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); + param = this.decodeURI(param); } catch (IllegalArgumentException e) { // Malformed URI sequence. throw new IllegalArgumentException( @@ -1524,6 +1611,27 @@ public LinkedList diff_fromDelta(String text1, String delta) "Negative number in diff_fromDelta: " + param); } String text; + // some objective-c versions of the library produced patches with + // (null) in the place where surrogates were split across diff + // boundaries. if we leave those in we'll be stuck with a + // high-surrogate (null) low-surrogate pattern that will break + // deeper in the library or consuming application. we'll "fix" + // these by dropping the (null) and re-joining the surrogate halves + if (x + 2 < tokens.length && + Character.isHighSurrogate(text1.charAt(pointer + n - 1)) && + tokens[x + 1].substring(1).equals("(null)") && + Character.isLowSurrogate(text1.charAt(pointer + n))) { + n -= 1; + tokens[x + 1] = "+"; + int m; + try { + m = Integer.parseInt(tokens[x + 2].substring(1)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid number in diff_fromDelta: " + tokens[x + 2].substring(1), e); + } + tokens[x + 2] = tokens[x + 2].charAt(0) + String.valueOf(m + 1); + } try { text = text1.substring(pointer, pointer += n); } catch (StringIndexOutOfBoundsException e) { @@ -2284,10 +2392,7 @@ public List patch_fromText(String textline) line = text.getFirst().substring(1); line = line.replace("+", "%2B"); // decode would change all "+" to " " try { - line = URLDecoder.decode(line, "UTF-8"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); + line = this.decodeURI(line); } catch (IllegalArgumentException e) { // Malformed URI sequence. throw new IllegalArgumentException( From 91bd10e563d2a76687cc419c2b59176e8a7d557a Mon Sep 17 00:00:00 2001 From: Tyler Heck Date: Fri, 27 Dec 2019 09:38:03 -0700 Subject: [PATCH 2/3] Update version from 0.9.3 to 0.9.4 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b7292609..6c27ce56 100644 --- a/build.gradle +++ b/build.gradle @@ -35,5 +35,5 @@ def gitDescribe() { } def static gitVersion() { - '0.9.3' + '0.9.4' } From 977ace2d3cec3d7a3cf9d049d6f1797eff86f31b Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Tue, 14 Jan 2020 20:14:45 -0700 Subject: [PATCH 3/3] Remove attempted H(null)L fix --- .../neil/plaintext/diff_match_patch.java | 793 +++++++++--------- 1 file changed, 394 insertions(+), 399 deletions(-) diff --git a/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java b/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java index 234abcfd..fb5b1108 100644 --- a/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java +++ b/Simperium/src/main/java/name/fraser/neil/plaintext/diff_match_patch.java @@ -1,8 +1,7 @@ /* * Diff Match and Patch - * - * Copyright 2006 Google Inc. - * http://code.google.com/p/google-diff-match-patch/ + * Copyright 2018 The diff-match-patch Authors. + * https://github.com/google/diff-match-patch * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +19,9 @@ package name.fraser.neil.plaintext; import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; +import java.lang.Character; import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Stack; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -95,7 +87,7 @@ protected static class LinesToCharsResult { protected List lineArray; protected LinesToCharsResult(String chars1, String chars2, - List lineArray) { + List lineArray) { this.chars1 = chars1; this.chars2 = chars2; this.lineArray = lineArray; @@ -239,7 +231,7 @@ private LinkedList diff_compute(String text1, String text2, if (i != -1) { // Shorter text is inside the longer text (speedup). Operation op = (text1.length() > text2.length()) ? - Operation.DELETE : Operation.INSERT; + Operation.DELETE : Operation.INSERT; diffs.add(new Diff(op, longtext.substring(0, i))); diffs.add(new Diff(Operation.EQUAL, shorttext)); diffs.add(new Diff(op, longtext.substring(i + shorttext.length()))); @@ -265,9 +257,9 @@ private LinkedList diff_compute(String text1, String text2, String mid_common = hm[4]; // Send both pairs off for separate processing. LinkedList diffs_a = diff_main(text1_a, text2_a, - checklines, deadline); + checklines, deadline); LinkedList diffs_b = diff_main(text1_b, text2_b, - checklines, deadline); + checklines, deadline); // Merge the results. diffs = diffs_a; diffs.add(new Diff(Operation.EQUAL, mid_common)); @@ -294,10 +286,10 @@ private LinkedList diff_compute(String text1, String text2, private LinkedList diff_lineMode(String text1, String text2, long deadline) { // Scan the text on a line-by-line basis first. - LinesToCharsResult b = diff_linesToChars(text1, text2); - text1 = b.chars1; - text2 = b.chars2; - List linearray = b.lineArray; + LinesToCharsResult a = diff_linesToChars(text1, text2); + text1 = a.chars1; + text2 = a.chars2; + List linearray = a.lineArray; LinkedList diffs = diff_main(text1, text2, false, deadline); @@ -317,33 +309,33 @@ private LinkedList diff_lineMode(String text1, String text2, Diff thisDiff = pointer.next(); while (thisDiff != null) { switch (thisDiff.operation) { - case INSERT: - count_insert++; - text_insert += thisDiff.text; - break; - case DELETE: - count_delete++; - text_delete += thisDiff.text; - break; - case EQUAL: - // Upon reaching an equality, check for prior redundancies. - if (count_delete >= 1 && count_insert >= 1) { - // Delete the offending records and add the merged ones. - pointer.previous(); - for (int j = 0; j < count_delete + count_insert; j++) { + case INSERT: + count_insert++; + text_insert += thisDiff.text; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + break; + case EQUAL: + // Upon reaching an equality, check for prior redundancies. + if (count_delete >= 1 && count_insert >= 1) { + // Delete the offending records and add the merged ones. pointer.previous(); - pointer.remove(); - } - for (Diff newDiff : diff_main(text_delete, text_insert, false, - deadline)) { - pointer.add(newDiff); + for (int j = 0; j < count_delete + count_insert; j++) { + pointer.previous(); + pointer.remove(); + } + for (Diff subDiff : diff_main(text_delete, text_insert, false, + deadline)) { + pointer.add(subDiff); + } } - } - count_insert = 0; - count_delete = 0; - text_delete = ""; - text_insert = ""; - break; + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + break; } thisDiff = pointer.hasNext() ? pointer.next() : null; } @@ -362,7 +354,7 @@ private LinkedList diff_lineMode(String text1, String text2, * @return LinkedList of Diff objects. */ protected LinkedList diff_bisect(String text1, String text2, - long deadline) { + long deadline) { // Cache the text lengths to prevent multiple calls. int text1_length = text1.length(); int text2_length = text2.length(); @@ -404,7 +396,7 @@ protected LinkedList diff_bisect(String text1, String text2, } int y1 = x1 - k1; while (x1 < text1_length && y1 < text2_length - && text1.charAt(x1) == text2.charAt(y1)) { + && text1.charAt(x1) == text2.charAt(y1)) { x1++; y1++; } @@ -439,8 +431,8 @@ protected LinkedList diff_bisect(String text1, String text2, } int y2 = x2 - k2; while (x2 < text1_length && y2 < text2_length - && text1.charAt(text1_length - x2 - 1) - == text2.charAt(text2_length - y2 - 1)) { + && text1.charAt(text1_length - x2 - 1) + == text2.charAt(text2_length - y2 - 1)) { x2++; y2++; } @@ -518,8 +510,9 @@ protected LinesToCharsResult diff_linesToChars(String text1, String text2) { // So we'll insert a junk entry to avoid generating a null character. lineArray.add(""); - String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash); - String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash); + // Allocate 2/3rds of the space for text1, the rest for text2. + String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash, 40000); + String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash, 65535); return new LinesToCharsResult(chars1, chars2, lineArray); } @@ -529,10 +522,11 @@ protected LinesToCharsResult diff_linesToChars(String text1, String text2) { * @param text String to encode. * @param lineArray List of unique strings. * @param lineHash Map of strings to indices. + * @param maxLines Maximum length of lineArray. * @return Encoded string. */ private String diff_linesToCharsMunge(String text, List lineArray, - Map lineHash) { + Map lineHash, int maxLines) { int lineStart = 0; int lineEnd = -1; String line; @@ -546,15 +540,21 @@ private String diff_linesToCharsMunge(String text, List lineArray, lineEnd = text.length() - 1; } line = text.substring(lineStart, lineEnd + 1); - lineStart = lineEnd + 1; if (lineHash.containsKey(line)) { chars.append(String.valueOf((char) (int) lineHash.get(line))); } else { + if (lineArray.size() == maxLines) { + // Bail out at 65535 because + // String.valueOf((char) 65536).equals(String.valueOf(((char) 0))) + line = text.substring(lineStart); + lineEnd = text.length(); + } lineArray.add(line); lineHash.put(line, lineArray.size() - 1); chars.append(String.valueOf((char) (lineArray.size() - 1))); } + lineStart = lineEnd + 1; } return chars.toString(); } @@ -562,16 +562,16 @@ private String diff_linesToCharsMunge(String text, List lineArray, /** * Rehydrate the text in a diff from a string of line hashes to real lines of * text. - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @param lineArray List of unique strings. */ - protected void diff_charsToLines(LinkedList diffs, - List lineArray) { + protected void diff_charsToLines(List diffs, + List lineArray) { StringBuilder text; for (Diff diff : diffs) { text = new StringBuilder(); - for (int y = 0; y < diff.text.length(); y++) { - text.append(lineArray.get(diff.text.charAt(y))); + for (int j = 0; j < diff.text.length(); j++) { + text.append(lineArray.get(diff.text.charAt(j))); } diff.text = text.toString(); } @@ -584,7 +584,7 @@ protected void diff_charsToLines(LinkedList diffs, * @return The number of characters common to the start of each string. */ public int diff_commonPrefix(String text1, String text2) { - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ int n = Math.min(text1.length(), text2.length()); for (int i = 0; i < n; i++) { if (text1.charAt(i) != text2.charAt(i)) { @@ -601,7 +601,7 @@ public int diff_commonPrefix(String text1, String text2) { * @return The number of characters common to the end of each string. */ public int diff_commonSuffix(String text1, String text2) { - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ + // Performance analysis: https://neil.fraser.name/news/2007/10/09/ int text1_length = text1.length(); int text2_length = text2.length(); int n = Math.min(text1_length, text2_length); @@ -642,7 +642,7 @@ protected int diff_commonOverlap(String text1, String text2) { // Start by looking for a single character match // and increase length until no match is found. - // Performance analysis: http://neil.fraser.name/news/2010/11/04/ + // Performance analysis: https://neil.fraser.name/news/2010/11/04/ int best = 0; int length = 1; while (true) { @@ -653,7 +653,7 @@ protected int diff_commonOverlap(String text1, String text2) { } length += found; if (found == 0 || text1.substring(text_length - length).equals( - text2.substring(0, length))) { + text2.substring(0, length))) { best = length; length++; } @@ -683,10 +683,10 @@ protected String[] diff_halfMatch(String text1, String text2) { // First check if the second quarter is the seed for a half-match. String[] hm1 = diff_halfMatchI(longtext, shorttext, - (longtext.length() + 3) / 4); + (longtext.length() + 3) / 4); // Check again based on the third quarter. String[] hm2 = diff_halfMatchI(longtext, shorttext, - (longtext.length() + 1) / 2); + (longtext.length() + 1) / 2); String[] hm; if (hm1 == null && hm2 == null) { return null; @@ -727,12 +727,12 @@ private String[] diff_halfMatchI(String longtext, String shorttext, int i) { String best_shorttext_a = "", best_shorttext_b = ""; while ((j = shorttext.indexOf(seed, j + 1)) != -1) { int prefixLength = diff_commonPrefix(longtext.substring(i), - shorttext.substring(j)); + shorttext.substring(j)); int suffixLength = diff_commonSuffix(longtext.substring(0, i), - shorttext.substring(0, j)); + shorttext.substring(0, j)); if (best_common.length() < suffixLength + prefixLength) { best_common = shorttext.substring(j - suffixLength, j) - + shorttext.substring(j, j + prefixLength); + + shorttext.substring(j, j + prefixLength); best_longtext_a = longtext.substring(0, i - suffixLength); best_longtext_b = longtext.substring(i + prefixLength); best_shorttext_a = shorttext.substring(0, j - suffixLength); @@ -741,7 +741,7 @@ private String[] diff_halfMatchI(String longtext, String shorttext, int i) { } if (best_common.length() * 2 >= longtext.length()) { return new String[]{best_longtext_a, best_longtext_b, - best_shorttext_a, best_shorttext_b, best_common}; + best_shorttext_a, best_shorttext_b, best_common}; } else { return null; } @@ -756,8 +756,8 @@ public void diff_cleanupSemantic(LinkedList diffs) { return; } boolean changes = false; - Stack equalities = new Stack(); // Stack of qualities. - String lastequality = null; // Always equal to equalities.lastElement().text + Deque equalities = new ArrayDeque(); // Double-ended queue of qualities. + String lastEquality = null; // Always equal to equalities.peek().text ListIterator pointer = diffs.listIterator(); // Number of characters that changed prior to the equality. int length_insertions1 = 0; @@ -774,7 +774,7 @@ public void diff_cleanupSemantic(LinkedList diffs) { length_deletions1 = length_deletions2; length_insertions2 = 0; length_deletions2 = 0; - lastequality = thisDiff.text; + lastEquality = thisDiff.text; } else { // An insertion or deletion. if (thisDiff.operation == Operation.INSERT) { @@ -784,35 +784,35 @@ public void diff_cleanupSemantic(LinkedList diffs) { } // Eliminate an equality that is smaller or equal to the edits on both // sides of it. - if (lastequality != null && (lastequality.length() - <= Math.max(length_insertions1, length_deletions1)) - && (lastequality.length() + if (lastEquality != null && (lastEquality.length() + <= Math.max(length_insertions1, length_deletions1)) + && (lastEquality.length() <= Math.max(length_insertions2, length_deletions2))) { - //System.out.println("Splitting: '" + lastequality + "'"); + //System.out.println("Splitting: '" + lastEquality + "'"); // Walk back to offending equality. - while (thisDiff != equalities.lastElement()) { + while (thisDiff != equalities.peek()) { thisDiff = pointer.previous(); } pointer.next(); // Replace equality with a delete. - pointer.set(new Diff(Operation.DELETE, lastequality)); + pointer.set(new Diff(Operation.DELETE, lastEquality)); // Insert a corresponding an insert. - pointer.add(new Diff(Operation.INSERT, lastequality)); + pointer.add(new Diff(Operation.INSERT, lastEquality)); equalities.pop(); // Throw away the equality we just deleted. - if (!equalities.empty()) { + if (!equalities.isEmpty()) { // Throw away the previous equality (it needs to be reevaluated). equalities.pop(); } - if (equalities.empty()) { + if (equalities.isEmpty()) { // There are no previous equalities, walk back to the start. while (pointer.hasPrevious()) { pointer.previous(); } } else { // There is a safe equality we can fall back to. - thisDiff = equalities.lastElement(); + thisDiff = equalities.peek(); while (thisDiff != pointer.previous()) { // Intentionally empty loop. } @@ -822,7 +822,7 @@ public void diff_cleanupSemantic(LinkedList diffs) { length_insertions2 = 0; length_deletions1 = 0; length_deletions2 = 0; - lastequality = null; + lastEquality = null; changes = true; } } @@ -852,35 +852,35 @@ public void diff_cleanupSemantic(LinkedList diffs) { } while (thisDiff != null) { if (prevDiff.operation == Operation.DELETE && - thisDiff.operation == Operation.INSERT) { + thisDiff.operation == Operation.INSERT) { String deletion = prevDiff.text; String insertion = thisDiff.text; int overlap_length1 = this.diff_commonOverlap(deletion, insertion); int overlap_length2 = this.diff_commonOverlap(insertion, deletion); if (overlap_length1 >= overlap_length2) { if (overlap_length1 >= deletion.length() / 2.0 || - overlap_length1 >= insertion.length() / 2.0) { + overlap_length1 >= insertion.length() / 2.0) { // Overlap found. Insert an equality and trim the surrounding edits. pointer.previous(); pointer.add(new Diff(Operation.EQUAL, - insertion.substring(0, overlap_length1))); + insertion.substring(0, overlap_length1))); prevDiff.text = - deletion.substring(0, deletion.length() - overlap_length1); + deletion.substring(0, deletion.length() - overlap_length1); thisDiff.text = insertion.substring(overlap_length1); // pointer.add inserts the element before the cursor, so there is // no need to step past the new element. } } else { if (overlap_length2 >= deletion.length() / 2.0 || - overlap_length2 >= insertion.length() / 2.0) { + overlap_length2 >= insertion.length() / 2.0) { // Reverse overlap found. // Insert an equality and swap and trim the surrounding edits. pointer.previous(); pointer.add(new Diff(Operation.EQUAL, - deletion.substring(0, overlap_length2))); + deletion.substring(0, overlap_length2))); prevDiff.operation = Operation.INSERT; prevDiff.text = - insertion.substring(0, insertion.length() - overlap_length2); + insertion.substring(0, insertion.length() - overlap_length2); thisDiff.operation = Operation.DELETE; thisDiff.text = deletion.substring(overlap_length2); // pointer.add inserts the element before the cursor, so there is @@ -914,7 +914,7 @@ public void diff_cleanupSemanticLossless(LinkedList diffs) { // Intentionally ignore the first and last element (don't need checking). while (nextDiff != null) { if (prevDiff.operation == Operation.EQUAL && - nextDiff.operation == Operation.EQUAL) { + nextDiff.operation == Operation.EQUAL) { // This is a single edit surrounded by equalities. equality1 = prevDiff.text; edit = thisDiff.text; @@ -934,14 +934,14 @@ public void diff_cleanupSemanticLossless(LinkedList diffs) { bestEdit = edit; bestEquality2 = equality2; bestScore = diff_cleanupSemanticScore(equality1, edit) - + diff_cleanupSemanticScore(edit, equality2); + + diff_cleanupSemanticScore(edit, equality2); while (edit.length() != 0 && equality2.length() != 0 - && edit.charAt(0) == equality2.charAt(0)) { + && edit.charAt(0) == equality2.charAt(0)) { equality1 += edit.charAt(0); edit = edit.substring(1) + equality2.charAt(0); equality2 = equality2.substring(1); score = diff_cleanupSemanticScore(equality1, edit) - + diff_cleanupSemanticScore(edit, equality2); + + diff_cleanupSemanticScore(edit, equality2); // The >= encourages trailing rather than leading whitespace on edits. if (score >= bestScore) { bestScore = score; @@ -1005,9 +1005,9 @@ private int diff_cleanupSemanticScore(String one, String two) { boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1); boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2); boolean lineBreak1 = whitespace1 - && Character.getType(char1) == Character.CONTROL; + && Character.getType(char1) == Character.CONTROL; boolean lineBreak2 = whitespace2 - && Character.getType(char2) == Character.CONTROL; + && Character.getType(char2) == Character.CONTROL; boolean blankLine1 = lineBreak1 && BLANKLINEEND.matcher(one).find(); boolean blankLine2 = lineBreak2 && BLANKLINESTART.matcher(two).find(); @@ -1032,9 +1032,9 @@ private int diff_cleanupSemanticScore(String one, String two) { // Define some regex patterns for matching boundaries. private Pattern BLANKLINEEND - = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL); + = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL); private Pattern BLANKLINESTART - = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL); + = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL); /** * Reduce the number of edits by eliminating operationally trivial equalities. @@ -1045,8 +1045,8 @@ public void diff_cleanupEfficiency(LinkedList diffs) { return; } boolean changes = false; - Stack equalities = new Stack(); // Stack of equalities. - String lastequality = null; // Always equal to equalities.lastElement().text + Deque equalities = new ArrayDeque(); // Double-ended queue of equalities. + String lastEquality = null; // Always equal to equalities.peek().text ListIterator pointer = diffs.listIterator(); // Is there an insertion operation before the last equality. boolean pre_ins = false; @@ -1057,7 +1057,7 @@ public void diff_cleanupEfficiency(LinkedList diffs) { // Is there a deletion operation after the last equality. boolean post_del = false; Diff thisDiff = pointer.next(); - Diff safeDiff = thisDiff; // The last Diff that is known to be unsplitable. + Diff safeDiff = thisDiff; // The last Diff that is known to be unsplittable. while (thisDiff != null) { if (thisDiff.operation == Operation.EQUAL) { // Equality found. @@ -1066,11 +1066,11 @@ public void diff_cleanupEfficiency(LinkedList diffs) { equalities.push(thisDiff); pre_ins = post_ins; pre_del = post_del; - lastequality = thisDiff.text; + lastEquality = thisDiff.text; } else { // Not a candidate, and can never become one. equalities.clear(); - lastequality = null; + lastEquality = null; safeDiff = thisDiff; } post_ins = post_del = false; @@ -1089,42 +1089,42 @@ public void diff_cleanupEfficiency(LinkedList diffs) { * AXCD * ABXC */ - if (lastequality != null - && ((pre_ins && pre_del && post_ins && post_del) - || ((lastequality.length() < Diff_EditCost / 2) - && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) - + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { - //System.out.println("Splitting: '" + lastequality + "'"); + if (lastEquality != null + && ((pre_ins && pre_del && post_ins && post_del) + || ((lastEquality.length() < Diff_EditCost / 2) + && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) { + //System.out.println("Splitting: '" + lastEquality + "'"); // Walk back to offending equality. - while (thisDiff != equalities.lastElement()) { + while (thisDiff != equalities.peek()) { thisDiff = pointer.previous(); } pointer.next(); // Replace equality with a delete. - pointer.set(new Diff(Operation.DELETE, lastequality)); + pointer.set(new Diff(Operation.DELETE, lastEquality)); // Insert a corresponding an insert. - pointer.add(thisDiff = new Diff(Operation.INSERT, lastequality)); + pointer.add(thisDiff = new Diff(Operation.INSERT, lastEquality)); equalities.pop(); // Throw away the equality we just deleted. - lastequality = null; + lastEquality = null; if (pre_ins && pre_del) { // No changes made which could affect previous entry, keep going. post_ins = post_del = true; equalities.clear(); safeDiff = thisDiff; } else { - if (!equalities.empty()) { + if (!equalities.isEmpty()) { // Throw away the previous equality (it needs to be reevaluated). equalities.pop(); } - if (equalities.empty()) { + if (equalities.isEmpty()) { // There are no previous questionable equalities, // walk back to the last known safe diff. thisDiff = safeDiff; } else { // There is an equality we can fall back to. - thisDiff = equalities.lastElement(); + thisDiff = equalities.peek(); } while (thisDiff != pointer.previous()) { // Intentionally empty loop. @@ -1160,81 +1160,81 @@ public void diff_cleanupMerge(LinkedList diffs) { int commonlength; while (thisDiff != null) { switch (thisDiff.operation) { - case INSERT: - count_insert++; - text_insert += thisDiff.text; - prevEqual = null; - break; - case DELETE: - count_delete++; - text_delete += thisDiff.text; - prevEqual = null; - break; - case EQUAL: - if (count_delete + count_insert > 1) { - boolean both_types = count_delete != 0 && count_insert != 0; - // Delete the offending records. - pointer.previous(); // Reverse direction. - while (count_delete-- > 0) { - pointer.previous(); - pointer.remove(); - } - while (count_insert-- > 0) { - pointer.previous(); - pointer.remove(); - } - if (both_types) { - // Factor out any common prefixies. - commonlength = diff_commonPrefix(text_insert, text_delete); - if (commonlength != 0) { - if (pointer.hasPrevious()) { - thisDiff = pointer.previous(); - assert thisDiff.operation == Operation.EQUAL - : "Previous diff should have been an equality."; - thisDiff.text += text_insert.substring(0, commonlength); - pointer.next(); - } else { - pointer.add(new Diff(Operation.EQUAL, - text_insert.substring(0, commonlength))); - } - text_insert = text_insert.substring(commonlength); - text_delete = text_delete.substring(commonlength); + case INSERT: + count_insert++; + text_insert += thisDiff.text; + prevEqual = null; + break; + case DELETE: + count_delete++; + text_delete += thisDiff.text; + prevEqual = null; + break; + case EQUAL: + if (count_delete + count_insert > 1) { + boolean both_types = count_delete != 0 && count_insert != 0; + // Delete the offending records. + pointer.previous(); // Reverse direction. + while (count_delete-- > 0) { + pointer.previous(); + pointer.remove(); } - // Factor out any common suffixies. - commonlength = diff_commonSuffix(text_insert, text_delete); - if (commonlength != 0) { - thisDiff = pointer.next(); - thisDiff.text = text_insert.substring(text_insert.length() - - commonlength) + thisDiff.text; - text_insert = text_insert.substring(0, text_insert.length() - - commonlength); - text_delete = text_delete.substring(0, text_delete.length() - - commonlength); + while (count_insert-- > 0) { pointer.previous(); + pointer.remove(); } + if (both_types) { + // Factor out any common prefixies. + commonlength = diff_commonPrefix(text_insert, text_delete); + if (commonlength != 0) { + if (pointer.hasPrevious()) { + thisDiff = pointer.previous(); + assert thisDiff.operation == Operation.EQUAL + : "Previous diff should have been an equality."; + thisDiff.text += text_insert.substring(0, commonlength); + pointer.next(); + } else { + pointer.add(new Diff(Operation.EQUAL, + text_insert.substring(0, commonlength))); + } + text_insert = text_insert.substring(commonlength); + text_delete = text_delete.substring(commonlength); + } + // Factor out any common suffixies. + commonlength = diff_commonSuffix(text_insert, text_delete); + if (commonlength != 0) { + thisDiff = pointer.next(); + thisDiff.text = text_insert.substring(text_insert.length() + - commonlength) + thisDiff.text; + text_insert = text_insert.substring(0, text_insert.length() + - commonlength); + text_delete = text_delete.substring(0, text_delete.length() + - commonlength); + pointer.previous(); + } + } + // Insert the merged records. + if (text_delete.length() != 0) { + pointer.add(new Diff(Operation.DELETE, text_delete)); + } + if (text_insert.length() != 0) { + pointer.add(new Diff(Operation.INSERT, text_insert)); + } + // Step forward to the equality. + thisDiff = pointer.hasNext() ? pointer.next() : null; + } else if (prevEqual != null) { + // Merge this equality with the previous one. + prevEqual.text += thisDiff.text; + pointer.remove(); + thisDiff = pointer.previous(); + pointer.next(); // Forward direction } - // Insert the merged records. - if (text_delete.length() != 0) { - pointer.add(new Diff(Operation.DELETE, text_delete)); - } - if (text_insert.length() != 0) { - pointer.add(new Diff(Operation.INSERT, text_insert)); - } - // Step forward to the equality. - thisDiff = pointer.hasNext() ? pointer.next() : null; - } else if (prevEqual != null) { - // Merge this equality with the previous one. - prevEqual.text += thisDiff.text; - pointer.remove(); - thisDiff = pointer.previous(); - pointer.next(); // Forward direction - } - count_insert = 0; - count_delete = 0; - text_delete = ""; - text_insert = ""; - prevEqual = thisDiff; - break; + count_insert = 0; + count_delete = 0; + text_delete = ""; + text_insert = ""; + prevEqual = thisDiff; + break; } thisDiff = pointer.hasNext() ? pointer.next() : null; } @@ -1257,13 +1257,13 @@ public void diff_cleanupMerge(LinkedList diffs) { // Intentionally ignore the first and last element (don't need checking). while (nextDiff != null) { if (prevDiff.operation == Operation.EQUAL && - nextDiff.operation == Operation.EQUAL) { + nextDiff.operation == Operation.EQUAL) { // This is a single edit surrounded by equalities. if (thisDiff.text.endsWith(prevDiff.text)) { // Shift the edit over the previous equality. thisDiff.text = prevDiff.text - + thisDiff.text.substring(0, thisDiff.text.length() - - prevDiff.text.length()); + + thisDiff.text.substring(0, thisDiff.text.length() + - prevDiff.text.length()); nextDiff.text = prevDiff.text + nextDiff.text; pointer.previous(); // Walk past nextDiff. pointer.previous(); // Walk past thisDiff. @@ -1277,7 +1277,7 @@ public void diff_cleanupMerge(LinkedList diffs) { // Shift the edit over the next equality. prevDiff.text += nextDiff.text; thisDiff.text = thisDiff.text.substring(nextDiff.text.length()) - + nextDiff.text; + + nextDiff.text; pointer.remove(); // Delete nextDiff. nextDiff = pointer.hasNext() ? pointer.next() : null; changes = true; @@ -1297,11 +1297,11 @@ public void diff_cleanupMerge(LinkedList diffs) { * loc is a location in text1, compute and return the equivalent location in * text2. * e.g. "The cat" vs "The big cat", 1->1, 5->8 - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @param loc Location within text1. * @return Location within text2. */ - public int diff_xIndex(LinkedList diffs, int loc) { + public int diff_xIndex(List diffs, int loc) { int chars1 = 0; int chars2 = 0; int last_chars1 = 0; @@ -1334,26 +1334,26 @@ public int diff_xIndex(LinkedList diffs, int loc) { /** * Convert a Diff list into a pretty HTML report. - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @return HTML representation. */ - public String diff_prettyHtml(LinkedList diffs) { + public String diff_prettyHtml(List diffs) { StringBuilder html = new StringBuilder(); for (Diff aDiff : diffs) { String text = aDiff.text.replace("&", "&").replace("<", "<") - .replace(">", ">").replace("\n", "¶
"); + .replace(">", ">").replace("\n", "¶
"); switch (aDiff.operation) { - case INSERT: - html.append("").append(text) - .append(""); - break; - case DELETE: - html.append("").append(text) - .append(""); - break; - case EQUAL: - html.append("").append(text).append(""); - break; + case INSERT: + html.append("").append(text) + .append(""); + break; + case DELETE: + html.append("").append(text) + .append(""); + break; + case EQUAL: + html.append("").append(text).append(""); + break; } } return html.toString(); @@ -1361,10 +1361,10 @@ public String diff_prettyHtml(LinkedList diffs) { /** * Compute and return the source text (all equalities and deletions). - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @return Source text. */ - public String diff_text1(LinkedList diffs) { + public String diff_text1(List diffs) { StringBuilder text = new StringBuilder(); for (Diff aDiff : diffs) { if (aDiff.operation != Operation.INSERT) { @@ -1376,10 +1376,10 @@ public String diff_text1(LinkedList diffs) { /** * Compute and return the destination text (all equalities and insertions). - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @return Destination text. */ - public String diff_text2(LinkedList diffs) { + public String diff_text2(List diffs) { StringBuilder text = new StringBuilder(); for (Diff aDiff : diffs) { if (aDiff.operation != Operation.DELETE) { @@ -1392,27 +1392,27 @@ public String diff_text2(LinkedList diffs) { /** * Compute the Levenshtein distance; the number of inserted, deleted or * substituted characters. - * @param diffs LinkedList of Diff objects. + * @param diffs List of Diff objects. * @return Number of changes. */ - public int diff_levenshtein(LinkedList diffs) { + public int diff_levenshtein(List diffs) { int levenshtein = 0; int insertions = 0; int deletions = 0; for (Diff aDiff : diffs) { switch (aDiff.operation) { - case INSERT: - insertions += aDiff.text.length(); - break; - case DELETE: - deletions += aDiff.text.length(); - break; - case EQUAL: - // A deletion and an insertion is one substitution. - levenshtein += Math.max(insertions, deletions); - insertions = 0; - deletions = 0; - break; + case INSERT: + insertions += aDiff.text.length(); + break; + case DELETE: + deletions += aDiff.text.length(); + break; + case EQUAL: + // A deletion and an insertion is one substitution. + levenshtein += Math.max(insertions, deletions); + insertions = 0; + deletions = 0; + break; } } levenshtein += Math.max(insertions, deletions); @@ -1424,10 +1424,10 @@ public int diff_levenshtein(LinkedList diffs) { * required to transform text1 into text2. * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. * Operations are tab-separated. Inserted text is escaped using %xx notation. - * @param diffs Array of Diff objects. + * @param diffs List of Diff objects. * @return Delta text. */ - public String diff_toDelta(LinkedList diffs) { + public String diff_toDelta(List diffs) { StringBuilder text = new StringBuilder(); char lastEnd = 0; boolean isFirst = true; @@ -1435,35 +1435,41 @@ public String diff_toDelta(LinkedList diffs) { if (aDiff.text.isEmpty()) { continue; } + char thisTop = aDiff.text.charAt(0); char thisEnd = aDiff.text.charAt(aDiff.text.length() - 1); + if (Character.isHighSurrogate(thisEnd)) { lastEnd = thisEnd; aDiff.text = aDiff.text.substring(0, aDiff.text.length() - 1); } - if (!isFirst && Character.isHighSurrogate(lastEnd) && Character.isLowSurrogate(thisTop)) { + + if (! isFirst && Character.isHighSurrogate(lastEnd) && Character.isLowSurrogate(thisTop)) { aDiff.text = lastEnd + aDiff.text; } + isFirst = false; - if (aDiff.text.isEmpty()) { + + if ( aDiff.text.isEmpty() ) { continue; } + switch (aDiff.operation) { - case INSERT: - try { - text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8") - .replace('+', ' ')).append("\t"); - } catch (UnsupportedEncodingException e) { - // Not likely on modern system. - throw new Error("This system does not support UTF-8.", e); - } - break; - case DELETE: - text.append("-").append(aDiff.text.length()).append("\t"); - break; - case EQUAL: - text.append("=").append(aDiff.text.length()).append("\t"); - break; + case INSERT: + try { + text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8") + .replace('+', ' ')).append("\t"); + } catch (UnsupportedEncodingException e) { + // Not likely on modern system. + throw new Error("This system does not support UTF-8.", e); + } + break; + case DELETE: + text.append("-").append(aDiff.text.length()).append("\t"); + break; + case EQUAL: + text.append("=").append(aDiff.text.length()).append("\t"); + break; } } String delta = text.toString(); @@ -1475,8 +1481,8 @@ public String diff_toDelta(LinkedList diffs) { return delta; } - private int digit16(char c) throws IllegalArgumentException { - switch (c) { + private int digit16(char b) throws IllegalArgumentException { + switch (b) { case '0': return 0; case '1': return 1; case '2': return 2; @@ -1493,18 +1499,21 @@ private int digit16(char c) throws IllegalArgumentException { case 'D': case 'd': return 13; case 'E': case 'e': return 14; case 'F': case 'f': return 15; - default: throw new IllegalArgumentException(); + default: + throw new IllegalArgumentException(); } } private String decodeURI(String text) throws IllegalArgumentException { int i = 0; StringBuilder decoded = new StringBuilder(text.length()); + while (i < text.length()) { if (text.charAt(i) != '%') { decoded.append(text.charAt(i++)); continue; } + // start a percent-sequence int byte1 = (digit16(text.charAt(i + 1)) << 4) + digit16(text.charAt(i + 2)); if ((byte1 & 0x80) == 0) { @@ -1512,9 +1521,11 @@ private String decodeURI(String text) throws IllegalArgumentException { i += 3; continue; } - if (text.charAt(i + 3) != '%') { + + if ( text.charAt(i + 3) != '%') { throw new IllegalArgumentException(); } + int byte2 = (digit16(text.charAt(i + 4)) << 4) + digit16(text.charAt(i + 5)); if ((byte2 & 0xC0) != 0x80) { throw new IllegalArgumentException(); @@ -1525,9 +1536,11 @@ private String decodeURI(String text) throws IllegalArgumentException { i += 6; continue; } + if (text.charAt(i + 6) != '%') { throw new IllegalArgumentException(); } + int byte3 = (digit16(text.charAt(i + 7)) << 4) + digit16(text.charAt(i + 8)); if ((byte3 & 0xC0) != 0x80) { throw new IllegalArgumentException(); @@ -1539,9 +1552,11 @@ private String decodeURI(String text) throws IllegalArgumentException { i += 9; continue; } + if (text.charAt(i + 9) != '%') { throw new IllegalArgumentException(); } + int byte4 = (digit16(text.charAt(i + 10)) << 4) + digit16(text.charAt(i + 11)); if ((byte4 & 0xC0) != 0x80) { throw new IllegalArgumentException(); @@ -1556,8 +1571,10 @@ private String decodeURI(String text) throws IllegalArgumentException { continue; } } + throw new IllegalArgumentException(); } + return decoded.toString(); } @@ -1570,12 +1587,11 @@ private String decodeURI(String text) throws IllegalArgumentException { * @throws IllegalArgumentException If invalid input. */ public LinkedList diff_fromDelta(String text1, String delta) - throws IllegalArgumentException { + throws IllegalArgumentException { LinkedList diffs = new LinkedList(); int pointer = 0; // Cursor in text1 String[] tokens = delta.split("\t"); - for (int x = 0; x < tokens.length; x++) { - String token = tokens[x]; + for (String token : tokens) { if (token.length() == 0) { // Blank tokens are ok (from a trailing \t). continue; @@ -1584,76 +1600,55 @@ public LinkedList diff_fromDelta(String text1, String delta) // operation of this token (delete, insert, equality). String param = token.substring(1); switch (token.charAt(0)) { - case '+': - // decode would change all "+" to " " - param = param.replace("+", "%2B"); - try { - param = this.decodeURI(param); - } catch (IllegalArgumentException e) { - // Malformed URI sequence. - throw new IllegalArgumentException( - "Illegal escape in diff_fromDelta: " + param, e); - } - diffs.add(new Diff(Operation.INSERT, param)); - break; - case '-': - // Fall through. - case '=': - int n; - try { - n = Integer.parseInt(param); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Invalid number in diff_fromDelta: " + param, e); - } - if (n < 0) { - throw new IllegalArgumentException( - "Negative number in diff_fromDelta: " + param); - } - String text; - // some objective-c versions of the library produced patches with - // (null) in the place where surrogates were split across diff - // boundaries. if we leave those in we'll be stuck with a - // high-surrogate (null) low-surrogate pattern that will break - // deeper in the library or consuming application. we'll "fix" - // these by dropping the (null) and re-joining the surrogate halves - if (x + 2 < tokens.length && - Character.isHighSurrogate(text1.charAt(pointer + n - 1)) && - tokens[x + 1].substring(1).equals("(null)") && - Character.isLowSurrogate(text1.charAt(pointer + n))) { - n -= 1; - tokens[x + 1] = "+"; - int m; + case '+': + // decode would change all "+" to " " + param = param.replace("+", "%2B"); + try { + param = this.decodeURI(param); + } catch (IllegalArgumentException e) { + // Malformed URI sequence. + throw new IllegalArgumentException( + "Illegal escape in diff_fromDelta: " + param, e); + } + diffs.add(new Diff(Operation.INSERT, param)); + break; + case '-': + // Fall through. + case '=': + int n; try { - m = Integer.parseInt(tokens[x + 2].substring(1)); + n = Integer.parseInt(param); } catch (NumberFormatException e) { throw new IllegalArgumentException( - "Invalid number in diff_fromDelta: " + tokens[x + 2].substring(1), e); + "Invalid number in diff_fromDelta: " + param, e); } - tokens[x + 2] = tokens[x + 2].charAt(0) + String.valueOf(m + 1); - } - try { - text = text1.substring(pointer, pointer += n); - } catch (StringIndexOutOfBoundsException e) { - throw new IllegalArgumentException("Delta length (" + pointer - + ") larger than source text length (" + text1.length() - + ").", e); - } - if (token.charAt(0) == '=') { - diffs.add(new Diff(Operation.EQUAL, text)); - } else { - diffs.add(new Diff(Operation.DELETE, text)); - } - break; - default: - // Anything else is an error. - throw new IllegalArgumentException( - "Invalid diff operation in diff_fromDelta: " + token.charAt(0)); + if (n < 0) { + throw new IllegalArgumentException( + "Negative number in diff_fromDelta: " + param); + } + String text; + try { + text = text1.substring(pointer, pointer += n); + } catch (StringIndexOutOfBoundsException e) { + throw new IllegalArgumentException("Delta length (" + pointer + + ") larger than source text length (" + text1.length() + + ").", e); + } + if (token.charAt(0) == '=') { + diffs.add(new Diff(Operation.EQUAL, text)); + } else { + diffs.add(new Diff(Operation.DELETE, text)); + } + break; + default: + // Anything else is an error. + throw new IllegalArgumentException( + "Invalid diff operation in diff_fromDelta: " + token.charAt(0)); } } if (pointer != text1.length()) { throw new IllegalArgumentException("Delta length (" + pointer - + ") smaller than source text length (" + text1.length() + ")."); + + ") smaller than source text length (" + text1.length() + ")."); } return diffs; } @@ -1684,7 +1679,7 @@ public int match_main(String text, String pattern, int loc) { // Nothing to match. return -1; } else if (loc + pattern.length() <= text.length() - && text.substring(loc, loc + pattern.length()).equals(pattern)) { + && text.substring(loc, loc + pattern.length()).equals(pattern)) { // Perfect match at the perfect spot! (Includes case of null pattern) return loc; } else { @@ -1703,7 +1698,7 @@ public int match_main(String text, String pattern, int loc) { */ protected int match_bitap(String text, String pattern, int loc) { assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits) - : "Pattern too long for this application."; + : "Pattern too long for this application."; // Initialise the alphabet. Map s = match_alphabet(pattern); @@ -1714,12 +1709,12 @@ protected int match_bitap(String text, String pattern, int loc) { int best_loc = text.indexOf(pattern, loc); if (best_loc != -1) { score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), - score_threshold); + score_threshold); // What about in the other direction? (speedup) best_loc = text.lastIndexOf(pattern, loc + pattern.length()); if (best_loc != -1) { score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern), - score_threshold); + score_threshold); } } @@ -1739,7 +1734,7 @@ protected int match_bitap(String text, String pattern, int loc) { bin_mid = bin_max; while (bin_min < bin_mid) { if (match_bitapScore(d, loc + bin_mid, loc, pattern) - <= score_threshold) { + <= score_threshold) { bin_min = bin_mid; } else { bin_max = bin_mid; @@ -1767,7 +1762,7 @@ protected int match_bitap(String text, String pattern, int loc) { } else { // Subsequent passes: fuzzy match. rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) - | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; + | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1]; } if ((rd[j] & matchmask) != 0) { double score = match_bitapScore(d, j - 1, loc, pattern); @@ -1853,23 +1848,23 @@ protected void patch_addContext(Patch patch, String text) { // Look for the first and last matches of pattern in text. If two different // matches are found, increase the pattern length. while (text.indexOf(pattern) != text.lastIndexOf(pattern) - && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { + && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) { padding += Patch_Margin; pattern = text.substring(Math.max(0, patch.start2 - padding), - Math.min(text.length(), patch.start2 + patch.length1 + padding)); + Math.min(text.length(), patch.start2 + patch.length1 + padding)); } // Add one chunk for good luck. padding += Patch_Margin; // Add the prefix. String prefix = text.substring(Math.max(0, patch.start2 - padding), - patch.start2); + patch.start2); if (prefix.length() != 0) { patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix)); } // Add the suffix. String suffix = text.substring(patch.start2 + patch.length1, - Math.min(text.length(), patch.start2 + patch.length1 + padding)); + Math.min(text.length(), patch.start2 + patch.length1 + padding)); if (suffix.length() != 0) { patch.diffs.addLast(new Diff(Operation.EQUAL, suffix)); } @@ -1926,8 +1921,8 @@ public LinkedList patch_make(LinkedList diffs) { * @return LinkedList of Patch objects. * @deprecated Prefer patch_make(String text1, LinkedList diffs). */ - public LinkedList patch_make(String text1, String text2, - LinkedList diffs) { + @Deprecated public LinkedList patch_make(String text1, String text2, + LinkedList diffs) { return patch_make(text1, diffs); } @@ -1963,42 +1958,42 @@ public LinkedList patch_make(String text1, LinkedList diffs) { } switch (aDiff.operation) { - case INSERT: - patch.diffs.add(aDiff); - patch.length2 += aDiff.text.length(); - postpatch_text = postpatch_text.substring(0, char_count2) - + aDiff.text + postpatch_text.substring(char_count2); - break; - case DELETE: - patch.length1 += aDiff.text.length(); - patch.diffs.add(aDiff); - postpatch_text = postpatch_text.substring(0, char_count2) - + postpatch_text.substring(char_count2 + aDiff.text.length()); - break; - case EQUAL: - if (aDiff.text.length() <= 2 * Patch_Margin - && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) { - // Small equality inside a patch. + case INSERT: patch.diffs.add(aDiff); - patch.length1 += aDiff.text.length(); patch.length2 += aDiff.text.length(); - } + postpatch_text = postpatch_text.substring(0, char_count2) + + aDiff.text + postpatch_text.substring(char_count2); + break; + case DELETE: + patch.length1 += aDiff.text.length(); + patch.diffs.add(aDiff); + postpatch_text = postpatch_text.substring(0, char_count2) + + postpatch_text.substring(char_count2 + aDiff.text.length()); + break; + case EQUAL: + if (aDiff.text.length() <= 2 * Patch_Margin + && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) { + // Small equality inside a patch. + patch.diffs.add(aDiff); + patch.length1 += aDiff.text.length(); + patch.length2 += aDiff.text.length(); + } - if (aDiff.text.length() >= 2 * Patch_Margin) { - // Time for a new patch. - if (!patch.diffs.isEmpty()) { - patch_addContext(patch, prepatch_text); - patches.add(patch); - patch = new Patch(); - // Unlike Unidiff, our patch lists have a rolling context. - // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff - // Update prepatch text & pos to reflect the application of the - // just completed patch. - prepatch_text = postpatch_text; - char_count1 = char_count2; + if (aDiff.text.length() >= 2 * Patch_Margin && !patch.diffs.isEmpty()) { + // Time for a new patch. + if (!patch.diffs.isEmpty()) { + patch_addContext(patch, prepatch_text); + patches.add(patch); + patch = new Patch(); + // Unlike Unidiff, our patch lists have a rolling context. + // https://github.com/google/diff-match-patch/wiki/Unidiff + // Update prepatch text & pos to reflect the application of the + // just completed patch. + prepatch_text = postpatch_text; + char_count1 = char_count2; + } } - } - break; + break; } // Update the current character count. @@ -2076,11 +2071,11 @@ public Object[] patch_apply(LinkedList patches, String text) { // patch_splitMax will only provide an oversized pattern in the case of // a monster delete. start_loc = match_main(text, - text1.substring(0, this.Match_MaxBits), expected_loc); + text1.substring(0, this.Match_MaxBits), expected_loc); if (start_loc != -1) { end_loc = match_main(text, - text1.substring(text1.length() - this.Match_MaxBits), - expected_loc + text1.length() - this.Match_MaxBits); + text1.substring(text1.length() - this.Match_MaxBits), + expected_loc + text1.length() - this.Match_MaxBits); if (end_loc == -1 || start_loc >= end_loc) { // Can't find valid trailing context. Drop this patch. start_loc = -1; @@ -2101,22 +2096,22 @@ public Object[] patch_apply(LinkedList patches, String text) { String text2; if (end_loc == -1) { text2 = text.substring(start_loc, - Math.min(start_loc + text1.length(), text.length())); + Math.min(start_loc + text1.length(), text.length())); } else { text2 = text.substring(start_loc, - Math.min(end_loc + this.Match_MaxBits, text.length())); + Math.min(end_loc + this.Match_MaxBits, text.length())); } if (text1.equals(text2)) { // Perfect match, just shove the replacement text in. text = text.substring(0, start_loc) + diff_text2(aPatch.diffs) - + text.substring(start_loc + text1.length()); + + text.substring(start_loc + text1.length()); } else { // Imperfect match. Run a diff to get a framework of equivalent // indices. LinkedList diffs = diff_main(text1, text2, false); if (text1.length() > this.Match_MaxBits - && diff_levenshtein(diffs) / (float) text1.length() - > this.Patch_DeleteThreshold) { + && diff_levenshtein(diffs) / (float) text1.length() + > this.Patch_DeleteThreshold) { // The end points match, but the content is unacceptably bad. results[x] = false; } else { @@ -2128,12 +2123,12 @@ && diff_levenshtein(diffs) / (float) text1.length() if (aDiff.operation == Operation.INSERT) { // Insertion text = text.substring(0, start_loc + index2) + aDiff.text - + text.substring(start_loc + index2); + + text.substring(start_loc + index2); } else if (aDiff.operation == Operation.DELETE) { // Deletion text = text.substring(0, start_loc + index2) - + text.substring(start_loc + diff_xIndex(diffs, - index1 + aDiff.text.length())); + + text.substring(start_loc + diff_xIndex(diffs, + index1 + aDiff.text.length())); } } if (aDiff.operation != Operation.DELETE) { @@ -2147,7 +2142,7 @@ && diff_levenshtein(diffs) / (float) text1.length() } // Strip the padding off. text = text.substring(nullPadding.length(), text.length() - - nullPadding.length()); + - nullPadding.length()); return new Object[]{text, results}; } @@ -2185,7 +2180,7 @@ public String patch_addPadding(LinkedList patches) { Diff firstDiff = diffs.getFirst(); int extraLength = paddingLength - firstDiff.text.length(); firstDiff.text = nullPadding.substring(firstDiff.text.length()) - + firstDiff.text; + + firstDiff.text; patch.start1 -= extraLength; patch.start2 -= extraLength; patch.length1 += extraLength; @@ -2249,7 +2244,7 @@ public void patch_splitMax(LinkedList patches) { patch.diffs.add(new Diff(Operation.EQUAL, precontext)); } while (!bigpatch.diffs.isEmpty() - && patch.length1 < patch_size - Patch_Margin) { + && patch.length1 < patch_size - Patch_Margin) { diff_type = bigpatch.diffs.getFirst().operation; diff_text = bigpatch.diffs.getFirst().text; if (diff_type == Operation.INSERT) { @@ -2259,8 +2254,8 @@ public void patch_splitMax(LinkedList patches) { patch.diffs.addLast(bigpatch.diffs.removeFirst()); empty = false; } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1 - && patch.diffs.getFirst().operation == Operation.EQUAL - && diff_text.length() > 2 * patch_size) { + && patch.diffs.getFirst().operation == Operation.EQUAL + && diff_text.length() > 2 * patch_size) { // This is a large deletion. Let it pass in one chunk. patch.length1 += diff_text.length(); start1 += diff_text.length(); @@ -2270,7 +2265,7 @@ public void patch_splitMax(LinkedList patches) { } else { // Deletion or equality. Only take as much as we can stomach. diff_text = diff_text.substring(0, Math.min(diff_text.length(), - patch_size - patch.length1 - Patch_Margin)); + patch_size - patch.length1 - Patch_Margin)); patch.length1 += diff_text.length(); start1 += diff_text.length(); if (diff_type == Operation.EQUAL) { @@ -2284,14 +2279,14 @@ public void patch_splitMax(LinkedList patches) { bigpatch.diffs.removeFirst(); } else { bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text - .substring(diff_text.length()); + .substring(diff_text.length()); } } } // Compute the head context for the next patch. precontext = diff_text2(patch.diffs); precontext = precontext.substring(Math.max(0, precontext.length() - - Patch_Margin)); + - Patch_Margin)); // Append the end context for this patch. if (diff_text1(bigpatch.diffs).length() > Patch_Margin) { postcontext = diff_text1(bigpatch.diffs).substring(0, Patch_Margin); @@ -2302,7 +2297,7 @@ public void patch_splitMax(LinkedList patches) { patch.length1 += postcontext.length(); patch.length2 += postcontext.length(); if (!patch.diffs.isEmpty() - && patch.diffs.getLast().operation == Operation.EQUAL) { + && patch.diffs.getLast().operation == Operation.EQUAL) { patch.diffs.getLast().text += postcontext; } else { patch.diffs.add(new Diff(Operation.EQUAL, postcontext)); @@ -2337,7 +2332,7 @@ public String patch_toText(List patches) { * @throws IllegalArgumentException If invalid input. */ public List patch_fromText(String textline) - throws IllegalArgumentException { + throws IllegalArgumentException { List patches = new LinkedList(); if (textline.length() == 0) { return patches; @@ -2346,7 +2341,7 @@ public List patch_fromText(String textline) LinkedList text = new LinkedList(textList); Patch patch; Pattern patchHeader - = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); + = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$"); Matcher m; char sign; String line; @@ -2354,7 +2349,7 @@ public List patch_fromText(String textline) m = patchHeader.matcher(text.getFirst()); if (!m.matches()) { throw new IllegalArgumentException( - "Invalid patch string: " + text.getFirst()); + "Invalid patch string: " + text.getFirst()); } patch = new Patch(); patches.add(patch); @@ -2396,7 +2391,7 @@ public List patch_fromText(String textline) } catch (IllegalArgumentException e) { // Malformed URI sequence. throw new IllegalArgumentException( - "Illegal escape in patch_fromText: " + line, e); + "Illegal escape in patch_fromText: " + line, e); } if (sign == '-') { // Deletion. @@ -2413,7 +2408,7 @@ public List patch_fromText(String textline) } else { // WTF? throw new IllegalArgumentException( - "Invalid patch mode '" + sign + "' in: " + line); + "Invalid patch mode '" + sign + "' in: " + line); } text.removeFirst(); } @@ -2518,9 +2513,9 @@ public Patch() { } /** - * Emmulate GNU diff's format. + * Emulate GNU diff's format. * Header: @@ -382,8 +481,9 @@ - * Indicies are printed as 1-based, not 0-based. + * Indices are printed as 1-based, not 0-based. * @return The GNU diff string. */ public String toString() { @@ -2541,23 +2536,23 @@ public String toString() { } StringBuilder text = new StringBuilder(); text.append("@@ -").append(coords1).append(" +").append(coords2) - .append(" @@\n"); + .append(" @@\n"); // Escape the body of the patch with %xx notation. for (Diff aDiff : this.diffs) { switch (aDiff.operation) { - case INSERT: - text.append('+'); - break; - case DELETE: - text.append('-'); - break; - case EQUAL: - text.append(' '); - break; + case INSERT: + text.append('+'); + break; + case DELETE: + text.append('-'); + break; + case EQUAL: + text.append(' '); + break; } try { text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' ')) - .append("\n"); + .append("\n"); } catch (UnsupportedEncodingException e) { // Not likely on modern system. throw new Error("This system does not support UTF-8.", e); @@ -2582,10 +2577,10 @@ public String toString() { */ private static String unescapeForEncodeUriCompatability(String str) { return str.replace("%21", "!").replace("%7E", "~") - .replace("%27", "'").replace("%28", "(").replace("%29", ")") - .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?") - .replace("%3A", ":").replace("%40", "@").replace("%26", "&") - .replace("%3D", "=").replace("%2B", "+").replace("%24", "$") - .replace("%2C", ",").replace("%23", "#"); + .replace("%27", "'").replace("%28", "(").replace("%29", ")") + .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?") + .replace("%3A", ":").replace("%40", "@").replace("%26", "&") + .replace("%3D", "=").replace("%2B", "+").replace("%24", "$") + .replace("%2C", ",").replace("%23", "#"); } }