View Javadoc
1   /**
2    * Diff Match and Patch
3    *
4    * Copyright 2006 Google Inc.
5    * http://code.google.com/p/google-diff-match-patch/
6    *
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package com.jsql.model.injection.strategy.blind.patch;
20  
21  import org.apache.commons.lang3.StringUtils;
22  
23  import java.util.*;
24  import java.util.regex.Pattern;
25  
26  /*
27   * Functions for diff, match and patch.
28   * Computes the difference between two texts to create a patch.
29   * Applies the patch onto another text, allowing for errors.
30   *
31   * @author fraser@google.com (Neil Fraser)
32   */
33  
34  /**
35   * Class containing the diff, match and patch methods.
36   * Also contains the behaviour settings.
37   */
38  public class DiffMatchPatch {
39  
40      // Defaults.
41      // Set these on your diff_match_patch instance to override the defaults.
42  
43      /**
44       * Number of seconds to map a diff before giving up (0 for infinity).
45       */
46      public static final float DIFF_TIMEOUT = 1.0f;
47  
48      /**
49       * Cost of an empty edit operation in terms of edit characters.
50       */
51      public static final short DIFF_EDIT_COST = 4;
52  
53      // Define some regex patterns for matching boundaries.
54      private static final Pattern BLANK_LINE_END = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL);
55      private static final Pattern BLANK_LINE_START = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL);
56  
57      /**
58       * Internal class for returning results from diff_linesToChars().
59       * Other less paranoid languages just use a three-element array.
60       */
61      protected record LinesToCharsResult(String chars1, String chars2, List<String> lineArray) {}
62  
63      //  DIFF FUNCTIONS
64  
65      /**
66       * The data structure representing a diff is a Linked list of Diff objects:
67       * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"),
68       *  Diff(Operation.EQUAL, " world.")}
69       * which means: delete "Hello", add "Goodbye" and keep " world."
70       */
71      public enum Operation {
72          DELETE, INSERT, EQUAL
73      }
74  
75      /**
76       * Find the differences between two texts.
77       * @param text1 Old string to be diffed.
78       * @param text2 New string to be diffed.
79       * @param checklines Speedup flag.  If false, then don't run a
80       *     line-level diff first to identify the changed areas.
81       *     If true, then run a faster slightly less optimal diff.
82       * @return Linked List of Diff objects.
83       */
84      public LinkedList<Diff> diffMain(String text1, String text2, boolean checklines) {
85          // Set a deadline by which time the diff must be complete.
86          long deadline = System.currentTimeMillis() + (long) (DiffMatchPatch.DIFF_TIMEOUT * 1000);
87          return this.diffMain(text1, text2, checklines, deadline);
88      }
89  
90      /**
91       * Find the differences between two texts.  Simplifies the problem by
92       * stripping any common prefix or suffix off the texts before diffing.
93       * @param valueText1 Old string to be diffed.
94       * @param valueText2 New string to be diffed.
95       * @param checklines Speedup flag.  If false, then don't run a
96       *     line-level diff first to identify the changed areas.
97       *     If true, then run a faster slightly less optimal diff.
98       * @param deadline Time when the diff should be complete by.  Used
99       *     internally for recursive calls.  Users should set DiffTimeout instead.
100      * @return Linked List of Diff objects.
101      */
102     private LinkedList<Diff> diffMain(String valueText1, String valueText2, boolean checklines, long deadline) {
103         String text1 = valueText1;
104         String text2 = valueText2;
105 
106         // Check for null inputs.
107         if (text1 == null || text2 == null) {
108             throw new IllegalArgumentException("Null inputs. (diff_main)");
109         }
110 
111         // Check for equality (speedup).
112         LinkedList<Diff> diffs;
113         if (text1.equals(text2)) {
114             diffs = new LinkedList<>();
115             if (!text1.isEmpty()) {
116                 diffs.add(new Diff(Operation.EQUAL, text1));
117             }
118             return diffs;
119         }
120 
121         // Trim off common prefix (speedup).
122         int commonlength = this.diffCommonPrefix(text1, text2);
123         String commonprefix = text1.substring(0, commonlength);
124         text1 = text1.substring(commonlength);
125         text2 = text2.substring(commonlength);
126 
127         // Trim off common suffix (speedup).
128         commonlength = this.diffCommonSuffix(text1, text2);
129         String commonsuffix = text1.substring(text1.length() - commonlength);
130         text1 = text1.substring(0, text1.length() - commonlength);
131         text2 = text2.substring(0, text2.length() - commonlength);
132 
133         // Compute the diff on the middle block.
134         diffs = this.diffCompute(text1, text2, checklines, deadline);
135 
136         // Restore the prefix and suffix.
137         if (!commonprefix.isEmpty()) {
138             diffs.addFirst(new Diff(Operation.EQUAL, commonprefix));
139         }
140         if (!commonsuffix.isEmpty()) {
141             diffs.addLast(new Diff(Operation.EQUAL, commonsuffix));
142         }
143 
144         this.diffCleanupMerge(diffs);
145         return diffs;
146     }
147 
148     /**
149      * Find the differences between two texts.  Assumes that the texts do not
150      * have any common prefix or suffix.
151      * @param text1 Old string to be diffed.
152      * @param text2 New string to be diffed.
153      * @param checklines Speedup flag.  If false, then don't run a
154      *     line-level diff first to identify the changed areas.
155      *     If true, then run a faster slightly less optimal diff.
156      * @param deadline Time when the diff should be complete by.
157      * @return Linked List of Diff objects.
158      */
159     private LinkedList<Diff> diffCompute(String text1, String text2, boolean checklines, long deadline) {
160         LinkedList<Diff> diffs = new LinkedList<>();
161 
162         if (text1.isEmpty()) {
163             // Just add some text (speedup).
164             diffs.add(new Diff(Operation.INSERT, text2));
165             return diffs;
166         }
167 
168         if (text2.isEmpty()) {
169             // Just delete some text (speedup).
170             diffs.add(new Diff(Operation.DELETE, text1));
171             return diffs;
172         }
173 
174         {
175             // New scope to garbage collect longtext and shorttext.
176             String longtext = text1.length() > text2.length() ? text1 : text2;
177             String shorttext = text1.length() > text2.length() ? text2 : text1;
178             int i = longtext.indexOf(shorttext);
179             if (i != -1) {
180                 // Shorter text is inside the longer text (speedup).
181                 Operation op = (text1.length() > text2.length()) ?
182                         Operation.DELETE : Operation.INSERT;
183                 diffs.add(new Diff(op, longtext.substring(0, i)));
184                 diffs.add(new Diff(Operation.EQUAL, shorttext));
185                 diffs.add(new Diff(op, longtext.substring(i + shorttext.length())));
186                 return diffs;
187             }
188 
189             if (shorttext.length() == 1) {
190                 // Single character string.
191                 // After the previous speedup, the character can't be an equality.
192                 diffs.add(new Diff(Operation.DELETE, text1));
193                 diffs.add(new Diff(Operation.INSERT, text2));
194                 return diffs;
195             }
196         }
197 
198         // Check to see if the problem can be split in two.
199         String[] hm = this.diffHalfMatch(text1, text2);
200         if (hm != null) {
201             // A half-match was found, sort out the return data.
202             String text1A = hm[0];
203             String text1B = hm[1];
204             String text2A = hm[2];
205             String text2B = hm[3];
206             String midCommon = hm[4];
207             // Send both pairs off for separate processing.
208             LinkedList<Diff> diffsA = this.diffMain(text1A, text2A, checklines, deadline);
209             List<Diff> diffsB = this.diffMain(text1B, text2B, checklines, deadline);
210             // Merge the results.
211             diffs = diffsA;
212             diffs.add(new Diff(Operation.EQUAL, midCommon));
213             diffs.addAll(diffsB);
214             return diffs;
215         }
216 
217         if (checklines && text1.length() > 100 && text2.length() > 100) {
218             return this.diffLineMode(text1, text2, deadline);
219         }
220 
221         return this.diffBisect(text1, text2, deadline);
222     }
223 
224     /**
225      * Do a quick line-level diff on both strings, then rediff the parts for
226      * greater accuracy.
227      * This speedup can produce non-minimal diffs.
228      * @param valueText1 Old string to be diffed.
229      * @param valueText2 New string to be diffed.
230      * @param deadline Time when the diff should be complete by.
231      * @return Linked List of Diff objects.
232      */
233     private LinkedList<Diff> diffLineMode(String valueText1, String valueText2, long deadline) {
234         // Scan the text on a line-by-line basis first.
235         LinesToCharsResult b = this.diffLinesToChars(valueText1, valueText2);
236         String text1 = b.chars1;
237         String text2 = b.chars2;
238         List<String> linearray = b.lineArray;
239 
240         LinkedList<Diff> diffs = this.diffMain(text1, text2, false, deadline);
241 
242         // Convert the diff back to original text.
243         this.diffCharsToLines(diffs, linearray);
244         // Eliminate freak matches (e.g. blank lines)
245         this.diffCleanupSemantic(diffs);
246 
247         // Rediff any replacement blocks, this time character-by-character.
248         // Add a dummy entry at the end.
249         diffs.add(new Diff(Operation.EQUAL, StringUtils.EMPTY));
250         int countDelete = 0;
251         int countInsert = 0;
252         StringBuilder textDelete = new StringBuilder();
253         StringBuilder textInsert = new StringBuilder();
254         ListIterator<Diff> pointer = diffs.listIterator();
255         Diff thisDiff = pointer.next();
256 
257         while (thisDiff != null) {
258             switch (thisDiff.getOperation()) {
259                 case INSERT:
260                     countInsert++;
261                     textInsert.append(thisDiff.getText());
262                     break;
263                 case DELETE:
264                     countDelete++;
265                     textDelete.append(thisDiff.getText());
266                     break;
267                 case EQUAL:
268                     // Upon reaching an equality, check for prior redundancies.
269                     if (countDelete >= 1 && countInsert >= 1) {
270                         // Delete the offending records and add the merged ones.
271                         pointer.previous();
272                         for (int j = 0; j < countDelete + countInsert; j++) {
273                             pointer.previous();
274                             pointer.remove();
275                         }
276                         for (Diff newDiff : this.diffMain(textDelete.toString(), textInsert.toString(), false, deadline)) {
277                             pointer.add(newDiff);
278                         }
279                     }
280                     countInsert = 0;
281                     countDelete = 0;
282                     textDelete.setLength(0);
283                     textInsert.setLength(0);
284                     break;
285             }
286             thisDiff = pointer.hasNext() ? pointer.next() : null;
287         }
288         diffs.removeLast();  // Remove the dummy entry at the end.
289 
290         return diffs;
291     }
292 
293     /**
294      * Find the 'middle snake' of a diff, split the problem in two
295      * and return the recursively constructed diff.
296      * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
297      * @param text1 Old string to be diffed.
298      * @param text2 New string to be diffed.
299      * @param deadline Time at which to bail if not yet complete.
300      * @return LinkedList of Diff objects.
301      */
302     protected LinkedList<Diff> diffBisect(String text1, String text2, long deadline) {
303         // Cache the text lengths to prevent multiple calls.
304         int text1Length = text1.length();
305         int text2Length = text2.length();
306         int maxD = (text1Length + text2Length + 1) / 2;
307         int vLength = 2 * maxD;
308         int[] v1 = new int[vLength];
309         int[] v2 = new int[vLength];
310         for (int x = 0; x < vLength; x++) {
311             v1[x] = -1;
312             v2[x] = -1;
313         }
314         v1[maxD + 1] = 0;
315         v2[maxD + 1] = 0;
316         int delta = text1Length - text2Length;
317         // If the total number of characters is odd, then the front path will
318         // collide with the reverse path.
319         boolean front = delta % 2 != 0;
320         // Offsets for start and end of k loop.
321         // Prevents mapping of space beyond the grid.
322         int k1start = 0;
323         int k1end = 0;
324         int k2start = 0;
325         int k2end = 0;
326 
327         for (int d = 0; d < maxD; d++) {
328             // Bail out if deadline is reached.
329             if (System.currentTimeMillis() > deadline) {
330                 break;
331             }
332 
333             // Walk the front path one step.
334             for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
335                 int k1Offset = maxD + k1;
336                 int x1;
337                 if (k1 == -d || (k1 != d && v1[k1Offset - 1] < v1[k1Offset + 1])) {
338                     x1 = v1[k1Offset + 1];
339                 } else {
340                     x1 = v1[k1Offset - 1] + 1;
341                 }
342                 int y1 = x1 - k1;
343                 while (x1 < text1Length && y1 < text2Length
344                         && text1.charAt(x1) == text2.charAt(y1)) {
345                     x1++;
346                     y1++;
347                 }
348                 v1[k1Offset] = x1;
349                 if (x1 > text1Length) {
350                     // Ran off the right of the graph.
351                     k1end += 2;
352                 } else if (y1 > text2Length) {
353                     // Ran off the bottom of the graph.
354                     k1start += 2;
355                 } else if (front) {
356                     int k2Offset = maxD + delta - k1;
357                     if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] != -1) {
358                         // Mirror x2 onto top-left coordinate system.
359                         int x2 = text1Length - v2[k2Offset];
360                         if (x1 >= x2) {
361                             // Overlap detected.
362                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
363                         }
364                     }
365                 }
366             }
367 
368             // Walk the reverse path one step.
369             for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
370                 int k2Offset = maxD + k2;
371                 int x2;
372                 if (k2 == -d || (k2 != d && v2[k2Offset - 1] < v2[k2Offset + 1])) {
373                     x2 = v2[k2Offset + 1];
374                 } else {
375                     x2 = v2[k2Offset - 1] + 1;
376                 }
377                 int y2 = x2 - k2;
378                 while (x2 < text1Length && y2 < text2Length
379                         && text1.charAt(text1Length - x2 - 1)
380                         == text2.charAt(text2Length - y2 - 1)) {
381                     x2++;
382                     y2++;
383                 }
384                 v2[k2Offset] = x2;
385                 if (x2 > text1Length) {
386                     // Ran off the left of the graph.
387                     k2end += 2;
388                 } else if (y2 > text2Length) {
389                     // Ran off the top of the graph.
390                     k2start += 2;
391                 } else if (!front) {
392                     int k1Offset = maxD + delta - k2;
393                     if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] != -1) {
394                         int x1 = v1[k1Offset];
395                         int y1 = maxD + x1 - k1Offset;
396                         // Mirror x2 onto top-left coordinate system.
397                         x2 = text1Length - x2;
398                         if (x1 >= x2) {
399                             // Overlap detected.
400                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
401                         }
402                     }
403                 }
404             }
405         }
406         // Diff took too long and hit the deadline or
407         // number of diffs equals number of characters, no commonality at all.
408         LinkedList<Diff> diffs = new LinkedList<>();
409         diffs.add(new Diff(Operation.DELETE, text1));
410         diffs.add(new Diff(Operation.INSERT, text2));
411         return diffs;
412     }
413 
414     /**
415      * Given the location of the 'middle snake', split the diff in two parts
416      * and recurse.
417      * @param text1 Old string to be diffed.
418      * @param text2 New string to be diffed.
419      * @param x Index of split point in text1.
420      * @param y Index of split point in text2.
421      * @param deadline Time at which to bail if not yet complete.
422      * @return LinkedList of Diff objects.
423      */
424     private LinkedList<Diff> diffBisectSplit(String text1, String text2, int x, int y, long deadline) {
425         String text1a = text1.substring(0, x);
426         String text2a = text2.substring(0, y);
427         String text1b = text1.substring(x);
428         String text2b = text2.substring(y);
429 
430         // Compute both diffs serially.
431         LinkedList<Diff> diffs = this.diffMain(text1a, text2a, false, deadline);
432         List<Diff> diffsb = this.diffMain(text1b, text2b, false, deadline);
433 
434         diffs.addAll(diffsb);
435         return diffs;
436     }
437 
438     /**
439      * Split two texts into a list of strings.  Reduce the texts to a string of
440      * hashes where each Unicode character represents one line.
441      * @param text1 First string.
442      * @param text2 Second string.
443      * @return An object containing the encoded text1, the encoded text2 and
444      *     the List of unique strings.  The zeroth element of the List of
445      *     unique strings is intentionally blank.
446      */
447     protected LinesToCharsResult diffLinesToChars(String text1, String text2) {
448         List<String> lineArray = new ArrayList<>();
449         Map<String, Integer> lineHash = new HashMap<>();
450         // e.g. linearray[4] == "Hello\n"
451         // e.g. linehash.get("Hello\n") == 4
452 
453         // "\x00" is a valid character, but various debuggers don't like it.
454         // So we'll insert a junk entry to avoid generating a null character.
455         lineArray.add(StringUtils.EMPTY);
456 
457         String chars1 = this.diffLinesToCharsMunge(text1, lineArray, lineHash);
458         String chars2 = this.diffLinesToCharsMunge(text2, lineArray, lineHash);
459         return new LinesToCharsResult(chars1, chars2, lineArray);
460     }
461 
462     /**
463      * Split a text into a list of strings.  Reduce the texts to a string of
464      * hashes where each Unicode character represents one line.
465      * @param text String to encode.
466      * @param lineArray List of unique strings.
467      * @param lineHash Map of strings to indices.
468      * @return Encoded string.
469      */
470     private String diffLinesToCharsMunge(String text, List<String> lineArray,
471                                          Map<String, Integer> lineHash) {
472         int lineStart = 0;
473         int lineEnd = -1;
474         String line;
475         StringBuilder chars = new StringBuilder();
476         // Walk the text, pulling out a substring for each line.
477         // text.split('\n') would would temporarily double our memory footprint.
478         // Modifying text would create many large strings to garbage collect.
479         while (lineEnd < text.length() - 1) {
480             lineEnd = text.indexOf('\n', lineStart);
481             if (lineEnd == -1) {
482                 lineEnd = text.length() - 1;
483             }
484             line = text.substring(lineStart, lineEnd + 1);
485             lineStart = lineEnd + 1;
486 
487             if (lineHash.containsKey(line)) {
488                 chars.append((char) (int) lineHash.get(line));
489             } else {
490                 lineArray.add(line);
491                 lineHash.put(line, lineArray.size() - 1);
492                 chars.append((char) (lineArray.size() - 1));
493             }
494         }
495         return chars.toString();
496     }
497 
498     /**
499      * Rehydrate the text in a diff from a string of line hashes to real lines of
500      * text.
501      * @param diffs LinkedList of Diff objects.
502      * @param lineArray List of unique strings.
503      */
504     protected void diffCharsToLines(List<Diff> diffs, List<String> lineArray) {
505         StringBuilder text;
506         for (Diff diff : diffs) {
507             text = new StringBuilder();
508             for (int y = 0; y < diff.getText().length(); y++) {
509                 text.append(lineArray.get(diff.getText().charAt(y)));
510             }
511             diff.setText(text.toString());
512         }
513     }
514 
515     /**
516      * Determine the common prefix of two strings
517      * @param text1 First string.
518      * @param text2 Second string.
519      * @return The number of characters common to the start of each string.
520      */
521     public int diffCommonPrefix(String text1, String text2) {
522         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
523         int n = Math.min(text1.length(), text2.length());
524         for (int i = 0; i < n; i++) {
525             if (text1.charAt(i) != text2.charAt(i)) {
526                 return i;
527             }
528         }
529         return n;
530     }
531 
532     /**
533      * Determine the common suffix of two strings
534      * @param text1 First string.
535      * @param text2 Second string.
536      * @return The number of characters common to the end of each string.
537      */
538     public int diffCommonSuffix(String text1, String text2) {
539         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
540         int text1Length = text1.length();
541         int text2Length = text2.length();
542         int n = Math.min(text1Length, text2Length);
543         for (int i = 1; i <= n; i++) {
544             if (text1.charAt(text1Length - i) != text2.charAt(text2Length - i)) {
545                 return i - 1;
546             }
547         }
548         return n;
549     }
550 
551     /**
552      * Determine if the suffix of one string is the prefix of another.
553      * @param valueText1 First string.
554      * @param valueText2 Second string.
555      * @return The number of characters common to the end of the first
556      *     string and the start of the second string.
557      */
558     protected int diffCommonOverlap(String valueText1, String valueText2) {
559         String text1 = valueText1;
560         String text2 = valueText2;
561 
562         // Cache the text lengths to prevent multiple calls.
563         int text1Length = text1.length();
564         int text2Length = text2.length();
565         // Eliminate the null case.
566         if (text1Length == 0 || text2Length == 0) {
567             return 0;
568         }
569         // Truncate the longer string.
570         if (text1Length > text2Length) {
571             text1 = text1.substring(text1Length - text2Length);
572         } else if (text1Length < text2Length) {
573             text2 = text2.substring(0, text1Length);
574         }
575         int textLength = Math.min(text1Length, text2Length);
576         // Quick check for the worst case.
577         if (text1.equals(text2)) {
578             return textLength;
579         }
580 
581         // Start by looking for a single character match
582         // and increase length until no match is found.
583         // Performance analysis: http://neil.fraser.name/news/2010/11/04/
584         int best = 0;
585         int length = 1;
586         while (true) {
587             String pattern = text1.substring(textLength - length);
588             int found = text2.indexOf(pattern);
589             if (found == -1) {
590                 return best;
591             }
592             length += found;
593             if (found == 0 || text1.substring(textLength - length).equals(
594                     text2.substring(0, length))) {
595                 best = length;
596                 length++;
597             }
598         }
599     }
600 
601     /**
602      * Do the two texts share a substring which is at least half the length of
603      * the longer text?
604      * This speedup can produce non-minimal diffs.
605      * @param text1 First string.
606      * @param text2 Second string.
607      * @return Five element String array, containing the prefix of text1, the
608      *     suffix of text1, the prefix of text2, the suffix of text2 and the
609      *     common middle.  Or null if there was no match.
610      */
611     protected String[] diffHalfMatch(String text1, String text2) {
612         String longtext = text1.length() > text2.length() ? text1 : text2;
613         String shorttext = text1.length() > text2.length() ? text2 : text1;
614         if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) {
615             return null;  // Pointless.
616         }
617 
618         // First check if the second quarter is the seed for a half-match.
619         String[] hm1 = this.diffHalfMatchI(longtext, shorttext, (longtext.length() + 3) / 4);
620         // Check again based on the third quarter.
621         String[] hm2 = this.diffHalfMatchI(longtext, shorttext, (longtext.length() + 1) / 2);
622         String[] hm;
623         if (hm1 == null && hm2 == null) {
624             return null;
625         } else if (hm2 == null) {
626             hm = hm1;
627         } else if (hm1 == null) {
628             hm = hm2;
629         } else {
630             // Both matched.  Select the longest.
631             hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
632         }
633 
634         // A half-match was found, sort out the return data.
635         if (text1.length() > text2.length()) {
636             return hm;
637         } else {
638             return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]};
639         }
640     }
641 
642     /**
643      * Does a substring of shorttext exist within longtext such that the
644      * substring is at least half the length of longtext?
645      * @param longtext Longer string.
646      * @param shorttext Shorter string.
647      * @param i Start index of quarter length substring within longtext.
648      * @return Five element String array, containing the prefix of longtext, the
649      *     suffix of longtext, the prefix of shorttext, the suffix of shorttext
650      *     and the common middle.  Or null if there was no match.
651      */
652     private String[] diffHalfMatchI(String longtext, String shorttext, int i) {
653         // Start with a 1/4 length substring at position i as a seed.
654         String seed = longtext.substring(i, i + longtext.length() / 4);
655         int j = -1;
656         String bestCommon = StringUtils.EMPTY;
657         String bestLongtextA = StringUtils.EMPTY;
658         String bestLongtextB = StringUtils.EMPTY;
659         String bestShorttextA = StringUtils.EMPTY;
660         String bestShorttextB = StringUtils.EMPTY;
661         while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
662             int prefixLength = this.diffCommonPrefix(longtext.substring(i),
663                     shorttext.substring(j));
664             int suffixLength = this.diffCommonSuffix(longtext.substring(0, i),
665                     shorttext.substring(0, j));
666             if (bestCommon.length() < suffixLength + prefixLength) {
667                 bestCommon = shorttext.substring(j - suffixLength, j)
668                         + shorttext.substring(j, j + prefixLength);
669                 bestLongtextA = longtext.substring(0, i - suffixLength);
670                 bestLongtextB = longtext.substring(i + prefixLength);
671                 bestShorttextA = shorttext.substring(0, j - suffixLength);
672                 bestShorttextB = shorttext.substring(j + prefixLength);
673             }
674         }
675         if (bestCommon.length() * 2 >= longtext.length()) {
676             return new String[]{bestLongtextA, bestLongtextB,
677                     bestShorttextA, bestShorttextB, bestCommon};
678         } else {
679             return null;
680         }
681     }
682 
683     /**
684      * Reduce the number of edits by eliminating semantically trivial equalities.
685      * @param diffs LinkedList of Diff objects.
686      */
687     public void diffCleanupSemantic(LinkedList<Diff> diffs) {
688         if (diffs.isEmpty()) {
689             return;
690         }
691         boolean changes = false;
692         // Synchronized Stack to avoid Exception
693         Stack<Diff> equalities = new Stack<>();  // Stack of qualities.
694         String lastequality = null; // Always equal to equalities.lastElement().text
695         ListIterator<Diff> pointer = diffs.listIterator();
696         // Number of characters that changed prior to the equality.
697         int lengthInsertions1 = 0;
698         int lengthDeletions1 = 0;
699         // Number of characters that changed after the equality.
700         int lengthInsertions2 = 0;
701         int lengthDeletions2 = 0;
702         Diff thisDiff = pointer.next();
703 
704         while (thisDiff != null) {
705             if (thisDiff.getOperation() == Operation.EQUAL) {
706                 // Equality found.
707                 equalities.push(thisDiff);
708                 lengthInsertions1 = lengthInsertions2;
709                 lengthDeletions1 = lengthDeletions2;
710                 lengthInsertions2 = 0;
711                 lengthDeletions2 = 0;
712                 lastequality = thisDiff.getText();
713             } else {
714                 // An insertion or deletion.
715                 if (thisDiff.getOperation() == Operation.INSERT) {
716                     lengthInsertions2 += thisDiff.getText().length();
717                 } else {
718                     lengthDeletions2 += thisDiff.getText().length();
719                 }
720                 // Eliminate an equality that is smaller or equal to the edits on both
721                 // sides of it.
722                 if (
723                         lastequality != null
724                                 && lastequality.length() <= Math.max(lengthInsertions1, lengthDeletions1)
725                                 && lastequality.length() <= Math.max(lengthInsertions2, lengthDeletions2)
726                 ) {
727                     // Walk back to offending equality.
728                     while (thisDiff != equalities.lastElement()) {
729                         thisDiff = pointer.previous();
730                     }
731                     pointer.next();
732 
733                     // Replace equality with a delete.
734                     pointer.set(new Diff(Operation.DELETE, lastequality));
735                     // Insert a corresponding an insert.
736                     pointer.add(new Diff(Operation.INSERT, lastequality));
737 
738                     equalities.pop();  // Throw away the equality we just deleted.
739                     if (!equalities.empty()) {
740                         // Throw away the previous equality (it needs to be reevaluated).
741                         equalities.pop();
742                     }
743                     if (equalities.empty()) {
744                         // There are no previous equalities, walk back to the start.
745                         while (pointer.hasPrevious()) {
746                             pointer.previous();
747                         }
748                     } else {
749                         // There is a safe equality we can fall back to.
750                         thisDiff = equalities.lastElement();
751                         while (thisDiff != pointer.previous()) {
752                             // Intentionally empty loop.
753                         }
754                     }
755 
756                     lengthInsertions1 = 0;  // Reset the counters.
757                     lengthInsertions2 = 0;
758                     lengthDeletions1 = 0;
759                     lengthDeletions2 = 0;
760                     lastequality = null;
761                     changes = true;
762                 }
763             }
764             thisDiff = pointer.hasNext() ? pointer.next() : null;
765         }
766 
767         // Normalize the diff.
768         if (changes) {
769             this.diffCleanupMerge(diffs);
770         }
771         this.diffCleanupSemanticLossless(diffs);
772 
773         // Find any overlaps between deletions and insertions.
774         // e.g: <del>abcxxx</del><ins>xxxdef</ins>
775         //   -> <del>abc</del>xxx<ins>def</ins>
776         // e.g: <del>xxxabc</del><ins>defxxx</ins>
777         //   -> <ins>def</ins>xxx<del>abc</del>
778         // Only extract an overlap if it is as big as the edit ahead or behind it.
779         pointer = diffs.listIterator();
780         Diff prevDiff = null;
781         thisDiff = null;
782         if (pointer.hasNext()) {
783             prevDiff = pointer.next();
784             if (pointer.hasNext()) {
785                 thisDiff = pointer.next();
786             }
787         }
788 
789         while (thisDiff != null) {
790             if (prevDiff.getOperation() == Operation.DELETE &&
791                     thisDiff.getOperation() == Operation.INSERT) {
792                 String deletion = prevDiff.getText();
793                 String insertion = thisDiff.getText();
794                 int overlapLength1 = this.diffCommonOverlap(deletion, insertion);
795                 int overlapLength2 = this.diffCommonOverlap(insertion, deletion);
796                 if (overlapLength1 >= overlapLength2) {
797                     if (overlapLength1 >= deletion.length() / 2.0 ||
798                             overlapLength1 >= insertion.length() / 2.0) {
799                         // Overlap found.  Insert an equality and trim the surrounding edits.
800                         pointer.previous();
801                         pointer.add(new Diff(Operation.EQUAL,
802                                 insertion.substring(0, overlapLength1)));
803                         prevDiff.setText(deletion.substring(0, deletion.length() - overlapLength1));
804                         thisDiff.setText(insertion.substring(overlapLength1));
805                         // pointer.add inserts the element before the cursor, so there is
806                         // no need to step past the new element.
807                     }
808                 } else {
809                     if (overlapLength2 >= deletion.length() / 2.0 ||
810                             overlapLength2 >= insertion.length() / 2.0) {
811                         // Reverse overlap found.
812                         // Insert an equality and swap and trim the surrounding edits.
813                         pointer.previous();
814                         pointer.add(new Diff(Operation.EQUAL,
815                                 deletion.substring(0, overlapLength2)));
816                         prevDiff.setOperation(Operation.INSERT);
817                         prevDiff.setText(insertion.substring(0, insertion.length() - overlapLength2));
818                         thisDiff.setOperation(Operation.DELETE);
819                         thisDiff.setText(deletion.substring(overlapLength2));
820                         // pointer.add inserts the element before the cursor, so there is
821                         // no need to step past the new element.
822                     }
823                 }
824                 thisDiff = pointer.hasNext() ? pointer.next() : null;
825             }
826             prevDiff = thisDiff;
827             thisDiff = pointer.hasNext() ? pointer.next() : null;
828         }
829     }
830 
831     /**
832      * Look for single edits surrounded on both sides by equalities
833      * which can be shifted sideways to align the edit to a word boundary.
834      * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
835      * @param diffs LinkedList of Diff objects.
836      */
837     public void diffCleanupSemanticLossless(List<Diff> diffs) {
838         StringBuilder equality1 = new StringBuilder();
839         String edit;
840         StringBuilder equality2 = new StringBuilder();
841         String commonString;
842         int commonOffset;
843         int score;
844         int bestScore;
845         String bestEquality1;
846         String bestEdit;
847         String bestEquality2;
848         // Create a new iterator at the start.
849         ListIterator<Diff> pointer = diffs.listIterator();
850         Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
851         Diff thisDiff = pointer.hasNext() ? pointer.next() : null;
852         Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
853 
854         // Intentionally ignore the first and last element (don't need checking).
855         while (nextDiff != null) {
856             if (prevDiff.getOperation() == Operation.EQUAL &&
857                     nextDiff.getOperation() == Operation.EQUAL) {
858                 // This is a single edit surrounded by equalities.
859                 equality1.setLength(0);
860                 equality1.append(prevDiff.getText());
861                 edit = thisDiff.getText();
862                 equality2.setLength(0);
863                 equality2.append(nextDiff.getText());
864 
865                 // First, shift the edit as far left as possible.
866                 commonOffset = this.diffCommonSuffix(equality1.toString(), edit);
867                 if (commonOffset != 0) {
868                     commonString = edit.substring(edit.length() - commonOffset);
869                     String substring = equality1.substring(0, equality1.length() - commonOffset);
870                     equality1.setLength(0);
871                     equality1.append(substring);
872                     edit = commonString + edit.substring(0, edit.length() - commonOffset);
873                     equality2.insert(0, commonString);
874                 }
875 
876                 // Second, step character by character right, looking for the best fit.
877                 bestEquality1 = equality1.toString();
878                 bestEdit = edit;
879                 bestEquality2 = equality2.toString();
880                 bestScore = this.diffCleanupSemanticScore(equality1.toString(), edit)
881                         + this.diffCleanupSemanticScore(edit, equality2.toString());
882                 while (!edit.isEmpty() && !equality2.isEmpty()
883                         && edit.charAt(0) == equality2.charAt(0)) {
884                     equality1.append(edit.charAt(0));
885                     edit = edit.substring(1) + equality2.charAt(0);
886                     String substring = equality2.substring(1);
887                     equality2.setLength(0);
888                     equality2.append(substring);
889                     score = this.diffCleanupSemanticScore(equality1.toString(), edit)
890                             + this.diffCleanupSemanticScore(edit, equality2.toString());
891                     // The >= encourages trailing rather than leading whitespace on edits.
892                     if (score >= bestScore) {
893                         bestScore = score;
894                         bestEquality1 = equality1.toString();
895                         bestEdit = edit;
896                         bestEquality2 = equality2.toString();
897                     }
898                 }
899 
900                 if (!prevDiff.getText().equals(bestEquality1)) {
901                     // We have an improvement, save it back to the diff.
902                     if (!bestEquality1.isEmpty()) {
903                         prevDiff.setText(bestEquality1);
904                     } else {
905                         pointer.previous(); // Walk past nextDiff.
906                         pointer.previous(); // Walk past thisDiff.
907                         pointer.previous(); // Walk past prevDiff.
908                         pointer.remove(); // Delete prevDiff.
909                         pointer.next(); // Walk past thisDiff.
910                         pointer.next(); // Walk past nextDiff.
911                     }
912                     thisDiff.setText(bestEdit);
913                     if (!bestEquality2.isEmpty()) {
914                         nextDiff.setText(bestEquality2);
915                     } else {
916                         pointer.remove(); // Delete nextDiff.
917                         nextDiff = thisDiff;
918                         thisDiff = prevDiff;
919                     }
920                 }
921             }
922             prevDiff = thisDiff;
923             thisDiff = nextDiff;
924             nextDiff = pointer.hasNext() ? pointer.next() : null;
925         }
926     }
927 
928     /**
929      * Given two strings, compute a score representing whether the internal
930      * boundary falls on logical boundaries.
931      * Scores range from 6 (best) to 0 (worst).
932      * @param one First string.
933      * @param two Second string.
934      * @return The score.
935      */
936     private int diffCleanupSemanticScore(String one, String two) {
937         if (one.isEmpty() || two.isEmpty()) {
938             // Edges are the best.
939             return 6;
940         }
941 
942         // Each port of this function behaves slightly differently due to
943         // subtle differences in each language's definition of things like
944         // 'whitespace'.  Since this function's purpose is largely cosmetic,
945         // the choice has been made to use each language's native features
946         // rather than force total conformity.
947         char char1 = one.charAt(one.length() - 1);
948         char char2 = two.charAt(0);
949         boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1);
950         boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2);
951         boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1);
952         boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2);
953         boolean lineBreak1 = whitespace1
954                 && Character.getType(char1) == Character.CONTROL;
955         boolean lineBreak2 = whitespace2
956                 && Character.getType(char2) == Character.CONTROL;
957         boolean blankLine1 = lineBreak1 && DiffMatchPatch.BLANK_LINE_END.matcher(one).find();
958         boolean blankLine2 = lineBreak2 && DiffMatchPatch.BLANK_LINE_START.matcher(two).find();
959 
960         if (blankLine1 || blankLine2) {
961             // Five points for blank lines.
962             return 5;
963         } else if (lineBreak1 || lineBreak2) {
964             // Four points for line breaks.
965             return 4;
966         } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
967             // Three points for end of sentences.
968             return 3;
969         } else if (whitespace1 || whitespace2) {
970             // Two points for whitespace.
971             return 2;
972         } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
973             // One point for non-alphanumeric.
974             return 1;
975         }
976         return 0;
977     }
978 
979     /**
980      * Reduce the number of edits by eliminating operationally trivial equalities.
981      * @param diffs LinkedList of Diff objects.
982      */
983     public void diffCleanupEfficiency(LinkedList<Diff> diffs) {
984         if (diffs.isEmpty()) {
985             return;
986         }
987         boolean changes = false;
988         // Synchronized Stack to avoid Exception
989         Stack<Diff> equalities = new Stack<>();  // Stack of equalities.
990         String lastequality = null; // Always equal to equalities.lastElement().text
991         ListIterator<Diff> pointer = diffs.listIterator();
992         // Is there an insertion operation before the last equality.
993         boolean preIns = false;
994         // Is there a deletion operation before the last equality.
995         boolean preDel = false;
996         // Is there an insertion operation after the last equality.
997         boolean postIns = false;
998         // Is there a deletion operation after the last equality.
999         boolean postDel = false;
1000         Diff thisDiff = pointer.next();
1001         Diff safeDiff = thisDiff;  // The last Diff that is known to be unsplitable.
1002         while (thisDiff != null) {
1003 
1004             if (thisDiff.getOperation() == Operation.EQUAL) {
1005 
1006                 // Equality found.
1007                 if (thisDiff.getText().length() < DiffMatchPatch.DIFF_EDIT_COST && (postIns || postDel)) {
1008                     // Candidate found.
1009                     equalities.push(thisDiff);
1010                     preIns = postIns;
1011                     preDel = postDel;
1012                     lastequality = thisDiff.getText();
1013                 } else {
1014                     // Not a candidate, and can never become one.
1015                     equalities.clear();
1016                     lastequality = null;
1017                     safeDiff = thisDiff;
1018                 }
1019                 postIns = postDel = false;
1020             } else {
1021                 // An insertion or deletion.
1022                 if (thisDiff.getOperation() == Operation.DELETE) {
1023                     postDel = true;
1024                 } else {
1025                     postIns = true;
1026                 }
1027 
1028                 /*
1029                  * Five types to be split:
1030                  * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
1031                  * <ins>A</ins>X<ins>C</ins><del>D</del>
1032                  * <ins>A</ins><del>B</del>X<ins>C</ins>
1033                  * <ins>A</del>X<ins>C</ins><del>D</del>
1034                  * <ins>A</ins><del>B</del>X<del>C</del>
1035                  */
1036                 if (
1037                         lastequality != null
1038                                 && (
1039                                 (preIns && preDel && postIns && postDel)
1040                                         || (
1041                                         (lastequality.length() < DiffMatchPatch.DIFF_EDIT_COST / 2)
1042                                                 && ((preIns ? 1 : 0) + (preDel ? 1 : 0) + (postIns ? 1 : 0) + (postDel ? 1 : 0)) == 3
1043                                 )
1044                         )
1045                 ) {
1046                     // Walk back to offending equality.
1047                     while (thisDiff != equalities.lastElement()) {
1048                         thisDiff = pointer.previous();
1049                     }
1050                     pointer.next();
1051 
1052                     // Replace equality with a delete.
1053                     pointer.set(new Diff(Operation.DELETE, lastequality));
1054                     // Insert a corresponding an insert.
1055                     thisDiff = new Diff(Operation.INSERT, lastequality);
1056                     pointer.add(thisDiff);
1057 
1058                     equalities.pop();  // Throw away the equality we just deleted.
1059                     lastequality = null;
1060                     if (preIns && preDel) {
1061                         // No changes made which could affect previous entry, keep going.
1062                         postIns = postDel = true;
1063                         equalities.clear();
1064                         safeDiff = thisDiff;
1065                     } else {
1066                         if (!equalities.empty()) {
1067                             // Throw away the previous equality (it needs to be reevaluated).
1068                             equalities.pop();
1069                         }
1070                         if (equalities.empty()) {
1071                             // There are no previous questionable equalities,
1072                             // walk back to the last known safe diff.
1073                             thisDiff = safeDiff;
1074                         } else {
1075                             // There is an equality we can fall back to.
1076                             thisDiff = equalities.lastElement();
1077                         }
1078                         while (thisDiff != pointer.previous()) {
1079                             // Intentionally empty loop.
1080                         }
1081                         postIns = postDel = false;
1082                     }
1083 
1084                     changes = true;
1085                 }
1086             }
1087             thisDiff = pointer.hasNext() ? pointer.next() : null;
1088         }
1089 
1090         if (changes) {
1091             this.diffCleanupMerge(diffs);
1092         }
1093     }
1094 
1095     /**
1096      * Reorder and merge like edit sections.  Merge equalities.
1097      * Any edit section can move as long as it doesn't cross an equality.
1098      * @param diffs LinkedList of Diff objects.
1099      */
1100     public void diffCleanupMerge(LinkedList<Diff> diffs) {
1101         diffs.add(new Diff(Operation.EQUAL, StringUtils.EMPTY));  // Add a dummy entry at the end.
1102         ListIterator<Diff> pointer = diffs.listIterator();
1103         int countDelete = 0;
1104         int countInsert = 0;
1105         StringBuilder textDelete = new StringBuilder();
1106         StringBuilder textInsert = new StringBuilder();
1107         Diff thisDiff = pointer.next();
1108         Diff prevEqual = null;
1109         int commonlength;
1110         while (thisDiff != null) {
1111             switch (thisDiff.getOperation()) {
1112                 case INSERT:
1113                     countInsert++;
1114                     textInsert.append(thisDiff.getText());
1115                     prevEqual = null;
1116                     break;
1117                 case DELETE:
1118                     countDelete++;
1119                     textDelete.append(thisDiff.getText());
1120                     prevEqual = null;
1121                     break;
1122                 case EQUAL:
1123                     if (countDelete + countInsert > 1) {
1124 
1125                         boolean bothTypes = countDelete != 0 && countInsert != 0;
1126                         // Delete the offending records.
1127                         pointer.previous();  // Reverse direction.
1128                         while (countDelete-- > 0) {
1129                             pointer.previous();
1130                             pointer.remove();
1131                         }
1132                         while (countInsert-- > 0) {
1133                             pointer.previous();
1134                             pointer.remove();
1135                         }
1136 
1137                         if (bothTypes) {
1138                             // Factor out any common prefixies.
1139                             commonlength = this.diffCommonPrefix(textInsert.toString(), textDelete.toString());
1140                             if (commonlength != 0) {
1141                                 if (pointer.hasPrevious()) {
1142                                     thisDiff = pointer.previous();
1143                                     // Previous diff should have been an equality: thisDiff.getOperation() == Operation.EQUAL")
1144                                     thisDiff.setText(thisDiff.getText() + textInsert.substring(0, commonlength));
1145                                     pointer.next();
1146                                 } else {
1147                                     pointer.add(new Diff(Operation.EQUAL,
1148                                             textInsert.substring(0, commonlength)));
1149                                 }
1150                                 String substringIns = textInsert.substring(commonlength);
1151                                 textInsert.setLength(0);
1152                                 textInsert.append(substringIns);
1153                                 String substringDel = textDelete.substring(commonlength);
1154                                 textDelete.setLength(0);
1155                                 textDelete.append(substringDel);
1156                             }
1157                             // Factor out any common suffixies.
1158                             commonlength = this.diffCommonSuffix(textInsert.toString(), textDelete.toString());
1159                             if (commonlength != 0) {
1160                                 thisDiff = pointer.next();
1161                                 thisDiff.setText(textInsert.substring(textInsert.length() - commonlength) + thisDiff.getText());
1162                                 String substringIns = textInsert.substring(0, textInsert.length() - commonlength);
1163                                 textInsert.setLength(0);
1164                                 textInsert.append(substringIns);
1165                                 String substringDel = textDelete.substring(0, textDelete.length() - commonlength);
1166                                 textDelete.setLength(0);
1167                                 textDelete.append(substringDel);
1168                                 pointer.previous();
1169                             }
1170                         }
1171                         // Insert the merged records.
1172                         if (!textDelete.isEmpty()) {
1173                             pointer.add(new Diff(Operation.DELETE, textDelete.toString()));
1174                         }
1175                         if (!textInsert.isEmpty()) {
1176                             pointer.add(new Diff(Operation.INSERT, textInsert.toString()));
1177                         }
1178                         // Step forward to the equality.
1179                         thisDiff = pointer.hasNext() ? pointer.next() : null;
1180                     } else if (prevEqual != null) {
1181                         // Merge this equality with the previous one.
1182                         prevEqual.setText(prevEqual.getText() + thisDiff.getText());
1183                         pointer.remove();
1184                         thisDiff = pointer.previous();
1185                         pointer.next();  // Forward direction
1186                     }
1187                     countInsert = 0;
1188                     countDelete = 0;
1189                     textDelete.setLength(0);
1190                     textInsert.setLength(0);
1191                     prevEqual = thisDiff;
1192                     break;
1193             }
1194             thisDiff = pointer.hasNext() ? pointer.next() : null;
1195         }
1196         if (diffs.getLast().getText().isEmpty()) {
1197             diffs.removeLast();  // Remove the dummy entry at the end.
1198         }
1199 
1200         /*
1201          * Second pass: look for single edits surrounded on both sides by equalities
1202          * which can be shifted sideways to eliminate an equality.
1203          * e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
1204          */
1205         boolean changes = false;
1206         // Create a new iterator at the start.
1207         // (As opposed to walking the current one back.)
1208         pointer = diffs.listIterator();
1209         Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
1210         thisDiff = pointer.hasNext() ? pointer.next() : null;
1211         Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
1212 
1213         // Intentionally ignore the first and last element (don't need checking).
1214         while (nextDiff != null) {
1215             if (prevDiff.getOperation() == Operation.EQUAL &&
1216                     nextDiff.getOperation() == Operation.EQUAL) {
1217                 // This is a single edit surrounded by equalities.
1218                 if (thisDiff.getText().endsWith(prevDiff.getText())) {
1219                     // Shift the edit over the previous equality.
1220                     thisDiff.setText(prevDiff.getText()
1221                             + thisDiff.getText().substring(0, thisDiff.getText().length()
1222                             - prevDiff.getText().length()));
1223                     nextDiff.setText(prevDiff.getText() + nextDiff.getText());
1224                     pointer.previous(); // Walk past nextDiff.
1225                     pointer.previous(); // Walk past thisDiff.
1226                     pointer.previous(); // Walk past prevDiff.
1227                     pointer.remove(); // Delete prevDiff.
1228                     pointer.next(); // Walk past thisDiff.
1229                     thisDiff = pointer.next(); // Walk past nextDiff.
1230                     nextDiff = pointer.hasNext() ? pointer.next() : null;
1231                     changes = true;
1232                 } else if (thisDiff.getText().startsWith(nextDiff.getText())) {
1233                     // Shift the edit over the next equality.
1234                     prevDiff.setText(prevDiff.getText() + nextDiff.getText());
1235                     thisDiff.setText(thisDiff.getText().substring(nextDiff.getText().length())
1236                             + nextDiff.getText());
1237                     pointer.remove(); // Delete nextDiff.
1238                     nextDiff = pointer.hasNext() ? pointer.next() : null;
1239                     changes = true;
1240                 }
1241             }
1242             prevDiff = thisDiff;
1243             thisDiff = nextDiff;
1244             nextDiff = pointer.hasNext() ? pointer.next() : null;
1245         }
1246         // If shifts were made, the diff needs reordering and another shift sweep.
1247         if (changes) {
1248             this.diffCleanupMerge(diffs);
1249         }
1250     }
1251 }