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 com.jsql.util.LogLevelUtil;
22  import org.apache.commons.lang3.StringUtils;
23  import org.apache.logging.log4j.LogManager;
24  import org.apache.logging.log4j.Logger;
25  
26  import java.net.URLDecoder;
27  import java.net.URLEncoder;
28  import java.nio.charset.StandardCharsets;
29  import java.util.*;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  
33  /*
34   * Functions for diff, match and patch.
35   * Computes the difference between two texts to create a patch.
36   * Applies the patch onto another text, allowing for errors.
37   *
38   * @author fraser@google.com (Neil Fraser)
39   */
40  
41  /**
42   * Class containing the diff, match and patch methods.
43   * Also contains the behaviour settings.
44   */
45  public class DiffMatchPatch {
46      
47      /**
48       * Log4j logger sent to view.
49       */
50      private static final Logger LOGGER = LogManager.getRootLogger();
51  
52      // Defaults.
53      // Set these on your diff_match_patch instance to override the defaults.
54  
55      /**
56       * Number of seconds to map a diff before giving up (0 for infinity).
57       */
58      public static final float DIFF_TIMEOUT = 1.0f;
59      
60      /**
61       * Cost of an empty edit operation in terms of edit characters.
62       */
63      public static final short DIFF_EDIT_COST = 4;
64      
65      /**
66       * At what point is no match declared (0.0 = perfection, 1.0 = very loose).
67       */
68      public static final float MATCH_THRESHOLD = 0.5f;
69      
70      /**
71       * How far to search for a match (0 = exact location, 1000+ = broad match).
72       * A match this many characters away from the expected location will add
73       * 1.0 to the score (0.0 is a perfect match).
74       */
75      public static final int MATCH_DISTANCE = 1000;
76      
77      /**
78       * When deleting a large block of text (over ~64 characters), how close do
79       * the contents have to be to match the expected contents. (0.0 = perfection,
80       * 1.0 = very loose).  Note that Match_Threshold controls how closely the
81       * end points of a delete need to match.
82       */
83      public static final float PATCH_DELETE_THRESHOLD = 0.5f;
84      
85      /**
86       * Chunk size for context length.
87       */
88      public static final short PATCH_MARGIN = 4;
89  
90      /**
91       * The number of bits in an int.
92       */
93      private static final short MATCH_MAX_BITS = 32;
94  
95      // Define some regex patterns for matching boundaries.
96      private static final Pattern BLANK_LINE_END = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL);
97      private static final Pattern BLANK_LINE_START = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL);
98  
99      /**
100      * Internal class for returning results from diff_linesToChars().
101      * Other less paranoid languages just use a three-element array.
102      */
103     protected static class LinesToCharsResult {
104         protected final String chars1;
105         protected final String chars2;
106         protected final List<String> lineArray;
107 
108         protected LinesToCharsResult(String chars1, String chars2,
109                 List<String> lineArray) {
110             this.chars1 = chars1;
111             this.chars2 = chars2;
112             this.lineArray = lineArray;
113         }
114     }
115 
116     //  DIFF FUNCTIONS
117 
118     /**
119      * The data structure representing a diff is a Linked list of Diff objects:
120      * {Diff(Operation.DELETE, "Hello"), Diff(Operation.INSERT, "Goodbye"),
121      *  Diff(Operation.EQUAL, " world.")}
122      * which means: delete "Hello", add "Goodbye" and keep " world."
123      */
124     public enum Operation {
125         DELETE, INSERT, EQUAL
126     }
127 
128     /**
129      * Find the differences between two texts.
130      * Run a faster, slightly less optimal diff.
131      * This method allows the 'checklines' of diff_main() to be optional.
132      * Most of the time checklines is wanted, so default to true.
133      * @param text1 Old string to be diffed.
134      * @param text2 New string to be diffed.
135      * @return Linked List of Diff objects.
136      */
137     public List<Diff> diffMain(String text1, String text2) {
138         return this.diffMain(text1, text2, true);
139     }
140 
141     /**
142      * Find the differences between two texts.
143      * @param text1 Old string to be diffed.
144      * @param text2 New string to be diffed.
145      * @param checklines Speedup flag.  If false, then don't run a
146      *     line-level diff first to identify the changed areas.
147      *     If true, then run a faster slightly less optimal diff.
148      * @return Linked List of Diff objects.
149      */
150     public LinkedList<Diff> diffMain(String text1, String text2, boolean checklines) {
151 
152         // Set a deadline by which time the diff must be complete.
153         long deadline = System.currentTimeMillis() + (long) (DiffMatchPatch.DIFF_TIMEOUT * 1000);
154         return this.diffMain(text1, text2, checklines, deadline);
155     }
156 
157     /**
158      * Find the differences between two texts.  Simplifies the problem by
159      * stripping any common prefix or suffix off the texts before diffing.
160      * @param valueText1 Old string to be diffed.
161      * @param valueText2 New string to be diffed.
162      * @param checklines Speedup flag.  If false, then don't run a
163      *     line-level diff first to identify the changed areas.
164      *     If true, then run a faster slightly less optimal diff.
165      * @param deadline Time when the diff should be complete by.  Used
166      *     internally for recursive calls.  Users should set DiffTimeout instead.
167      * @return Linked List of Diff objects.
168      */
169     private LinkedList<Diff> diffMain(String valueText1, String valueText2, boolean checklines, long deadline) {
170 
171         String text1 = valueText1;
172         String text2 = valueText2;
173         
174         // Check for null inputs.
175         if (text1 == null || text2 == null) {
176             throw new IllegalArgumentException("Null inputs. (diff_main)");
177         }
178 
179         // Check for equality (speedup).
180         LinkedList<Diff> diffs;
181         if (text1.equals(text2)) {
182             diffs = new LinkedList<>();
183             if (!text1.isEmpty()) {
184                 diffs.add(new Diff(Operation.EQUAL, text1));
185             }
186             return diffs;
187         }
188 
189         // Trim off common prefix (speedup).
190         int commonlength = this.diffCommonPrefix(text1, text2);
191         String commonprefix = text1.substring(0, commonlength);
192         text1 = text1.substring(commonlength);
193         text2 = text2.substring(commonlength);
194 
195         // Trim off common suffix (speedup).
196         commonlength = this.diffCommonSuffix(text1, text2);
197         String commonsuffix = text1.substring(text1.length() - commonlength);
198         text1 = text1.substring(0, text1.length() - commonlength);
199         text2 = text2.substring(0, text2.length() - commonlength);
200 
201         // Compute the diff on the middle block.
202         diffs = this.diffCompute(text1, text2, checklines, deadline);
203 
204         // Restore the prefix and suffix.
205         if (!commonprefix.isEmpty()) {
206             diffs.addFirst(new Diff(Operation.EQUAL, commonprefix));
207         }
208         if (!commonsuffix.isEmpty()) {
209             diffs.addLast(new Diff(Operation.EQUAL, commonsuffix));
210         }
211 
212         this.diffCleanupMerge(diffs);
213         return diffs;
214     }
215 
216     /**
217      * Find the differences between two texts.  Assumes that the texts do not
218      * have any common prefix or suffix.
219      * @param text1 Old string to be diffed.
220      * @param text2 New string to be diffed.
221      * @param checklines Speedup flag.  If false, then don't run a
222      *     line-level diff first to identify the changed areas.
223      *     If true, then run a faster slightly less optimal diff.
224      * @param deadline Time when the diff should be complete by.
225      * @return Linked List of Diff objects.
226      */
227     private LinkedList<Diff> diffCompute(String text1, String text2, boolean checklines, long deadline) {
228 
229         LinkedList<Diff> diffs = new LinkedList<>();
230 
231         if (text1.isEmpty()) {
232             // Just add some text (speedup).
233             diffs.add(new Diff(Operation.INSERT, text2));
234             return diffs;
235         }
236 
237         if (text2.isEmpty()) {
238             // Just delete some text (speedup).
239             diffs.add(new Diff(Operation.DELETE, text1));
240             return diffs;
241         }
242 
243         {
244             // New scope to garbage collect longtext and shorttext.
245             String longtext = text1.length() > text2.length() ? text1 : text2;
246             String shorttext = text1.length() > text2.length() ? text2 : text1;
247             int i = longtext.indexOf(shorttext);
248             if (i != -1) {
249                 // Shorter text is inside the longer text (speedup).
250                 Operation op = (text1.length() > text2.length()) ?
251                         Operation.DELETE : Operation.INSERT;
252                 diffs.add(new Diff(op, longtext.substring(0, i)));
253                 diffs.add(new Diff(Operation.EQUAL, shorttext));
254                 diffs.add(new Diff(op, longtext.substring(i + shorttext.length())));
255                 return diffs;
256             }
257 
258             if (shorttext.length() == 1) {
259                 // Single character string.
260                 // After the previous speedup, the character can't be an equality.
261                 diffs.add(new Diff(Operation.DELETE, text1));
262                 diffs.add(new Diff(Operation.INSERT, text2));
263                 return diffs;
264             }
265         }
266 
267         // Check to see if the problem can be split in two.
268         String[] hm = this.diffHalfMatch(text1, text2);
269         if (hm != null) {
270             // A half-match was found, sort out the return data.
271             String text1A = hm[0];
272             String text1B = hm[1];
273             String text2A = hm[2];
274             String text2B = hm[3];
275             String midCommon = hm[4];
276             // Send both pairs off for separate processing.
277             LinkedList<Diff> diffsA = this.diffMain(text1A, text2A, checklines, deadline);
278             List<Diff> diffsB = this.diffMain(text1B, text2B, checklines, deadline);
279             // Merge the results.
280             diffs = diffsA;
281             diffs.add(new Diff(Operation.EQUAL, midCommon));
282             diffs.addAll(diffsB);
283             return diffs;
284         }
285 
286         if (checklines && text1.length() > 100 && text2.length() > 100) {
287             return this.diffLineMode(text1, text2, deadline);
288         }
289 
290         return this.diffBisect(text1, text2, deadline);
291     }
292 
293     /**
294      * Do a quick line-level diff on both strings, then rediff the parts for
295      * greater accuracy.
296      * This speedup can produce non-minimal diffs.
297      * @param valueText1 Old string to be diffed.
298      * @param valueText2 New string to be diffed.
299      * @param deadline Time when the diff should be complete by.
300      * @return Linked List of Diff objects.
301      */
302     private LinkedList<Diff> diffLineMode(String valueText1, String valueText2, long deadline) {
303 
304         // Scan the text on a line-by-line basis first.
305         LinesToCharsResult b = this.diffLinesToChars(valueText1, valueText2);
306         String text1 = b.chars1;
307         String text2 = b.chars2;
308         List<String> linearray = b.lineArray;
309 
310         LinkedList<Diff> diffs = this.diffMain(text1, text2, false, deadline);
311 
312         // Convert the diff back to original text.
313         this.diffCharsToLines(diffs, linearray);
314         // Eliminate freak matches (e.g. blank lines)
315         this.diffCleanupSemantic(diffs);
316 
317         // Rediff any replacement blocks, this time character-by-character.
318         // Add a dummy entry at the end.
319         diffs.add(new Diff(Operation.EQUAL, StringUtils.EMPTY));
320         int countDelete = 0;
321         int countInsert = 0;
322         StringBuilder textDelete = new StringBuilder();
323         StringBuilder textInsert = new StringBuilder();
324         ListIterator<Diff> pointer = diffs.listIterator();
325         Diff thisDiff = pointer.next();
326 
327         while (thisDiff != null) {
328             switch (thisDiff.getOperation()) {
329             case INSERT:
330                 countInsert++;
331                 textInsert.append(thisDiff.getText());
332                 break;
333             case DELETE:
334                 countDelete++;
335                 textDelete.append(thisDiff.getText());
336                 break;
337             case EQUAL:
338                 // Upon reaching an equality, check for prior redundancies.
339                 if (countDelete >= 1 && countInsert >= 1) {
340                     // Delete the offending records and add the merged ones.
341                     pointer.previous();
342                     for (int j = 0; j < countDelete + countInsert; j++) {
343                         pointer.previous();
344                         pointer.remove();
345                     }
346                     for (Diff newDiff : this.diffMain(textDelete.toString(), textInsert.toString(), false, deadline)) {
347                         pointer.add(newDiff);
348                     }
349                 }
350                 countInsert = 0;
351                 countDelete = 0;
352                 textDelete.setLength(0);
353                 textInsert.setLength(0);
354                 break;
355             }
356             thisDiff = pointer.hasNext() ? pointer.next() : null;
357         }
358         diffs.removeLast();  // Remove the dummy entry at the end.
359 
360         return diffs;
361     }
362 
363     /**
364      * Find the 'middle snake' of a diff, split the problem in two
365      * and return the recursively constructed diff.
366      * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
367      * @param text1 Old string to be diffed.
368      * @param text2 New string to be diffed.
369      * @param deadline Time at which to bail if not yet complete.
370      * @return LinkedList of Diff objects.
371      */
372     protected LinkedList<Diff> diffBisect(String text1, String text2, long deadline) {
373 
374         // Cache the text lengths to prevent multiple calls.
375         int text1Length = text1.length();
376         int text2Length = text2.length();
377         int maxD = (text1Length + text2Length + 1) / 2;
378         int vLength = 2 * maxD;
379         int[] v1 = new int[vLength];
380         int[] v2 = new int[vLength];
381         for (int x = 0; x < vLength; x++) {
382             v1[x] = -1;
383             v2[x] = -1;
384         }
385         v1[maxD + 1] = 0;
386         v2[maxD + 1] = 0;
387         int delta = text1Length - text2Length;
388         // If the total number of characters is odd, then the front path will
389         // collide with the reverse path.
390         boolean front = delta % 2 != 0;
391         // Offsets for start and end of k loop.
392         // Prevents mapping of space beyond the grid.
393         int k1start = 0;
394         int k1end = 0;
395         int k2start = 0;
396         int k2end = 0;
397 
398         for (int d = 0; d < maxD; d++) {
399             // Bail out if deadline is reached.
400             if (System.currentTimeMillis() > deadline) {
401                 break;
402             }
403 
404             // Walk the front path one step.
405             for (int k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
406                 int k1Offset = maxD + k1;
407                 int x1;
408                 if (k1 == -d || (k1 != d && v1[k1Offset - 1] < v1[k1Offset + 1])) {
409                     x1 = v1[k1Offset + 1];
410                 } else {
411                     x1 = v1[k1Offset - 1] + 1;
412                 }
413                 int y1 = x1 - k1;
414                 while (x1 < text1Length && y1 < text2Length
415                         && text1.charAt(x1) == text2.charAt(y1)) {
416                     x1++;
417                     y1++;
418                 }
419                 v1[k1Offset] = x1;
420                 if (x1 > text1Length) {
421                     // Ran off the right of the graph.
422                     k1end += 2;
423                 } else if (y1 > text2Length) {
424                     // Ran off the bottom of the graph.
425                     k1start += 2;
426                 } else if (front) {
427                     int k2Offset = maxD + delta - k1;
428                     if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] != -1) {
429                         // Mirror x2 onto top-left coordinate system.
430                         int x2 = text1Length - v2[k2Offset];
431                         if (x1 >= x2) {
432                             // Overlap detected.
433                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
434                         }
435                     }
436                 }
437             }
438 
439             // Walk the reverse path one step.
440             for (int k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
441                 int k2Offset = maxD + k2;
442                 int x2;
443                 if (k2 == -d || (k2 != d && v2[k2Offset - 1] < v2[k2Offset + 1])) {
444                     x2 = v2[k2Offset + 1];
445                 } else {
446                     x2 = v2[k2Offset - 1] + 1;
447                 }
448                 int y2 = x2 - k2;
449                 while (x2 < text1Length && y2 < text2Length
450                         && text1.charAt(text1Length - x2 - 1)
451                         == text2.charAt(text2Length - y2 - 1)) {
452                     x2++;
453                     y2++;
454                 }
455                 v2[k2Offset] = x2;
456                 if (x2 > text1Length) {
457                     // Ran off the left of the graph.
458                     k2end += 2;
459                 } else if (y2 > text2Length) {
460                     // Ran off the top of the graph.
461                     k2start += 2;
462                 } else if (!front) {
463                     int k1Offset = maxD + delta - k2;
464                     if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] != -1) {
465                         int x1 = v1[k1Offset];
466                         int y1 = maxD + x1 - k1Offset;
467                         // Mirror x2 onto top-left coordinate system.
468                         x2 = text1Length - x2;
469                         if (x1 >= x2) {
470                             // Overlap detected.
471                             return this.diffBisectSplit(text1, text2, x1, y1, deadline);
472                         }
473                     }
474                 }
475             }
476         }
477         // Diff took too long and hit the deadline or
478         // number of diffs equals number of characters, no commonality at all.
479         LinkedList<Diff> diffs = new LinkedList<>();
480         diffs.add(new Diff(Operation.DELETE, text1));
481         diffs.add(new Diff(Operation.INSERT, text2));
482         return diffs;
483     }
484 
485     /**
486      * Given the location of the 'middle snake', split the diff in two parts
487      * and recurse.
488      * @param text1 Old string to be diffed.
489      * @param text2 New string to be diffed.
490      * @param x Index of split point in text1.
491      * @param y Index of split point in text2.
492      * @param deadline Time at which to bail if not yet complete.
493      * @return LinkedList of Diff objects.
494      */
495     private LinkedList<Diff> diffBisectSplit(String text1, String text2, int x, int y, long deadline) {
496 
497         String text1a = text1.substring(0, x);
498         String text2a = text2.substring(0, y);
499         String text1b = text1.substring(x);
500         String text2b = text2.substring(y);
501 
502         // Compute both diffs serially.
503         LinkedList<Diff> diffs = this.diffMain(text1a, text2a, false, deadline);
504         List<Diff> diffsb = this.diffMain(text1b, text2b, false, deadline);
505 
506         diffs.addAll(diffsb);
507         return diffs;
508     }
509 
510     /**
511      * Split two texts into a list of strings.  Reduce the texts to a string of
512      * hashes where each Unicode character represents one line.
513      * @param text1 First string.
514      * @param text2 Second string.
515      * @return An object containing the encoded text1, the encoded text2 and
516      *     the List of unique strings.  The zeroth element of the List of
517      *     unique strings is intentionally blank.
518      */
519     protected LinesToCharsResult diffLinesToChars(String text1, String text2) {
520 
521         List<String> lineArray = new ArrayList<>();
522         Map<String, Integer> lineHash = new HashMap<>();
523         // e.g. linearray[4] == "Hello\n"
524         // e.g. linehash.get("Hello\n") == 4
525 
526         // "\x00" is a valid character, but various debuggers don't like it.
527         // So we'll insert a junk entry to avoid generating a null character.
528         lineArray.add(StringUtils.EMPTY);
529 
530         String chars1 = this.diffLinesToCharsMunge(text1, lineArray, lineHash);
531         String chars2 = this.diffLinesToCharsMunge(text2, lineArray, lineHash);
532         return new LinesToCharsResult(chars1, chars2, lineArray);
533     }
534 
535     /**
536      * Split a text into a list of strings.  Reduce the texts to a string of
537      * hashes where each Unicode character represents one line.
538      * @param text String to encode.
539      * @param lineArray List of unique strings.
540      * @param lineHash Map of strings to indices.
541      * @return Encoded string.
542      */
543     private String diffLinesToCharsMunge(String text, List<String> lineArray,
544             Map<String, Integer> lineHash) {
545 
546         int lineStart = 0;
547         int lineEnd = -1;
548         String line;
549         StringBuilder chars = new StringBuilder();
550         // Walk the text, pulling out a substring for each line.
551         // text.split('\n') would would temporarily double our memory footprint.
552         // Modifying text would create many large strings to garbage collect.
553         while (lineEnd < text.length() - 1) {
554             lineEnd = text.indexOf('\n', lineStart);
555             if (lineEnd == -1) {
556                 lineEnd = text.length() - 1;
557             }
558             line = text.substring(lineStart, lineEnd + 1);
559             lineStart = lineEnd + 1;
560 
561             if (lineHash.containsKey(line)) {
562                 chars.append((char) (int) lineHash.get(line));
563             } else {
564                 lineArray.add(line);
565                 lineHash.put(line, lineArray.size() - 1);
566                 chars.append((char) (lineArray.size() - 1));
567             }
568         }
569         return chars.toString();
570     }
571 
572     /**
573      * Rehydrate the text in a diff from a string of line hashes to real lines of
574      * text.
575      * @param diffs LinkedList of Diff objects.
576      * @param lineArray List of unique strings.
577      */
578     protected void diffCharsToLines(List<Diff> diffs, List<String> lineArray) {
579 
580         StringBuilder text;
581         for (Diff diff : diffs) {
582             text = new StringBuilder();
583             for (int y = 0; y < diff.getText().length(); y++) {
584                 text.append(lineArray.get(diff.getText().charAt(y)));
585             }
586             diff.setText(text.toString());
587         }
588     }
589 
590     /**
591      * Determine the common prefix of two strings
592      * @param text1 First string.
593      * @param text2 Second string.
594      * @return The number of characters common to the start of each string.
595      */
596     public int diffCommonPrefix(String text1, String text2) {
597 
598         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
599         int n = Math.min(text1.length(), text2.length());
600         for (int i = 0; i < n; i++) {
601             if (text1.charAt(i) != text2.charAt(i)) {
602                 return i;
603             }
604         }
605         return n;
606     }
607 
608     /**
609      * Determine the common suffix of two strings
610      * @param text1 First string.
611      * @param text2 Second string.
612      * @return The number of characters common to the end of each string.
613      */
614     public int diffCommonSuffix(String text1, String text2) {
615 
616         // Performance analysis: http://neil.fraser.name/news/2007/10/09/
617         int text1Length = text1.length();
618         int text2Length = text2.length();
619         int n = Math.min(text1Length, text2Length);
620         for (int i = 1; i <= n; i++) {
621             if (text1.charAt(text1Length - i) != text2.charAt(text2Length - i)) {
622                 return i - 1;
623             }
624         }
625         return n;
626     }
627 
628     /**
629      * Determine if the suffix of one string is the prefix of another.
630      * @param valueText1 First string.
631      * @param valueText2 Second string.
632      * @return The number of characters common to the end of the first
633      *     string and the start of the second string.
634      */
635     protected int diffCommonOverlap(String valueText1, String valueText2) {
636 
637         String text1 = valueText1;
638         String text2 = valueText2;
639         
640         // Cache the text lengths to prevent multiple calls.
641         int text1Length = text1.length();
642         int text2Length = text2.length();
643         // Eliminate the null case.
644         if (text1Length == 0 || text2Length == 0) {
645             return 0;
646         }
647         // Truncate the longer string.
648         if (text1Length > text2Length) {
649             text1 = text1.substring(text1Length - text2Length);
650         } else if (text1Length < text2Length) {
651             text2 = text2.substring(0, text1Length);
652         }
653         int textLength = Math.min(text1Length, text2Length);
654         // Quick check for the worst case.
655         if (text1.equals(text2)) {
656             return textLength;
657         }
658 
659         // Start by looking for a single character match
660         // and increase length until no match is found.
661         // Performance analysis: http://neil.fraser.name/news/2010/11/04/
662         int best = 0;
663         int length = 1;
664         while (true) {
665             String pattern = text1.substring(textLength - length);
666             int found = text2.indexOf(pattern);
667             if (found == -1) {
668                 return best;
669             }
670             length += found;
671             if (found == 0 || text1.substring(textLength - length).equals(
672                     text2.substring(0, length))) {
673                 best = length;
674                 length++;
675             }
676         }
677     }
678 
679     /**
680      * Do the two texts share a substring which is at least half the length of
681      * the longer text?
682      * This speedup can produce non-minimal diffs.
683      * @param text1 First string.
684      * @param text2 Second string.
685      * @return Five element String array, containing the prefix of text1, the
686      *     suffix of text1, the prefix of text2, the suffix of text2 and the
687      *     common middle.  Or null if there was no match.
688      */
689     protected String[] diffHalfMatch(String text1, String text2) {
690 
691         String longtext = text1.length() > text2.length() ? text1 : text2;
692         String shorttext = text1.length() > text2.length() ? text2 : text1;
693         if (longtext.length() < 4 || shorttext.length() * 2 < longtext.length()) {
694             return null;  // Pointless.
695         }
696 
697         // First check if the second quarter is the seed for a half-match.
698         String[] hm1 = this.diffHalfMatchI(longtext, shorttext, (longtext.length() + 3) / 4);
699         // Check again based on the third quarter.
700         String[] hm2 = this.diffHalfMatchI(longtext, shorttext, (longtext.length() + 1) / 2);
701         String[] hm;
702         if (hm1 == null && hm2 == null) {
703             return null;
704         } else if (hm2 == null) {
705             hm = hm1;
706         } else if (hm1 == null) {
707             hm = hm2;
708         } else {
709             // Both matched.  Select the longest.
710             hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
711         }
712 
713         // A half-match was found, sort out the return data.
714         if (text1.length() > text2.length()) {
715             return hm;
716         } else {
717             return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]};
718         }
719     }
720 
721     /**
722      * Does a substring of shorttext exist within longtext such that the
723      * substring is at least half the length of longtext?
724      * @param longtext Longer string.
725      * @param shorttext Shorter string.
726      * @param i Start index of quarter length substring within longtext.
727      * @return Five element String array, containing the prefix of longtext, the
728      *     suffix of longtext, the prefix of shorttext, the suffix of shorttext
729      *     and the common middle.  Or null if there was no match.
730      */
731     private String[] diffHalfMatchI(String longtext, String shorttext, int i) {
732 
733         // Start with a 1/4 length substring at position i as a seed.
734         String seed = longtext.substring(i, i + longtext.length() / 4);
735         int j = -1;
736         String bestCommon = StringUtils.EMPTY;
737         String bestLongtextA = StringUtils.EMPTY;
738         String bestLongtextB = StringUtils.EMPTY;
739         String bestShorttextA = StringUtils.EMPTY;
740         String bestShorttextB = StringUtils.EMPTY;
741         while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
742             int prefixLength = this.diffCommonPrefix(longtext.substring(i),
743                     shorttext.substring(j));
744             int suffixLength = this.diffCommonSuffix(longtext.substring(0, i),
745                     shorttext.substring(0, j));
746             if (bestCommon.length() < suffixLength + prefixLength) {
747                 bestCommon = shorttext.substring(j - suffixLength, j)
748                         + shorttext.substring(j, j + prefixLength);
749                 bestLongtextA = longtext.substring(0, i - suffixLength);
750                 bestLongtextB = longtext.substring(i + prefixLength);
751                 bestShorttextA = shorttext.substring(0, j - suffixLength);
752                 bestShorttextB = shorttext.substring(j + prefixLength);
753             }
754         }
755         if (bestCommon.length() * 2 >= longtext.length()) {
756             return new String[]{bestLongtextA, bestLongtextB,
757                     bestShorttextA, bestShorttextB, bestCommon};
758         } else {
759             return null;
760         }
761     }
762 
763     /**
764      * Reduce the number of edits by eliminating semantically trivial equalities.
765      * @param diffs LinkedList of Diff objects.
766      */
767     public void diffCleanupSemantic(LinkedList<Diff> diffs) {
768 
769         if (diffs.isEmpty()) {
770             return;
771         }
772         boolean changes = false;
773         // Synchronized Stack to avoid Exception
774         Stack<Diff> equalities = new Stack<>();  // Stack of qualities.
775         String lastequality = null; // Always equal to equalities.lastElement().text
776         ListIterator<Diff> pointer = diffs.listIterator();
777         // Number of characters that changed prior to the equality.
778         int lengthInsertions1 = 0;
779         int lengthDeletions1 = 0;
780         // Number of characters that changed after the equality.
781         int lengthInsertions2 = 0;
782         int lengthDeletions2 = 0;
783         Diff thisDiff = pointer.next();
784 
785         while (thisDiff != null) {
786             if (thisDiff.getOperation() == Operation.EQUAL) {
787                 // Equality found.
788                 equalities.push(thisDiff);
789                 lengthInsertions1 = lengthInsertions2;
790                 lengthDeletions1 = lengthDeletions2;
791                 lengthInsertions2 = 0;
792                 lengthDeletions2 = 0;
793                 lastequality = thisDiff.getText();
794             } else {
795                 // An insertion or deletion.
796                 if (thisDiff.getOperation() == Operation.INSERT) {
797                     lengthInsertions2 += thisDiff.getText().length();
798                 } else {
799                     lengthDeletions2 += thisDiff.getText().length();
800                 }
801                 // Eliminate an equality that is smaller or equal to the edits on both
802                 // sides of it.
803                 if (
804                     lastequality != null
805                     && lastequality.length() <= Math.max(lengthInsertions1, lengthDeletions1)
806                     && lastequality.length() <= Math.max(lengthInsertions2, lengthDeletions2)
807                 ) {
808                     // Walk back to offending equality.
809                     while (thisDiff != equalities.lastElement()) {
810                         thisDiff = pointer.previous();
811                     }
812                     pointer.next();
813 
814                     // Replace equality with a delete.
815                     pointer.set(new Diff(Operation.DELETE, lastequality));
816                     // Insert a corresponding an insert.
817                     pointer.add(new Diff(Operation.INSERT, lastequality));
818 
819                     equalities.pop();  // Throw away the equality we just deleted.
820                     if (!equalities.empty()) {
821                         // Throw away the previous equality (it needs to be reevaluated).
822                         equalities.pop();
823                     }
824                     if (equalities.empty()) {
825                         // There are no previous equalities, walk back to the start.
826                         while (pointer.hasPrevious()) {
827                             pointer.previous();
828                         }
829                     } else {
830                         // There is a safe equality we can fall back to.
831                         thisDiff = equalities.lastElement();
832                         while (thisDiff != pointer.previous()) {
833                             // Intentionally empty loop.
834                         }
835                     }
836 
837                     lengthInsertions1 = 0;  // Reset the counters.
838                     lengthInsertions2 = 0;
839                     lengthDeletions1 = 0;
840                     lengthDeletions2 = 0;
841                     lastequality = null;
842                     changes = true;
843                 }
844             }
845             thisDiff = pointer.hasNext() ? pointer.next() : null;
846         }
847 
848         // Normalize the diff.
849         if (changes) {
850             this.diffCleanupMerge(diffs);
851         }
852         this.diffCleanupSemanticLossless(diffs);
853 
854         // Find any overlaps between deletions and insertions.
855         // e.g: <del>abcxxx</del><ins>xxxdef</ins>
856         //   -> <del>abc</del>xxx<ins>def</ins>
857         // e.g: <del>xxxabc</del><ins>defxxx</ins>
858         //   -> <ins>def</ins>xxx<del>abc</del>
859         // Only extract an overlap if it is as big as the edit ahead or behind it.
860         pointer = diffs.listIterator();
861         Diff prevDiff = null;
862         thisDiff = null;
863         if (pointer.hasNext()) {
864             prevDiff = pointer.next();
865             if (pointer.hasNext()) {
866                 thisDiff = pointer.next();
867             }
868         }
869 
870         while (thisDiff != null) {
871             if (prevDiff.getOperation() == Operation.DELETE &&
872                     thisDiff.getOperation() == Operation.INSERT) {
873                 String deletion = prevDiff.getText();
874                 String insertion = thisDiff.getText();
875                 int overlapLength1 = this.diffCommonOverlap(deletion, insertion);
876                 int overlapLength2 = this.diffCommonOverlap(insertion, deletion);
877                 if (overlapLength1 >= overlapLength2) {
878                     if (overlapLength1 >= deletion.length() / 2.0 ||
879                             overlapLength1 >= insertion.length() / 2.0) {
880                         // Overlap found.  Insert an equality and trim the surrounding edits.
881                         pointer.previous();
882                         pointer.add(new Diff(Operation.EQUAL,
883                                 insertion.substring(0, overlapLength1)));
884                         prevDiff.setText(deletion.substring(0, deletion.length() - overlapLength1));
885                         thisDiff.setText(insertion.substring(overlapLength1));
886                         // pointer.add inserts the element before the cursor, so there is
887                         // no need to step past the new element.
888                     }
889                 } else {
890                     if (overlapLength2 >= deletion.length() / 2.0 ||
891                             overlapLength2 >= insertion.length() / 2.0) {
892                         // Reverse overlap found.
893                         // Insert an equality and swap and trim the surrounding edits.
894                         pointer.previous();
895                         pointer.add(new Diff(Operation.EQUAL,
896                                 deletion.substring(0, overlapLength2)));
897                         prevDiff.setOperation(Operation.INSERT);
898                         prevDiff.setText(insertion.substring(0, insertion.length() - overlapLength2));
899                         thisDiff.setOperation(Operation.DELETE);
900                         thisDiff.setText(deletion.substring(overlapLength2));
901                         // pointer.add inserts the element before the cursor, so there is
902                         // no need to step past the new element.
903                     }
904                 }
905                 thisDiff = pointer.hasNext() ? pointer.next() : null;
906             }
907             prevDiff = thisDiff;
908             thisDiff = pointer.hasNext() ? pointer.next() : null;
909         }
910     }
911 
912     /**
913      * Look for single edits surrounded on both sides by equalities
914      * which can be shifted sideways to align the edit to a word boundary.
915      * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
916      * @param diffs LinkedList of Diff objects.
917      */
918     public void diffCleanupSemanticLossless(List<Diff> diffs) {
919 
920         StringBuilder equality1 = new StringBuilder();
921         String edit;
922         StringBuilder equality2 = new StringBuilder();
923         String commonString;
924         int commonOffset;
925         int score;
926         int bestScore;
927         String bestEquality1;
928         String bestEdit;
929         String bestEquality2;
930         // Create a new iterator at the start.
931         ListIterator<Diff> pointer = diffs.listIterator();
932         Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
933         Diff thisDiff = pointer.hasNext() ? pointer.next() : null;
934         Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
935 
936         // Intentionally ignore the first and last element (don't need checking).
937         while (nextDiff != null) {
938             if (prevDiff.getOperation() == Operation.EQUAL &&
939                     nextDiff.getOperation() == Operation.EQUAL) {
940                 // This is a single edit surrounded by equalities.
941                 equality1.setLength(0);
942                 equality1.append(prevDiff.getText());
943                 edit = thisDiff.getText();
944                 equality2.setLength(0);
945                 equality2.append(nextDiff.getText());
946 
947                 // First, shift the edit as far left as possible.
948                 commonOffset = this.diffCommonSuffix(equality1.toString(), edit);
949                 if (commonOffset != 0) {
950                     commonString = edit.substring(edit.length() - commonOffset);
951                     String substring = equality1.substring(0, equality1.length() - commonOffset);
952                     equality1.setLength(0);
953                     equality1.append(substring);
954                     edit = commonString + edit.substring(0, edit.length() - commonOffset);
955                     equality2.insert(0, commonString);
956                 }
957 
958                 // Second, step character by character right, looking for the best fit.
959                 bestEquality1 = equality1.toString();
960                 bestEdit = edit;
961                 bestEquality2 = equality2.toString();
962                 bestScore = this.diffCleanupSemanticScore(equality1.toString(), edit)
963                         + this.diffCleanupSemanticScore(edit, equality2.toString());
964                 while (!edit.isEmpty() && equality2.length() != 0
965                         && edit.charAt(0) == equality2.charAt(0)) {
966                     equality1.append(edit.charAt(0));
967                     edit = edit.substring(1) + equality2.charAt(0);
968                     String substring = equality2.substring(1);
969                     equality2.setLength(0);
970                     equality2.append(substring);
971                     score = this.diffCleanupSemanticScore(equality1.toString(), edit)
972                             + this.diffCleanupSemanticScore(edit, equality2.toString());
973                     // The >= encourages trailing rather than leading whitespace on edits.
974                     if (score >= bestScore) {
975                         bestScore = score;
976                         bestEquality1 = equality1.toString();
977                         bestEdit = edit;
978                         bestEquality2 = equality2.toString();
979                     }
980                 }
981 
982                 if (!prevDiff.getText().equals(bestEquality1)) {
983                     // We have an improvement, save it back to the diff.
984                     if (!bestEquality1.isEmpty()) {
985                         prevDiff.setText(bestEquality1);
986                     } else {
987                         pointer.previous(); // Walk past nextDiff.
988                         pointer.previous(); // Walk past thisDiff.
989                         pointer.previous(); // Walk past prevDiff.
990                         pointer.remove(); // Delete prevDiff.
991                         pointer.next(); // Walk past thisDiff.
992                         pointer.next(); // Walk past nextDiff.
993                     }
994                     thisDiff.setText(bestEdit);
995                     if (!bestEquality2.isEmpty()) {
996                         nextDiff.setText(bestEquality2);
997                     } else {
998                         pointer.remove(); // Delete nextDiff.
999                         nextDiff = thisDiff;
1000                         thisDiff = prevDiff;
1001                     }
1002                 }
1003             }
1004             prevDiff = thisDiff;
1005             thisDiff = nextDiff;
1006             nextDiff = pointer.hasNext() ? pointer.next() : null;
1007         }
1008     }
1009 
1010     /**
1011      * Given two strings, compute a score representing whether the internal
1012      * boundary falls on logical boundaries.
1013      * Scores range from 6 (best) to 0 (worst).
1014      * @param one First string.
1015      * @param two Second string.
1016      * @return The score.
1017      */
1018     private int diffCleanupSemanticScore(String one, String two) {
1019 
1020         if (one.isEmpty() || two.isEmpty()) {
1021             // Edges are the best.
1022             return 6;
1023         }
1024 
1025         // Each port of this function behaves slightly differently due to
1026         // subtle differences in each language's definition of things like
1027         // 'whitespace'.  Since this function's purpose is largely cosmetic,
1028         // the choice has been made to use each language's native features
1029         // rather than force total conformity.
1030         char char1 = one.charAt(one.length() - 1);
1031         char char2 = two.charAt(0);
1032         boolean nonAlphaNumeric1 = !Character.isLetterOrDigit(char1);
1033         boolean nonAlphaNumeric2 = !Character.isLetterOrDigit(char2);
1034         boolean whitespace1 = nonAlphaNumeric1 && Character.isWhitespace(char1);
1035         boolean whitespace2 = nonAlphaNumeric2 && Character.isWhitespace(char2);
1036         boolean lineBreak1 = whitespace1
1037                 && Character.getType(char1) == Character.CONTROL;
1038         boolean lineBreak2 = whitespace2
1039                 && Character.getType(char2) == Character.CONTROL;
1040         boolean blankLine1 = lineBreak1 && DiffMatchPatch.BLANK_LINE_END.matcher(one).find();
1041         boolean blankLine2 = lineBreak2 && DiffMatchPatch.BLANK_LINE_START.matcher(two).find();
1042 
1043         if (blankLine1 || blankLine2) {
1044             // Five points for blank lines.
1045             return 5;
1046         } else if (lineBreak1 || lineBreak2) {
1047             // Four points for line breaks.
1048             return 4;
1049         } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
1050             // Three points for end of sentences.
1051             return 3;
1052         } else if (whitespace1 || whitespace2) {
1053             // Two points for whitespace.
1054             return 2;
1055         } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
1056             // One point for non-alphanumeric.
1057             return 1;
1058         }
1059         return 0;
1060     }
1061 
1062     /**
1063      * Reduce the number of edits by eliminating operationally trivial equalities.
1064      * @param diffs LinkedList of Diff objects.
1065      */
1066     public void diffCleanupEfficiency(LinkedList<Diff> diffs) {
1067 
1068         if (diffs.isEmpty()) {
1069             return;
1070         }
1071         boolean changes = false;
1072         // Synchronized Stack to avoid Exception
1073         Stack<Diff> equalities = new Stack<>();  // Stack of equalities.
1074         String lastequality = null; // Always equal to equalities.lastElement().text
1075         ListIterator<Diff> pointer = diffs.listIterator();
1076         // Is there an insertion operation before the last equality.
1077         boolean preIns = false;
1078         // Is there a deletion operation before the last equality.
1079         boolean preDel = false;
1080         // Is there an insertion operation after the last equality.
1081         boolean postIns = false;
1082         // Is there a deletion operation after the last equality.
1083         boolean postDel = false;
1084         Diff thisDiff = pointer.next();
1085         Diff safeDiff = thisDiff;  // The last Diff that is known to be unsplitable.
1086         while (thisDiff != null) {
1087 
1088             if (thisDiff.getOperation() == Operation.EQUAL) {
1089 
1090                 // Equality found.
1091                 if (thisDiff.getText().length() < DiffMatchPatch.DIFF_EDIT_COST && (postIns || postDel)) {
1092                     // Candidate found.
1093                     equalities.push(thisDiff);
1094                     preIns = postIns;
1095                     preDel = postDel;
1096                     lastequality = thisDiff.getText();
1097                 } else {
1098                     // Not a candidate, and can never become one.
1099                     equalities.clear();
1100                     lastequality = null;
1101                     safeDiff = thisDiff;
1102                 }
1103                 postIns = postDel = false;
1104             } else {
1105                 // An insertion or deletion.
1106                 if (thisDiff.getOperation() == Operation.DELETE) {
1107                     postDel = true;
1108                 } else {
1109                     postIns = true;
1110                 }
1111 
1112                 /*
1113                  * Five types to be split:
1114                  * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
1115                  * <ins>A</ins>X<ins>C</ins><del>D</del>
1116                  * <ins>A</ins><del>B</del>X<ins>C</ins>
1117                  * <ins>A</del>X<ins>C</ins><del>D</del>
1118                  * <ins>A</ins><del>B</del>X<del>C</del>
1119                  */
1120                 if (
1121                     lastequality != null
1122                     && (
1123                         (preIns && preDel && postIns && postDel)
1124                         || (
1125                             (lastequality.length() < DiffMatchPatch.DIFF_EDIT_COST / 2)
1126                             && ((preIns ? 1 : 0) + (preDel ? 1 : 0) + (postIns ? 1 : 0) + (postDel ? 1 : 0)) == 3
1127                         )
1128                     )
1129                 ) {
1130                     // Walk back to offending equality.
1131                     while (thisDiff != equalities.lastElement()) {
1132                         thisDiff = pointer.previous();
1133                     }
1134                     pointer.next();
1135 
1136                     // Replace equality with a delete.
1137                     pointer.set(new Diff(Operation.DELETE, lastequality));
1138                     // Insert a corresponding an insert.
1139                     thisDiff = new Diff(Operation.INSERT, lastequality);
1140                     pointer.add(thisDiff);
1141 
1142                     equalities.pop();  // Throw away the equality we just deleted.
1143                     lastequality = null;
1144                     if (preIns && preDel) {
1145                         // No changes made which could affect previous entry, keep going.
1146                         postIns = postDel = true;
1147                         equalities.clear();
1148                         safeDiff = thisDiff;
1149                     } else {
1150                         if (!equalities.empty()) {
1151                             // Throw away the previous equality (it needs to be reevaluated).
1152                             equalities.pop();
1153                         }
1154                         if (equalities.empty()) {
1155                             // There are no previous questionable equalities,
1156                             // walk back to the last known safe diff.
1157                             thisDiff = safeDiff;
1158                         } else {
1159                             // There is an equality we can fall back to.
1160                             thisDiff = equalities.lastElement();
1161                         }
1162                         while (thisDiff != pointer.previous()) {
1163                             // Intentionally empty loop.
1164                         }
1165                         postIns = postDel = false;
1166                     }
1167 
1168                     changes = true;
1169                 }
1170             }
1171             thisDiff = pointer.hasNext() ? pointer.next() : null;
1172         }
1173 
1174         if (changes) {
1175             this.diffCleanupMerge(diffs);
1176         }
1177     }
1178 
1179     /**
1180      * Reorder and merge like edit sections.  Merge equalities.
1181      * Any edit section can move as long as it doesn't cross an equality.
1182      * @param diffs LinkedList of Diff objects.
1183      */
1184     public void diffCleanupMerge(LinkedList<Diff> diffs) {
1185 
1186         diffs.add(new Diff(Operation.EQUAL, StringUtils.EMPTY));  // Add a dummy entry at the end.
1187         ListIterator<Diff> pointer = diffs.listIterator();
1188         int countDelete = 0;
1189         int countInsert = 0;
1190         StringBuilder textDelete = new StringBuilder();
1191         StringBuilder textInsert = new StringBuilder();
1192         Diff thisDiff = pointer.next();
1193         Diff prevEqual = null;
1194         int commonlength;
1195         while (thisDiff != null) {
1196             switch (thisDiff.getOperation()) {
1197             case INSERT:
1198                 countInsert++;
1199                 textInsert.append(thisDiff.getText());
1200                 prevEqual = null;
1201                 break;
1202             case DELETE:
1203                 countDelete++;
1204                 textDelete.append(thisDiff.getText());
1205                 prevEqual = null;
1206                 break;
1207             case EQUAL:
1208                 if (countDelete + countInsert > 1) {
1209 
1210                     boolean bothTypes = countDelete != 0 && countInsert != 0;
1211                     // Delete the offending records.
1212                     pointer.previous();  // Reverse direction.
1213                     while (countDelete-- > 0) {
1214                         pointer.previous();
1215                         pointer.remove();
1216                     }
1217                     while (countInsert-- > 0) {
1218                         pointer.previous();
1219                         pointer.remove();
1220                     }
1221 
1222                     if (bothTypes) {
1223                         // Factor out any common prefixies.
1224                         commonlength = this.diffCommonPrefix(textInsert.toString(), textDelete.toString());
1225                         if (commonlength != 0) {
1226                             if (pointer.hasPrevious()) {
1227                                 thisDiff = pointer.previous();
1228                                 // Previous diff should have been an equality: thisDiff.getOperation() == Operation.EQUAL")
1229                                 thisDiff.setText(thisDiff.getText() + textInsert.substring(0, commonlength));
1230                                 pointer.next();
1231                             } else {
1232                                 pointer.add(new Diff(Operation.EQUAL,
1233                                         textInsert.substring(0, commonlength)));
1234                             }
1235                             String substringIns = textInsert.substring(commonlength);
1236                             textInsert.setLength(0);
1237                             textInsert.append(substringIns);
1238                             String substringDel = textDelete.substring(commonlength);
1239                             textDelete.setLength(0);
1240                             textDelete.append(substringDel);
1241                         }
1242                         // Factor out any common suffixies.
1243                         commonlength = this.diffCommonSuffix(textInsert.toString(), textDelete.toString());
1244                         if (commonlength != 0) {
1245                             thisDiff = pointer.next();
1246                             thisDiff.setText(textInsert.substring(textInsert.length() - commonlength) + thisDiff.getText());
1247                             String substringIns = textInsert.substring(0, textInsert.length() - commonlength);
1248                             textInsert.setLength(0);
1249                             textInsert.append(substringIns);
1250                             String substringDel = textDelete.substring(0, textDelete.length() - commonlength);
1251                             textDelete.setLength(0);
1252                             textDelete.append(substringDel);
1253                             pointer.previous();
1254                         }
1255                     }
1256                     // Insert the merged records.
1257                     if (textDelete.length() != 0) {
1258                         pointer.add(new Diff(Operation.DELETE, textDelete.toString()));
1259                     }
1260                     if (textInsert.length() != 0) {
1261                         pointer.add(new Diff(Operation.INSERT, textInsert.toString()));
1262                     }
1263                     // Step forward to the equality.
1264                     thisDiff = pointer.hasNext() ? pointer.next() : null;
1265                 } else if (prevEqual != null) {
1266                     // Merge this equality with the previous one.
1267                     prevEqual.setText(prevEqual.getText() + thisDiff.getText());
1268                     pointer.remove();
1269                     thisDiff = pointer.previous();
1270                     pointer.next();  // Forward direction
1271                 }
1272                 countInsert = 0;
1273                 countDelete = 0;
1274                 textDelete.setLength(0);
1275                 textInsert.setLength(0);
1276                 prevEqual = thisDiff;
1277                 break;
1278             }
1279             thisDiff = pointer.hasNext() ? pointer.next() : null;
1280         }
1281         if (diffs.getLast().getText().isEmpty()) {
1282             diffs.removeLast();  // Remove the dummy entry at the end.
1283         }
1284 
1285         /*
1286          * Second pass: look for single edits surrounded on both sides by equalities
1287          * which can be shifted sideways to eliminate an equality.
1288          * e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
1289          */
1290         boolean changes = false;
1291         // Create a new iterator at the start.
1292         // (As opposed to walking the current one back.)
1293         pointer = diffs.listIterator();
1294         Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
1295         thisDiff = pointer.hasNext() ? pointer.next() : null;
1296         Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
1297 
1298         // Intentionally ignore the first and last element (don't need checking).
1299         while (nextDiff != null) {
1300             if (prevDiff.getOperation() == Operation.EQUAL &&
1301                     nextDiff.getOperation() == Operation.EQUAL) {
1302                 // This is a single edit surrounded by equalities.
1303                 if (thisDiff.getText().endsWith(prevDiff.getText())) {
1304                     // Shift the edit over the previous equality.
1305                     thisDiff.setText(prevDiff.getText()
1306                             + thisDiff.getText().substring(0, thisDiff.getText().length()
1307                                     - prevDiff.getText().length()));
1308                     nextDiff.setText(prevDiff.getText() + nextDiff.getText());
1309                     pointer.previous(); // Walk past nextDiff.
1310                     pointer.previous(); // Walk past thisDiff.
1311                     pointer.previous(); // Walk past prevDiff.
1312                     pointer.remove(); // Delete prevDiff.
1313                     pointer.next(); // Walk past thisDiff.
1314                     thisDiff = pointer.next(); // Walk past nextDiff.
1315                     nextDiff = pointer.hasNext() ? pointer.next() : null;
1316                     changes = true;
1317                 } else if (thisDiff.getText().startsWith(nextDiff.getText())) {
1318                     // Shift the edit over the next equality.
1319                     prevDiff.setText(prevDiff.getText() + nextDiff.getText());
1320                     thisDiff.setText(thisDiff.getText().substring(nextDiff.getText().length())
1321                             + nextDiff.getText());
1322                     pointer.remove(); // Delete nextDiff.
1323                     nextDiff = pointer.hasNext() ? pointer.next() : null;
1324                     changes = true;
1325                 }
1326             }
1327             prevDiff = thisDiff;
1328             thisDiff = nextDiff;
1329             nextDiff = pointer.hasNext() ? pointer.next() : null;
1330         }
1331         // If shifts were made, the diff needs reordering and another shift sweep.
1332         if (changes) {
1333             this.diffCleanupMerge(diffs);
1334         }
1335     }
1336 
1337     /**
1338      * loc is a location in text1, compute and return the equivalent location in
1339      * text2.
1340      * e.g. "The cat" vs "The big cat", 1->1, 5->8
1341      * @param diffs List of Diff objects.
1342      * @param loc Location within text1.
1343      * @return Location within text2.
1344      */
1345     public int diffXIndex(List<Diff> diffs, int loc) {
1346 
1347         int chars1 = 0;
1348         int chars2 = 0;
1349         int lastChars1 = 0;
1350         int lastChars2 = 0;
1351         Diff lastDiff = null;
1352         for (Diff aDiff : diffs) {
1353             if (aDiff.getOperation() != Operation.INSERT) {
1354                 // Equality or deletion.
1355                 chars1 += aDiff.getText().length();
1356             }
1357             if (aDiff.getOperation() != Operation.DELETE) {
1358                 // Equality or insertion.
1359                 chars2 += aDiff.getText().length();
1360             }
1361             if (chars1 > loc) {
1362                 // Overshot the location.
1363                 lastDiff = aDiff;
1364                 break;
1365             }
1366             lastChars1 = chars1;
1367             lastChars2 = chars2;
1368         }
1369         if (lastDiff != null && lastDiff.getOperation() == Operation.DELETE) {
1370             // The location was deleted.
1371             return lastChars2;
1372         }
1373         // Add the remaining character length.
1374         return lastChars2 + (loc - lastChars1);
1375     }
1376 
1377     /**
1378      * Convert a Diff list into a pretty HTML report.
1379      * @param diffs List of Diff objects.
1380      * @return HTML representation.
1381      */
1382     public String diffPrettyHtml(List<Diff> diffs) {
1383 
1384         StringBuilder html = new StringBuilder();
1385         for (Diff aDiff : diffs) {
1386             String text = aDiff.getText().replace("&", "&amp;").replace("<", "&lt;")
1387                     .replace(">", "&gt;").replace("\n", "&para;<br>");
1388             switch (aDiff.getOperation()) {
1389             case INSERT:
1390                 html.append("<ins style=\"background:#e6ffe6;\">").append(text)
1391                 .append("</ins>");
1392                 break;
1393             case DELETE:
1394                 html.append("<del style=\"background:#ffe6e6;\">").append(text)
1395                 .append("</del>");
1396                 break;
1397             case EQUAL:
1398                 html.append("<span>").append(text).append("</span>");
1399                 break;
1400             }
1401         }
1402         return html.toString();
1403     }
1404 
1405     /**
1406      * Compute and return the source text (all equalities and deletions).
1407      * @param diffs List of Diff objects.
1408      * @return Source text.
1409      */
1410     public String diffText1(List<Diff> diffs) {
1411 
1412         StringBuilder text = new StringBuilder();
1413         for (Diff aDiff : diffs) {
1414             if (aDiff.getOperation() != Operation.INSERT) {
1415                 text.append(aDiff.getText());
1416             }
1417         }
1418         return text.toString();
1419     }
1420 
1421     /**
1422      * Compute and return the destination text (all equalities and insertions).
1423      * @param diffs List of Diff objects.
1424      * @return Destination text.
1425      */
1426     public String diffText2(List<Diff> diffs) {
1427 
1428         StringBuilder text = new StringBuilder();
1429         for (Diff aDiff : diffs) {
1430             if (aDiff.getOperation() != Operation.DELETE) {
1431                 text.append(aDiff.getText());
1432             }
1433         }
1434         return text.toString();
1435     }
1436 
1437     /**
1438      * Compute the Levenshtein distance; the number of inserted, deleted or
1439      * substituted characters.
1440      * @param diffs List of Diff objects.
1441      * @return Number of changes.
1442      */
1443     public int diffLevenshtein(List<Diff> diffs) {
1444 
1445         int levenshtein = 0;
1446         int insertions = 0;
1447         int deletions = 0;
1448         for (Diff aDiff : diffs) {
1449             switch (aDiff.getOperation()) {
1450             case INSERT:
1451                 insertions += aDiff.getText().length();
1452                 break;
1453             case DELETE:
1454                 deletions += aDiff.getText().length();
1455                 break;
1456             case EQUAL:
1457                 // A deletion and an insertion is one substitution.
1458                 levenshtein += Math.max(insertions, deletions);
1459                 insertions = 0;
1460                 deletions = 0;
1461                 break;
1462             }
1463         }
1464         levenshtein += Math.max(insertions, deletions);
1465         return levenshtein;
1466     }
1467 
1468     /**
1469      * Crush the diff into an encoded string which describes the operations
1470      * required to transform text1 into text2.
1471      * E.g. =3\t-2\t+ing  -> Keep 3 chars, delete 2 chars, insert 'ing'.
1472      * Operations are tab-separated.  Inserted text is escaped using %xx notation.
1473      * @param diffs Array of Diff objects.
1474      * @return Delta text.
1475      */
1476     public String diffToDelta(List<Diff> diffs) {
1477 
1478         StringBuilder text = new StringBuilder();
1479         for (Diff aDiff : diffs) {
1480             switch (aDiff.getOperation()) {
1481             case INSERT:
1482                 text.append("+").append(URLEncoder.encode(aDiff.getText(), StandardCharsets.UTF_8)
1483                         .replace('+', ' ')).append("\t");
1484                 break;
1485             case DELETE:
1486                 text.append("-").append(aDiff.getText().length()).append("\t");
1487                 break;
1488             case EQUAL:
1489                 text.append("=").append(aDiff.getText().length()).append("\t");
1490                 break;
1491             }
1492         }
1493         String delta = text.toString();
1494         if (!delta.isEmpty()) {
1495             // Strip off trailing tab character.
1496             delta = delta.substring(0, delta.length() - 1);
1497             delta = Patch.unescapeForEncodeUriCompatability(delta);
1498         }
1499         return delta;
1500     }
1501 
1502     /**
1503      * Given the original text1, and an encoded string which describes the
1504      * operations required to transform text1 into text2, compute the full diff.
1505      * @param text1 Source string for the diff.
1506      * @param delta Delta text.
1507      * @return list of Diff objects or null if invalid.
1508      */
1509     public List<Diff> diffFromDelta(String text1, String delta) {
1510 
1511         List<Diff> diffs = new LinkedList<>();
1512         int pointer = 0;  // Cursor in text1
1513         String[] tokens = delta.split("\t");
1514 
1515         for (String token : tokens) {
1516             if (token.isEmpty()) {
1517                 // Blank tokens are ok (from a trailing \t).
1518                 continue;
1519             }
1520             // Each token begins with a one character parameter which specifies the
1521             // operation of this token (delete, insert, equality).
1522             String param = token.substring(1);
1523             switch (token.charAt(0)) {
1524             case '+':
1525                 // decode would change all "+" to " "
1526                 param = param.replace("+", "%2B");
1527                 try {
1528                     param = URLDecoder.decode(param, StandardCharsets.UTF_8);
1529                 } catch (IllegalArgumentException e) {
1530                     // Malformed URI sequence.
1531                     throw new IllegalArgumentException(
1532                             "Illegal escape in diff_fromDelta: " + param, e);
1533                 }
1534                 diffs.add(new Diff(Operation.INSERT, param));
1535                 break;
1536             case '-':
1537                 // Fall through.
1538             case '=':
1539                 int n;
1540                 try {
1541                     n = Integer.parseInt(param);
1542                 } catch (NumberFormatException e) {
1543                     throw new IllegalArgumentException(
1544                             "Invalid number in diff_fromDelta: " + param, e);
1545                 }
1546                 if (n < 0) {
1547                     throw new IllegalArgumentException(
1548                             "Negative number in diff_fromDelta: " + param);
1549                 }
1550                 String text;
1551                 try {
1552                     int p1 = pointer;
1553                     pointer += n;
1554                     int p2 = pointer;
1555                     text = text1.substring(p1, p2);
1556                 } catch (StringIndexOutOfBoundsException e) {
1557                     throw new IllegalArgumentException("Delta length (" + pointer
1558                             + ") larger than source text length (" + text1.length()
1559                             + ").", e);
1560                 }
1561                 if (token.charAt(0) == '=') {
1562                     diffs.add(new Diff(Operation.EQUAL, text));
1563                 } else {
1564                     diffs.add(new Diff(Operation.DELETE, text));
1565                 }
1566                 break;
1567             default:
1568                 // Anything else is an error.
1569                 throw new IllegalArgumentException(
1570                         "Invalid diff operation in diff_fromDelta: " + token.charAt(0));
1571             }
1572         }
1573         if (pointer != text1.length()) {
1574             throw new IllegalArgumentException("Delta length (" + pointer
1575                     + ") smaller than source text length (" + text1.length() + ").");
1576         }
1577         return diffs;
1578     }
1579 
1580 
1581     //  MATCH FUNCTIONS
1582 
1583     /**
1584      * Locate the best instance of 'pattern' in 'text' near 'loc'.
1585      * Returns -1 if no match found.
1586      * @param text The text to search.
1587      * @param pattern The pattern to search for.
1588      * @param valueLoc The location to search around.
1589      * @return Best match index or -1.
1590      */
1591     public int matchMain(String text, String pattern, int valueLoc) {
1592 
1593         // Check for null inputs.
1594         if (text == null || pattern == null) {
1595             throw new IllegalArgumentException("Null inputs. (match_main)");
1596         }
1597 
1598         int loc = Math.max(0, Math.min(valueLoc, text.length()));
1599         if (text.equals(pattern)) {
1600             // Shortcut (potentially not guaranteed by the algorithm)
1601             return 0;
1602         } else if (text.isEmpty()) {
1603             // Nothing to match.
1604             return -1;
1605         } else if (loc + pattern.length() <= text.length()
1606                 && text.substring(loc, loc + pattern.length()).equals(pattern)) {
1607             // Perfect match at the perfect spot!  (Includes case of null pattern)
1608             return loc;
1609         } else {
1610             // Do a fuzzy compare.
1611             return this.matchBitap(text, pattern, loc);
1612         }
1613     }
1614 
1615     /**
1616      * Locate the best instance of 'pattern' in 'text' near 'loc' using the
1617      * Bitap algorithm.  Returns -1 if no match found.
1618      * @param text The text to search.
1619      * @param pattern The pattern to search for.
1620      * @param loc The location to search around.
1621      * @return Best match index or -1.
1622      */
1623     protected int matchBitap(String text, String pattern, int loc) {
1624 
1625         // Initialize the alphabet.
1626         Map<Character, Integer> s = this.matchAlphabet(pattern);
1627 
1628         // Highest score beyond which we give up.
1629         double scoreThreshold = DiffMatchPatch.MATCH_THRESHOLD;
1630         // Is there a nearby exact match? (speedup)
1631         int bestLoc = text.indexOf(pattern, loc);
1632         if (bestLoc != -1) {
1633             scoreThreshold = Math.min(this.matchBitapScore(0, bestLoc, loc, pattern),
1634                     scoreThreshold);
1635             // What about in the other direction? (speedup)
1636             bestLoc = text.lastIndexOf(pattern, loc + pattern.length());
1637             if (bestLoc != -1) {
1638                 scoreThreshold = Math.min(this.matchBitapScore(0, bestLoc, loc, pattern),
1639                         scoreThreshold);
1640             }
1641         }
1642 
1643         // Initialize the bit arrays.
1644         int matchmask = 1 << (pattern.length() - 1);
1645         bestLoc = -1;
1646 
1647         int binMin;
1648         int binMid;
1649         int binMax = pattern.length() + text.length();
1650         // Empty initialization added to appease Java compiler.
1651         int[] lastRd = new int[0];
1652         for (int d = 0; d < pattern.length(); d++) {
1653 
1654             // Scan for the best match; each iteration allows for one more error.
1655             // Run a binary search to determine how far from 'loc' we can stray at
1656             // this error level.
1657             binMin = 0;
1658             binMid = binMax;
1659             while (binMin < binMid) {
1660                 if (this.matchBitapScore(d, loc + binMid, loc, pattern)
1661                         <= scoreThreshold) {
1662                     binMin = binMid;
1663                 } else {
1664                     binMax = binMid;
1665                 }
1666                 binMid = (binMax - binMin) / 2 + binMin;
1667             }
1668             // Use the result from this iteration as the maximum for the next.
1669             binMax = binMid;
1670             int start = Math.max(1, loc - binMid + 1);
1671             int finish = Math.min(loc + binMid, text.length()) + pattern.length();
1672 
1673             int[] rd = new int[finish + 2];
1674             rd[finish + 1] = (1 << d) - 1;
1675             for (int j = finish; j >= start; j--) {
1676 
1677                 int charMatch;
1678                 if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) {
1679                     // Out of range.
1680                     charMatch = 0;
1681                 } else {
1682                     charMatch = s.get(text.charAt(j - 1));
1683                 }
1684                 if (d == 0) {
1685                     // First pass: exact match.
1686                     rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
1687                 } else {
1688                     // Subsequent passes : fuzzy match.
1689                     rd[j] = (((rd[j + 1] << 1) | 1) & charMatch)
1690                             | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
1691                 }
1692 
1693                 if ((rd[j] & matchmask) != 0) {
1694                     double score = this.matchBitapScore(d, j - 1, loc, pattern);
1695                     // This match will almost certainly be better than any existing
1696                     // match.  But check anyway.
1697                     if (score <= scoreThreshold) {
1698                         // Told you so.
1699                         scoreThreshold = score;
1700                         bestLoc = j - 1;
1701                         if (bestLoc > loc) {
1702                             // When passing loc, don't exceed our current distance from loc.
1703                             start = Math.max(1, 2 * loc - bestLoc);
1704                         } else {
1705                             // Already passed loc, downhill from here on in.
1706                             break;
1707                         }
1708                     }
1709                 }
1710             }
1711             if (this.matchBitapScore(d + 1, loc, loc, pattern) > scoreThreshold) {
1712                 // No hope for a (better) match at greater error levels.
1713                 break;
1714             }
1715             lastRd = rd;
1716         }
1717         return bestLoc;
1718     }
1719 
1720     /**
1721      * Compute and return the score for a match with e errors and x location.
1722      * @param e Number of errors in match.
1723      * @param x Location of match.
1724      * @param loc Expected location of match.
1725      * @param pattern Pattern being sought.
1726      * @return Overall score for match (0.0 = good, 1.0 = bad).
1727      */
1728     private double matchBitapScore(int e, int x, int loc, String pattern) {
1729 
1730         float accuracy = (float) e / pattern.length();
1731         int proximity = Math.abs(loc - x);
1732         return accuracy + (proximity / (float) DiffMatchPatch.MATCH_DISTANCE);
1733     }
1734 
1735     /**
1736      * Initialize the alphabet for the Bitap algorithm.
1737      * @param pattern The text to encode.
1738      * @return Hash of character locations.
1739      */
1740     protected Map<Character, Integer> matchAlphabet(String pattern) {
1741 
1742         Map<Character, Integer> s = new HashMap<>();
1743         char[] charPattern = pattern.toCharArray();
1744         for (char c : charPattern) {
1745             s.put(c, 0);
1746         }
1747         int i = 0;
1748         for (char c : charPattern) {
1749             s.put(c, s.get(c) | (1 << (pattern.length() - i - 1)));
1750             i++;
1751         }
1752         return s;
1753     }
1754 
1755 
1756     //  PATCH FUNCTIONS
1757 
1758     /**
1759      * Increase the context until it is unique,
1760      * but don't let the pattern expand beyond Match_MaxBits.
1761      * @param patch The patch to grow.
1762      * @param text Source text.
1763      */
1764     protected void patchAddContext(Patch patch, String text) {
1765 
1766         if (text.isEmpty()) {
1767             return;
1768         }
1769         String pattern = text.substring(patch.getStart2(), patch.getStart2() + patch.getLength1());
1770         int padding = 0;
1771 
1772         // Look for the first and last matches of pattern in text.  If two different
1773         // matches are found, increase the pattern length.
1774         while (text.indexOf(pattern) != text.lastIndexOf(pattern)
1775                 && pattern.length() < DiffMatchPatch.MATCH_MAX_BITS - DiffMatchPatch.PATCH_MARGIN - DiffMatchPatch.PATCH_MARGIN) {
1776             padding += DiffMatchPatch.PATCH_MARGIN;
1777             pattern = text.substring(Math.max(0, patch.getStart2() - padding),
1778                     Math.min(text.length(), patch.getStart2() + patch.getLength1() + padding));
1779         }
1780         // Add one chunk for good luck.
1781         padding += DiffMatchPatch.PATCH_MARGIN;
1782 
1783         // Add the prefix.
1784         String prefix = text.substring(Math.max(0, patch.getStart2() - padding),
1785                 patch.getStart2());
1786         if (!prefix.isEmpty()) {
1787             patch.getDiffs().addFirst(new Diff(Operation.EQUAL, prefix));
1788         }
1789         // Add the suffix.
1790         String suffix = text.substring(patch.getStart2() + patch.getLength1(),
1791                 Math.min(text.length(), patch.getStart2() + patch.getLength1() + padding));
1792         if (!suffix.isEmpty()) {
1793             patch.getDiffs().addLast(new Diff(Operation.EQUAL, suffix));
1794         }
1795 
1796         // Roll back the start points.
1797         patch.setStart1(patch.getStart1() - prefix.length());
1798         patch.setStart2(patch.getStart2() - prefix.length());
1799         // Extend the lengths.
1800         patch.setLength1(patch.getLength1() + prefix.length() + suffix.length());
1801         patch.setLength2(patch.getLength2() + prefix.length() + suffix.length());
1802     }
1803 
1804     /**
1805      * Compute a list of patches to turn text1 into text2.
1806      * A set of diffs will be computed.
1807      * @param text1 Old text.
1808      * @param text2 New text.
1809      * @return LinkedList of Patch objects.
1810      */
1811     public List<Patch> patchMake(String text1, String text2) {
1812 
1813         if (text1 == null || text2 == null) {
1814             throw new IllegalArgumentException("Null inputs. (patch_make)");
1815         }
1816         // No diffs provided, compute our own.
1817         LinkedList<Diff> diffs = this.diffMain(text1, text2, true);
1818         if (diffs.size() > 2) {
1819             this.diffCleanupSemantic(diffs);
1820             this.diffCleanupEfficiency(diffs);
1821         }
1822         return this.patchMake(text1, diffs);
1823     }
1824 
1825     /**
1826      * Compute a list of patches to turn text1 into text2.
1827      * text1 will be derived from the provided diffs.
1828      * @param diffs Array of Diff objects for text1 to text2.
1829      * @return LinkedList of Patch objects.
1830      */
1831     public List<Patch> patchMake(LinkedList<Diff> diffs) {
1832 
1833         if (diffs == null) {
1834             throw new IllegalArgumentException("Null inputs. (patch_make)");
1835         }
1836         // No origin string provided, compute our own.
1837         String text1 = this.diffText1(diffs);
1838         return this.patchMake(text1, diffs);
1839     }
1840 
1841     /**
1842      * Compute a list of patches to turn text1 into text2.
1843      * text2 is not provided, diffs are the delta between text1 and text2.
1844      * @param text1 Old text.
1845      * @param diffs Array of Diff objects for text1 to text2.
1846      * @return Deque of Patch objects.
1847      */
1848     public List<Patch> patchMake(String text1, Deque<Diff> diffs) {
1849 
1850         if (text1 == null || diffs == null) {
1851             throw new IllegalArgumentException("Null inputs. (patch_make)");
1852         }
1853 
1854         List<Patch> patches = new LinkedList<>();
1855         if (diffs.isEmpty()) {
1856             return patches;  // Get rid of the null case.
1857         }
1858         Patch patch = new Patch();
1859         int charCount1 = 0;  // Number of characters into the text1 string.
1860         int charCount2 = 0;  // Number of characters into the text2 string.
1861         // Start with text1 (prepatch_text) and apply the diffs until we arrive at
1862         // text2 (postpatch_text). We recreate the patches one by one to determine
1863         // context info.
1864         String prepatchText = text1;
1865         String postpatchText = text1;
1866 
1867         for (Diff aDiff : diffs) {
1868             if (patch.getDiffs().isEmpty() && aDiff.getOperation() != Operation.EQUAL) {
1869                 // A new patch starts here.
1870                 patch.setStart1(charCount1);
1871                 patch.setStart2(charCount2);
1872             }
1873 
1874             switch (aDiff.getOperation()) {
1875             case INSERT:
1876                 patch.getDiffs().add(aDiff);
1877                 patch.setLength2(patch.getLength2() + aDiff.getText().length());
1878                 postpatchText = postpatchText.substring(0, charCount2)
1879                         + aDiff.getText() + postpatchText.substring(charCount2);
1880                 break;
1881             case DELETE:
1882                 patch.setLength1(patch.getLength1() + aDiff.getText().length());
1883                 patch.getDiffs().add(aDiff);
1884                 postpatchText = postpatchText.substring(0, charCount2)
1885                         + postpatchText.substring(charCount2 + aDiff.getText().length());
1886                 break;
1887             case EQUAL:
1888                 if (
1889                     aDiff.getText().length() <= 2 * DiffMatchPatch.PATCH_MARGIN
1890                     && !patch.getDiffs().isEmpty() && aDiff != diffs.getLast()
1891                 ) {
1892                     // Small equality inside a patch.
1893                     patch.getDiffs().add(aDiff);
1894                     patch.setLength1(patch.getLength1() + aDiff.getText().length());
1895                     patch.setLength2(patch.getLength2() + aDiff.getText().length());
1896                 }
1897 
1898                 if (
1899                     aDiff.getText().length() >= 2 * DiffMatchPatch.PATCH_MARGIN
1900                     && !patch.getDiffs().isEmpty()
1901                 ) {
1902                     // Time for a new patch.
1903                     this.patchAddContext(patch, prepatchText);
1904                     patches.add(patch);
1905                     patch = new Patch();
1906                     // Unlike Unidiff, our patch lists have a rolling context.
1907                     // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
1908                     // Update prepatch text & pos to reflect the application of the
1909                     // just completed patch.
1910                     prepatchText = postpatchText;
1911                     charCount1 = charCount2;
1912                 }
1913                 break;
1914             }
1915 
1916             // Update the current character count.
1917             if (aDiff.getOperation() != Operation.INSERT) {
1918                 charCount1 += aDiff.getText().length();
1919             }
1920             if (aDiff.getOperation() != Operation.DELETE) {
1921                 charCount2 += aDiff.getText().length();
1922             }
1923         }
1924         // Pick up the leftover patch if not empty.
1925         if (!patch.getDiffs().isEmpty()) {
1926             this.patchAddContext(patch, prepatchText);
1927             patches.add(patch);
1928         }
1929 
1930         return patches;
1931     }
1932 
1933     /**
1934      * Given an array of patches, return another array that is identical.
1935      * @param patches Array of Patch objects.
1936      * @return list of Patch objects.
1937      */
1938     public LinkedList<Patch> patchDeepCopy(List<Patch> patches) {
1939 
1940         LinkedList<Patch> patchesCopy = new LinkedList<>();
1941         for (Patch aPatch : patches) {
1942             Patch patchCopy = new Patch();
1943             for (Diff aDiff : aPatch.getDiffs()) {
1944                 Diff diffCopy = new Diff(aDiff.getOperation(), aDiff.getText());
1945                 patchCopy.getDiffs().add(diffCopy);
1946             }
1947             patchCopy.setStart1(aPatch.getStart1());
1948             patchCopy.setStart2(aPatch.getStart2());
1949             patchCopy.setLength1(aPatch.getLength1());
1950             patchCopy.setLength2(aPatch.getLength2());
1951             patchesCopy.add(patchCopy);
1952         }
1953         return patchesCopy;
1954     }
1955 
1956     /**
1957      * Merge a set of patches onto the text.  Return a patched text, as well
1958      * as an array of true/false values indicating which patches were applied.
1959      * @param valuePatches Array of Patch objects
1960      * @param valueText Old text.
1961      * @return Two element Object array, containing the new text and an array of
1962      *      boolean values.
1963      */
1964     public Object[] patchApply(LinkedList<Patch> valuePatches, String valueText) {
1965 
1966         if (valuePatches.isEmpty()) {
1967             return new Object[]{valueText, new boolean[0]};
1968         }
1969         
1970         // Deep copy the patches so that no changes are made to originals.
1971         LinkedList<Patch> patches = this.patchDeepCopy(valuePatches);
1972 
1973         String nullPadding = this.patchAddPadding(patches);
1974         String text = nullPadding + valueText + nullPadding;
1975         this.patchSplitMax(patches);
1976 
1977         int x = 0;
1978         // delta keeps track of the offset between the expected and actual location
1979         // of the previous patch.  If there are patches expected at positions 10 and
1980         // 20, but the first patch was found at 12, delta is 2 and the second patch
1981         // has an effective expected position of 22.
1982         int delta = 0;
1983         boolean[] results = new boolean[patches.size()];
1984         for (Patch aPatch : patches) {
1985 
1986             int expectedLoc = aPatch.getStart2() + delta;
1987             String text1 = this.diffText1(aPatch.getDiffs());
1988             int startLoc;
1989             int endLoc = -1;
1990 
1991             if (text1.length() > DiffMatchPatch.MATCH_MAX_BITS) {
1992                 // patch_splitMax will only provide an oversized pattern in the case of
1993                 // a monster delete.
1994                 startLoc = this.matchMain(text,
1995                         text1.substring(0, DiffMatchPatch.MATCH_MAX_BITS), expectedLoc);
1996                 if (startLoc != -1) {
1997                     endLoc = this.matchMain(text,
1998                             text1.substring(text1.length() - DiffMatchPatch.MATCH_MAX_BITS),
1999                             expectedLoc + text1.length() - DiffMatchPatch.MATCH_MAX_BITS);
2000                     if (endLoc == -1 || startLoc >= endLoc) {
2001                         // Can't find valid trailing context.  Drop this patch.
2002                         startLoc = -1;
2003                     }
2004                 }
2005             } else {
2006                 startLoc = this.matchMain(text, text1, expectedLoc);
2007             }
2008 
2009             if (startLoc == -1) {
2010                 // No match found.  :(
2011                 results[x] = false;
2012                 // Subtract the delta for this failed patch from subsequent patches.
2013                 delta -= aPatch.getLength2() - aPatch.getLength1();
2014             } else {
2015                 // Found a match.  :)
2016                 results[x] = true;
2017                 delta = startLoc - expectedLoc;
2018                 String text2;
2019                 if (endLoc == -1) {
2020                     text2 = text.substring(startLoc,
2021                             Math.min(startLoc + text1.length(), text.length()));
2022                 } else {
2023                     text2 = text.substring(startLoc,
2024                             Math.min(endLoc + DiffMatchPatch.MATCH_MAX_BITS, text.length()));
2025                 }
2026 
2027                 if (text1.equals(text2)) {
2028                     // Perfect match, just shove the replacement text in.
2029                     text = text.substring(0, startLoc) + this.diffText2(aPatch.getDiffs())
2030                             + text.substring(startLoc + text1.length());
2031                 } else {
2032                     // Imperfect match.  Run a diff to get a framework of equivalent
2033                     // indices.
2034                     List<Diff> diffs = this.diffMain(text1, text2, false);
2035                     if (text1.length() > DiffMatchPatch.MATCH_MAX_BITS
2036                             && this.diffLevenshtein(diffs) / (float) text1.length()
2037                             > DiffMatchPatch.PATCH_DELETE_THRESHOLD) {
2038                         // The end points match, but the content is unacceptably bad.
2039                         results[x] = false;
2040                     } else {
2041                         this.diffCleanupSemanticLossless(diffs);
2042                         int index1 = 0;
2043                         for (Diff aDiff : aPatch.getDiffs()) {
2044                             if (aDiff.getOperation() != Operation.EQUAL) {
2045                                 int index2 = this.diffXIndex(diffs, index1);
2046                                 if (aDiff.getOperation() == Operation.INSERT) {
2047                                     // Insertion
2048                                     text = text.substring(0, startLoc + index2) + aDiff.getText()
2049                                             + text.substring(startLoc + index2);
2050                                 } else if (aDiff.getOperation() == Operation.DELETE) {
2051                                     // Deletion
2052                                     text = text.substring(0, startLoc + index2)
2053                                             + text.substring(startLoc + this.diffXIndex(diffs,
2054                                                     index1 + aDiff.getText().length()));
2055                                 }
2056                             }
2057                             if (aDiff.getOperation() != Operation.DELETE) {
2058                                 index1 += aDiff.getText().length();
2059                             }
2060                         }
2061                     }
2062                 }
2063             }
2064             x++;
2065         }
2066         // Strip the padding off.
2067         text = text.substring(nullPadding.length(), text.length()
2068                 - nullPadding.length());
2069         return new Object[]{text, results};
2070     }
2071 
2072     /**
2073      * Add some padding on text start and end so that edges can match something.
2074      * Intended to be called only from within patch_apply.
2075      * @param patches Array of Patch objects.
2076      * @return The padding string added to each side.
2077      */
2078     public String patchAddPadding(Deque<Patch> patches) {
2079 
2080         short paddingLength = DiffMatchPatch.PATCH_MARGIN;
2081         StringBuilder nullPadding = new StringBuilder();
2082         for (short x = 1; x <= paddingLength; x++) {
2083             nullPadding.append((char) x);
2084         }
2085 
2086         // Bump all the patches forward.
2087         for (Patch aPatch : patches) {
2088             aPatch.setStart1(aPatch.getStart1() + paddingLength);
2089             aPatch.setStart2(aPatch.getStart2() + paddingLength);
2090         }
2091 
2092         // Add some padding on start of first diff.
2093         Patch patch = patches.getFirst();
2094         Deque<Diff> diffs = patch.getDiffs();
2095         if (diffs.isEmpty() || diffs.getFirst().getOperation() != Operation.EQUAL) {
2096             // Add nullPadding equality.
2097             diffs.addFirst(new Diff(Operation.EQUAL, nullPadding.toString()));
2098             patch.setStart1(patch.getStart1() - paddingLength);  // Should be 0.
2099             patch.setStart2(patch.getStart2() - paddingLength);  // Should be 0.
2100             patch.setLength1(patch.getLength1() + paddingLength);
2101             patch.setLength2(patch.getLength2() + paddingLength);
2102         } else if (paddingLength > diffs.getFirst().getText().length()) {
2103             // Grow first equality.
2104             Diff firstDiff = diffs.getFirst();
2105             int extraLength = paddingLength - firstDiff.getText().length();
2106             firstDiff.setText(nullPadding.substring(firstDiff.getText().length())
2107                     + firstDiff.getText());
2108             patch.setStart1(patch.getStart1() - extraLength);
2109             patch.setStart2(patch.getStart2() - extraLength);
2110             patch.setLength1(patch.getLength1() + extraLength);
2111             patch.setLength2(patch.getLength2() + extraLength);
2112         }
2113 
2114         // Add some padding on end of last diff.
2115         patch = patches.getLast();
2116         diffs = patch.getDiffs();
2117         if (diffs.isEmpty() || diffs.getLast().getOperation() != Operation.EQUAL) {
2118             // Add nullPadding equality.
2119             diffs.addLast(new Diff(Operation.EQUAL, nullPadding.toString()));
2120             patch.setLength1(patch.getLength1() + paddingLength);
2121             patch.setLength2(patch.getLength2() + paddingLength);
2122         } else if (paddingLength > diffs.getLast().getText().length()) {
2123             // Grow last equality.
2124             Diff lastDiff = diffs.getLast();
2125             int extraLength = paddingLength - lastDiff.getText().length();
2126             lastDiff.setText(lastDiff.getText() + nullPadding.substring(0, extraLength));
2127             patch.setLength1(patch.getLength1() + extraLength);
2128             patch.setLength2(patch.getLength2() + extraLength);
2129         }
2130 
2131         return nullPadding.toString();
2132     }
2133 
2134     /**
2135      * Look through the patches and break up any which are longer than the
2136      * maximum limit of the match algorithm.
2137      * Intended to be called only from within patch_apply.
2138      * @param patches List of Patch objects.
2139      */
2140     public void patchSplitMax(List<Patch> patches) {
2141 
2142         short patchSize = DiffMatchPatch.MATCH_MAX_BITS;
2143         String precontext;
2144         String postcontext;
2145         Patch patch;
2146         int start1;
2147         int start2;
2148         boolean empty;
2149         Operation diffType;
2150         String diffText;
2151         ListIterator<Patch> pointer = patches.listIterator();
2152         Patch bigpatch = pointer.hasNext() ? pointer.next() : null;
2153 
2154         while (bigpatch != null) {
2155             if (bigpatch.getLength1() <= DiffMatchPatch.MATCH_MAX_BITS) {
2156                 bigpatch = pointer.hasNext() ? pointer.next() : null;
2157                 continue;
2158             }
2159             // Remove the big old patch.
2160             pointer.remove();
2161             start1 = bigpatch.getStart1();
2162             start2 = bigpatch.getStart2();
2163             precontext = StringUtils.EMPTY;
2164 
2165             while (!bigpatch.getDiffs().isEmpty()) {
2166                 // Create one of several smaller patches.
2167                 patch = new Patch();
2168                 empty = true;
2169                 patch.setStart1(start1 - precontext.length());
2170                 patch.setStart2(start2 - precontext.length());
2171                 if (!precontext.isEmpty()) {
2172                     patch.setLength1(patch.setLength2(precontext.length()));
2173                     patch.getDiffs().add(new Diff(Operation.EQUAL, precontext));
2174                 }
2175                 while (!bigpatch.getDiffs().isEmpty()
2176                         && patch.getLength1() < patchSize - DiffMatchPatch.PATCH_MARGIN) {
2177                     diffType = bigpatch.getDiffs().getFirst().getOperation();
2178                     diffText = bigpatch.getDiffs().getFirst().getText();
2179 
2180                     if (diffType == Operation.INSERT) {
2181                         // Insertions are harmless.
2182                         patch.setLength2(patch.getLength2() + diffText.length());
2183                         start2 += diffText.length();
2184                         patch.getDiffs().addLast(bigpatch.getDiffs().removeFirst());
2185                         empty = false;
2186                     } else if (diffType == Operation.DELETE && patch.getDiffs().size() == 1
2187                             && patch.getDiffs().getFirst().getOperation() == Operation.EQUAL
2188                             && diffText.length() > 2 * patchSize) {
2189                         // This is a large deletion.  Let it pass in one chunk.
2190                         patch.setLength1(patch.getLength1() + diffText.length());
2191                         start1 += diffText.length();
2192                         empty = false;
2193                         patch.getDiffs().add(new Diff(diffType, diffText));
2194                         bigpatch.getDiffs().removeFirst();
2195                     } else {
2196                         // Deletion or equality.  Only take as much as we can stomach.
2197                         diffText = diffText.substring(0, Math.min(diffText.length(),
2198                                 patchSize - patch.getLength1() - DiffMatchPatch.PATCH_MARGIN));
2199                         patch.setLength1(patch.getLength1() + diffText.length());
2200                         start1 += diffText.length();
2201                         if (diffType == Operation.EQUAL) {
2202                             patch.setLength2(patch.getLength2() + diffText.length());
2203                             start2 += diffText.length();
2204                         } else {
2205                             empty = false;
2206                         }
2207                         patch.getDiffs().add(new Diff(diffType, diffText));
2208                         if (diffText.equals(bigpatch.getDiffs().getFirst().getText())) {
2209                             bigpatch.getDiffs().removeFirst();
2210                         } else {
2211                             bigpatch.getDiffs().getFirst().setText(bigpatch.getDiffs().getFirst().getText()
2212                                     .substring(diffText.length()));
2213                         }
2214                     }
2215                 }
2216                 // Compute the head context for the next patch.
2217                 precontext = this.diffText2(patch.getDiffs());
2218                 precontext = precontext.substring(Math.max(0, precontext.length()
2219                         - DiffMatchPatch.PATCH_MARGIN));
2220                 // Append the end context for this patch.
2221                 if (this.diffText1(bigpatch.getDiffs()).length() > DiffMatchPatch.PATCH_MARGIN) {
2222                     postcontext = this.diffText1(bigpatch.getDiffs()).substring(0, DiffMatchPatch.PATCH_MARGIN);
2223                 } else {
2224                     postcontext = this.diffText1(bigpatch.getDiffs());
2225                 }
2226 
2227                 if (!postcontext.isEmpty()) {
2228                     patch.setLength1(patch.getLength1() + postcontext.length());
2229                     patch.setLength2(patch.getLength2() + postcontext.length());
2230                     if (!patch.getDiffs().isEmpty()
2231                             && patch.getDiffs().getLast().getOperation() == Operation.EQUAL) {
2232                         patch.getDiffs().getLast().setText(patch.getDiffs().getLast().getText() + postcontext);
2233                     } else {
2234                         patch.getDiffs().add(new Diff(Operation.EQUAL, postcontext));
2235                     }
2236                 }
2237 
2238                 if (!empty) {
2239                     pointer.add(patch);
2240                 }
2241             }
2242             bigpatch = pointer.hasNext() ? pointer.next() : null;
2243         }
2244     }
2245 
2246     /**
2247      * Take a list of patches and return a textual representation.
2248      * @param patches List of Patch objects.
2249      * @return Text representation of patches.
2250      */
2251     public String patchToText(List<Patch> patches) {
2252 
2253         StringBuilder text = new StringBuilder();
2254         for (Patch aPatch : patches) {
2255             text.append(aPatch);
2256         }
2257         return text.toString();
2258     }
2259 
2260     /**
2261      * Parse a textual representation of patches and return a List of Patch
2262      * objects.
2263      * @param textline Text representation of patches.
2264      * @return List of Patch objects.
2265      */
2266     public List<Patch> patchFromText(String textline) {
2267 
2268         List<Patch> patches = new LinkedList<>();
2269         if (textline.isEmpty()) {
2270             return patches;
2271         }
2272         List<String> textList = Arrays.asList(textline.split("\n"));
2273         Deque<String> text = new LinkedList<>(textList);
2274         Patch patch;
2275         Pattern patchHeader = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$");
2276         Matcher m;
2277         char sign;
2278         String line;
2279         while (!text.isEmpty()) {
2280 
2281             m = patchHeader.matcher(text.getFirst());
2282             if (!m.matches()) {
2283                 throw new IllegalArgumentException(
2284                         "Invalid patch string: " + text.getFirst());
2285             }
2286             patch = new Patch();
2287             patches.add(patch);
2288             patch.setStart1(Integer.parseInt(m.group(1)));
2289             if (m.group(2).isEmpty()) {
2290                 patch.setStart1(patch.getStart1() - 1);
2291                 patch.setLength1(1);
2292             } else if ("0".equals(m.group(2))) {
2293                 patch.setLength1(0);
2294             } else {
2295                 patch.setStart1(patch.getStart1() - 1);
2296                 patch.setLength1(Integer.parseInt(m.group(2)));
2297             }
2298 
2299             patch.setStart2(Integer.parseInt(m.group(3)));
2300             if (m.group(4).isEmpty()) {
2301                 patch.setStart2(patch.getStart2() - 1);
2302                 patch.setLength2(1);
2303             } else if ("0".equals(m.group(4))) {
2304                 patch.setLength2(0);
2305             } else {
2306                 patch.setStart2(patch.getStart2() - 1);
2307                 patch.setLength2(Integer.parseInt(m.group(4)));
2308             }
2309             text.removeFirst();
2310 
2311             while (!text.isEmpty()) {
2312 
2313                 try {
2314                     sign = text.getFirst().charAt(0);
2315                 } catch (IndexOutOfBoundsException e) {
2316                     
2317                     LOGGER.log(LogLevelUtil.IGNORE, e);
2318                     
2319                     // Blank line?  Whatever.
2320                     text.removeFirst();
2321                     continue;
2322                 }
2323 
2324                 line = text.getFirst().substring(1);
2325                 line = line.replace("+", "%2B");  // decode would change all "+" to " "
2326                 try {
2327                     line = URLDecoder.decode(line, StandardCharsets.UTF_8);
2328                 } catch (IllegalArgumentException e) {
2329                     // Malformed URI sequence.
2330                     throw new IllegalArgumentException(
2331                             "Illegal escape in patch_fromText: " + line, e);
2332                 }
2333 
2334                 if (sign == '-') {
2335                     // Deletion.
2336                     patch.getDiffs().add(new Diff(Operation.DELETE, line));
2337                 } else if (sign == '+') {
2338                     // Insertion.
2339                     patch.getDiffs().add(new Diff(Operation.INSERT, line));
2340                 } else if (sign == ' ') {
2341                     // Minor equality.
2342                     patch.getDiffs().add(new Diff(Operation.EQUAL, line));
2343                 } else if (sign == '@') {
2344                     // Start of next patch.
2345                     break;
2346                 } else {
2347                     // WTF?
2348                     throw new IllegalArgumentException(
2349                             "Invalid patch mode '" + sign + "' in: " + line);
2350                 }
2351                 text.removeFirst();
2352             }
2353         }
2354         return patches;
2355     }
2356 }