1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
35
36
37
38
39
40
41
42
43
44
45 public class DiffMatchPatch {
46
47
48
49
50 private static final Logger LOGGER = LogManager.getRootLogger();
51
52
53
54
55
56
57
58 public static final float DIFF_TIMEOUT = 1.0f;
59
60
61
62
63 public static final short DIFF_EDIT_COST = 4;
64
65
66
67
68 public static final float MATCH_THRESHOLD = 0.5f;
69
70
71
72
73
74
75 public static final int MATCH_DISTANCE = 1000;
76
77
78
79
80
81
82
83 public static final float PATCH_DELETE_THRESHOLD = 0.5f;
84
85
86
87
88 public static final short PATCH_MARGIN = 4;
89
90
91
92
93 private static final short MATCH_MAX_BITS = 32;
94
95
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
101
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
117
118
119
120
121
122
123
124 public enum Operation {
125 DELETE, INSERT, EQUAL
126 }
127
128
129
130
131
132
133
134
135
136
137 public List<Diff> diffMain(String text1, String text2) {
138 return this.diffMain(text1, text2, true);
139 }
140
141
142
143
144
145
146
147
148
149
150 public LinkedList<Diff> diffMain(String text1, String text2, boolean checklines) {
151
152
153 long deadline = System.currentTimeMillis() + (long) (DiffMatchPatch.DIFF_TIMEOUT * 1000);
154 return this.diffMain(text1, text2, checklines, deadline);
155 }
156
157
158
159
160
161
162
163
164
165
166
167
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
175 if (text1 == null || text2 == null) {
176 throw new IllegalArgumentException("Null inputs. (diff_main)");
177 }
178
179
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
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
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
202 diffs = this.diffCompute(text1, text2, checklines, deadline);
203
204
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
218
219
220
221
222
223
224
225
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
233 diffs.add(new Diff(Operation.INSERT, text2));
234 return diffs;
235 }
236
237 if (text2.isEmpty()) {
238
239 diffs.add(new Diff(Operation.DELETE, text1));
240 return diffs;
241 }
242
243 {
244
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
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
260
261 diffs.add(new Diff(Operation.DELETE, text1));
262 diffs.add(new Diff(Operation.INSERT, text2));
263 return diffs;
264 }
265 }
266
267
268 String[] hm = this.diffHalfMatch(text1, text2);
269 if (hm != null) {
270
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
277 LinkedList<Diff> diffsA = this.diffMain(text1A, text2A, checklines, deadline);
278 List<Diff> diffsB = this.diffMain(text1B, text2B, checklines, deadline);
279
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
295
296
297
298
299
300
301
302 private LinkedList<Diff> diffLineMode(String valueText1, String valueText2, long deadline) {
303
304
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
313 this.diffCharsToLines(diffs, linearray);
314
315 this.diffCleanupSemantic(diffs);
316
317
318
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
339 if (countDelete >= 1 && countInsert >= 1) {
340
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();
359
360 return diffs;
361 }
362
363
364
365
366
367
368
369
370
371
372 protected LinkedList<Diff> diffBisect(String text1, String text2, long deadline) {
373
374
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
389
390 boolean front = delta % 2 != 0;
391
392
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
400 if (System.currentTimeMillis() > deadline) {
401 break;
402 }
403
404
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
422 k1end += 2;
423 } else if (y1 > text2Length) {
424
425 k1start += 2;
426 } else if (front) {
427 int k2Offset = maxD + delta - k1;
428 if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] != -1) {
429
430 int x2 = text1Length - v2[k2Offset];
431 if (x1 >= x2) {
432
433 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
434 }
435 }
436 }
437 }
438
439
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
458 k2end += 2;
459 } else if (y2 > text2Length) {
460
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
468 x2 = text1Length - x2;
469 if (x1 >= x2) {
470
471 return this.diffBisectSplit(text1, text2, x1, y1, deadline);
472 }
473 }
474 }
475 }
476 }
477
478
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
487
488
489
490
491
492
493
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
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
512
513
514
515
516
517
518
519 protected LinesToCharsResult diffLinesToChars(String text1, String text2) {
520
521 List<String> lineArray = new ArrayList<>();
522 Map<String, Integer> lineHash = new HashMap<>();
523
524
525
526
527
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
537
538
539
540
541
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
551
552
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
574
575
576
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
592
593
594
595
596 public int diffCommonPrefix(String text1, String text2) {
597
598
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
610
611
612
613
614 public int diffCommonSuffix(String text1, String text2) {
615
616
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
630
631
632
633
634
635 protected int diffCommonOverlap(String valueText1, String valueText2) {
636
637 String text1 = valueText1;
638 String text2 = valueText2;
639
640
641 int text1Length = text1.length();
642 int text2Length = text2.length();
643
644 if (text1Length == 0 || text2Length == 0) {
645 return 0;
646 }
647
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
655 if (text1.equals(text2)) {
656 return textLength;
657 }
658
659
660
661
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
681
682
683
684
685
686
687
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;
695 }
696
697
698 String[] hm1 = this.diffHalfMatchI(longtext, shorttext, (longtext.length() + 3) / 4);
699
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
710 hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
711 }
712
713
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
723
724
725
726
727
728
729
730
731 private String[] diffHalfMatchI(String longtext, String shorttext, int i) {
732
733
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
765
766
767 public void diffCleanupSemantic(LinkedList<Diff> diffs) {
768
769 if (diffs.isEmpty()) {
770 return;
771 }
772 boolean changes = false;
773
774 Stack<Diff> equalities = new Stack<>();
775 String lastequality = null;
776 ListIterator<Diff> pointer = diffs.listIterator();
777
778 int lengthInsertions1 = 0;
779 int lengthDeletions1 = 0;
780
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
788 equalities.push(thisDiff);
789 lengthInsertions1 = lengthInsertions2;
790 lengthDeletions1 = lengthDeletions2;
791 lengthInsertions2 = 0;
792 lengthDeletions2 = 0;
793 lastequality = thisDiff.getText();
794 } else {
795
796 if (thisDiff.getOperation() == Operation.INSERT) {
797 lengthInsertions2 += thisDiff.getText().length();
798 } else {
799 lengthDeletions2 += thisDiff.getText().length();
800 }
801
802
803 if (
804 lastequality != null
805 && lastequality.length() <= Math.max(lengthInsertions1, lengthDeletions1)
806 && lastequality.length() <= Math.max(lengthInsertions2, lengthDeletions2)
807 ) {
808
809 while (thisDiff != equalities.lastElement()) {
810 thisDiff = pointer.previous();
811 }
812 pointer.next();
813
814
815 pointer.set(new Diff(Operation.DELETE, lastequality));
816
817 pointer.add(new Diff(Operation.INSERT, lastequality));
818
819 equalities.pop();
820 if (!equalities.empty()) {
821
822 equalities.pop();
823 }
824 if (equalities.empty()) {
825
826 while (pointer.hasPrevious()) {
827 pointer.previous();
828 }
829 } else {
830
831 thisDiff = equalities.lastElement();
832 while (thisDiff != pointer.previous()) {
833
834 }
835 }
836
837 lengthInsertions1 = 0;
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
849 if (changes) {
850 this.diffCleanupMerge(diffs);
851 }
852 this.diffCleanupSemanticLossless(diffs);
853
854
855
856
857
858
859
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
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
887
888 }
889 } else {
890 if (overlapLength2 >= deletion.length() / 2.0 ||
891 overlapLength2 >= insertion.length() / 2.0) {
892
893
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
902
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
914
915
916
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
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
937 while (nextDiff != null) {
938 if (prevDiff.getOperation() == Operation.EQUAL &&
939 nextDiff.getOperation() == Operation.EQUAL) {
940
941 equality1.setLength(0);
942 equality1.append(prevDiff.getText());
943 edit = thisDiff.getText();
944 equality2.setLength(0);
945 equality2.append(nextDiff.getText());
946
947
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
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
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
984 if (!bestEquality1.isEmpty()) {
985 prevDiff.setText(bestEquality1);
986 } else {
987 pointer.previous();
988 pointer.previous();
989 pointer.previous();
990 pointer.remove();
991 pointer.next();
992 pointer.next();
993 }
994 thisDiff.setText(bestEdit);
995 if (!bestEquality2.isEmpty()) {
996 nextDiff.setText(bestEquality2);
997 } else {
998 pointer.remove();
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
1012
1013
1014
1015
1016
1017
1018 private int diffCleanupSemanticScore(String one, String two) {
1019
1020 if (one.isEmpty() || two.isEmpty()) {
1021
1022 return 6;
1023 }
1024
1025
1026
1027
1028
1029
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
1045 return 5;
1046 } else if (lineBreak1 || lineBreak2) {
1047
1048 return 4;
1049 } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
1050
1051 return 3;
1052 } else if (whitespace1 || whitespace2) {
1053
1054 return 2;
1055 } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
1056
1057 return 1;
1058 }
1059 return 0;
1060 }
1061
1062
1063
1064
1065
1066 public void diffCleanupEfficiency(LinkedList<Diff> diffs) {
1067
1068 if (diffs.isEmpty()) {
1069 return;
1070 }
1071 boolean changes = false;
1072
1073 Stack<Diff> equalities = new Stack<>();
1074 String lastequality = null;
1075 ListIterator<Diff> pointer = diffs.listIterator();
1076
1077 boolean preIns = false;
1078
1079 boolean preDel = false;
1080
1081 boolean postIns = false;
1082
1083 boolean postDel = false;
1084 Diff thisDiff = pointer.next();
1085 Diff safeDiff = thisDiff;
1086 while (thisDiff != null) {
1087
1088 if (thisDiff.getOperation() == Operation.EQUAL) {
1089
1090
1091 if (thisDiff.getText().length() < DiffMatchPatch.DIFF_EDIT_COST && (postIns || postDel)) {
1092
1093 equalities.push(thisDiff);
1094 preIns = postIns;
1095 preDel = postDel;
1096 lastequality = thisDiff.getText();
1097 } else {
1098
1099 equalities.clear();
1100 lastequality = null;
1101 safeDiff = thisDiff;
1102 }
1103 postIns = postDel = false;
1104 } else {
1105
1106 if (thisDiff.getOperation() == Operation.DELETE) {
1107 postDel = true;
1108 } else {
1109 postIns = true;
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
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
1131 while (thisDiff != equalities.lastElement()) {
1132 thisDiff = pointer.previous();
1133 }
1134 pointer.next();
1135
1136
1137 pointer.set(new Diff(Operation.DELETE, lastequality));
1138
1139 thisDiff = new Diff(Operation.INSERT, lastequality);
1140 pointer.add(thisDiff);
1141
1142 equalities.pop();
1143 lastequality = null;
1144 if (preIns && preDel) {
1145
1146 postIns = postDel = true;
1147 equalities.clear();
1148 safeDiff = thisDiff;
1149 } else {
1150 if (!equalities.empty()) {
1151
1152 equalities.pop();
1153 }
1154 if (equalities.empty()) {
1155
1156
1157 thisDiff = safeDiff;
1158 } else {
1159
1160 thisDiff = equalities.lastElement();
1161 }
1162 while (thisDiff != pointer.previous()) {
1163
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
1181
1182
1183
1184 public void diffCleanupMerge(LinkedList<Diff> diffs) {
1185
1186 diffs.add(new Diff(Operation.EQUAL, StringUtils.EMPTY));
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
1212 pointer.previous();
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
1224 commonlength = this.diffCommonPrefix(textInsert.toString(), textDelete.toString());
1225 if (commonlength != 0) {
1226 if (pointer.hasPrevious()) {
1227 thisDiff = pointer.previous();
1228
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
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
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
1264 thisDiff = pointer.hasNext() ? pointer.next() : null;
1265 } else if (prevEqual != null) {
1266
1267 prevEqual.setText(prevEqual.getText() + thisDiff.getText());
1268 pointer.remove();
1269 thisDiff = pointer.previous();
1270 pointer.next();
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();
1283 }
1284
1285
1286
1287
1288
1289
1290 boolean changes = false;
1291
1292
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
1299 while (nextDiff != null) {
1300 if (prevDiff.getOperation() == Operation.EQUAL &&
1301 nextDiff.getOperation() == Operation.EQUAL) {
1302
1303 if (thisDiff.getText().endsWith(prevDiff.getText())) {
1304
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();
1310 pointer.previous();
1311 pointer.previous();
1312 pointer.remove();
1313 pointer.next();
1314 thisDiff = pointer.next();
1315 nextDiff = pointer.hasNext() ? pointer.next() : null;
1316 changes = true;
1317 } else if (thisDiff.getText().startsWith(nextDiff.getText())) {
1318
1319 prevDiff.setText(prevDiff.getText() + nextDiff.getText());
1320 thisDiff.setText(thisDiff.getText().substring(nextDiff.getText().length())
1321 + nextDiff.getText());
1322 pointer.remove();
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
1332 if (changes) {
1333 this.diffCleanupMerge(diffs);
1334 }
1335 }
1336
1337
1338
1339
1340
1341
1342
1343
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
1355 chars1 += aDiff.getText().length();
1356 }
1357 if (aDiff.getOperation() != Operation.DELETE) {
1358
1359 chars2 += aDiff.getText().length();
1360 }
1361 if (chars1 > loc) {
1362
1363 lastDiff = aDiff;
1364 break;
1365 }
1366 lastChars1 = chars1;
1367 lastChars2 = chars2;
1368 }
1369 if (lastDiff != null && lastDiff.getOperation() == Operation.DELETE) {
1370
1371 return lastChars2;
1372 }
1373
1374 return lastChars2 + (loc - lastChars1);
1375 }
1376
1377
1378
1379
1380
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("&", "&").replace("<", "<")
1387 .replace(">", ">").replace("\n", "¶<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
1407
1408
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
1423
1424
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
1439
1440
1441
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
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
1470
1471
1472
1473
1474
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
1496 delta = delta.substring(0, delta.length() - 1);
1497 delta = Patch.unescapeForEncodeUriCompatability(delta);
1498 }
1499 return delta;
1500 }
1501
1502
1503
1504
1505
1506
1507
1508
1509 public List<Diff> diffFromDelta(String text1, String delta) {
1510
1511 List<Diff> diffs = new LinkedList<>();
1512 int pointer = 0;
1513 String[] tokens = delta.split("\t");
1514
1515 for (String token : tokens) {
1516 if (token.isEmpty()) {
1517
1518 continue;
1519 }
1520
1521
1522 String param = token.substring(1);
1523 switch (token.charAt(0)) {
1524 case '+':
1525
1526 param = param.replace("+", "%2B");
1527 try {
1528 param = URLDecoder.decode(param, StandardCharsets.UTF_8);
1529 } catch (IllegalArgumentException e) {
1530
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
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
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
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591 public int matchMain(String text, String pattern, int valueLoc) {
1592
1593
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
1601 return 0;
1602 } else if (text.isEmpty()) {
1603
1604 return -1;
1605 } else if (loc + pattern.length() <= text.length()
1606 && text.substring(loc, loc + pattern.length()).equals(pattern)) {
1607
1608 return loc;
1609 } else {
1610
1611 return this.matchBitap(text, pattern, loc);
1612 }
1613 }
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623 protected int matchBitap(String text, String pattern, int loc) {
1624
1625
1626 Map<Character, Integer> s = this.matchAlphabet(pattern);
1627
1628
1629 double scoreThreshold = DiffMatchPatch.MATCH_THRESHOLD;
1630
1631 int bestLoc = text.indexOf(pattern, loc);
1632 if (bestLoc != -1) {
1633 scoreThreshold = Math.min(this.matchBitapScore(0, bestLoc, loc, pattern),
1634 scoreThreshold);
1635
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
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
1651 int[] lastRd = new int[0];
1652 for (int d = 0; d < pattern.length(); d++) {
1653
1654
1655
1656
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
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
1680 charMatch = 0;
1681 } else {
1682 charMatch = s.get(text.charAt(j - 1));
1683 }
1684 if (d == 0) {
1685
1686 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
1687 } else {
1688
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
1696
1697 if (score <= scoreThreshold) {
1698
1699 scoreThreshold = score;
1700 bestLoc = j - 1;
1701 if (bestLoc > loc) {
1702
1703 start = Math.max(1, 2 * loc - bestLoc);
1704 } else {
1705
1706 break;
1707 }
1708 }
1709 }
1710 }
1711 if (this.matchBitapScore(d + 1, loc, loc, pattern) > scoreThreshold) {
1712
1713 break;
1714 }
1715 lastRd = rd;
1716 }
1717 return bestLoc;
1718 }
1719
1720
1721
1722
1723
1724
1725
1726
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
1737
1738
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
1757
1758
1759
1760
1761
1762
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
1773
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
1781 padding += DiffMatchPatch.PATCH_MARGIN;
1782
1783
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
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
1797 patch.setStart1(patch.getStart1() - prefix.length());
1798 patch.setStart2(patch.getStart2() - prefix.length());
1799
1800 patch.setLength1(patch.getLength1() + prefix.length() + suffix.length());
1801 patch.setLength2(patch.getLength2() + prefix.length() + suffix.length());
1802 }
1803
1804
1805
1806
1807
1808
1809
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
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
1827
1828
1829
1830
1831 public List<Patch> patchMake(LinkedList<Diff> diffs) {
1832
1833 if (diffs == null) {
1834 throw new IllegalArgumentException("Null inputs. (patch_make)");
1835 }
1836
1837 String text1 = this.diffText1(diffs);
1838 return this.patchMake(text1, diffs);
1839 }
1840
1841
1842
1843
1844
1845
1846
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;
1857 }
1858 Patch patch = new Patch();
1859 int charCount1 = 0;
1860 int charCount2 = 0;
1861
1862
1863
1864 String prepatchText = text1;
1865 String postpatchText = text1;
1866
1867 for (Diff aDiff : diffs) {
1868 if (patch.getDiffs().isEmpty() && aDiff.getOperation() != Operation.EQUAL) {
1869
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
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
1903 this.patchAddContext(patch, prepatchText);
1904 patches.add(patch);
1905 patch = new Patch();
1906
1907
1908
1909
1910 prepatchText = postpatchText;
1911 charCount1 = charCount2;
1912 }
1913 break;
1914 }
1915
1916
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
1925 if (!patch.getDiffs().isEmpty()) {
1926 this.patchAddContext(patch, prepatchText);
1927 patches.add(patch);
1928 }
1929
1930 return patches;
1931 }
1932
1933
1934
1935
1936
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
1958
1959
1960
1961
1962
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
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
1979
1980
1981
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
1993
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
2002 startLoc = -1;
2003 }
2004 }
2005 } else {
2006 startLoc = this.matchMain(text, text1, expectedLoc);
2007 }
2008
2009 if (startLoc == -1) {
2010
2011 results[x] = false;
2012
2013 delta -= aPatch.getLength2() - aPatch.getLength1();
2014 } else {
2015
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
2029 text = text.substring(0, startLoc) + this.diffText2(aPatch.getDiffs())
2030 + text.substring(startLoc + text1.length());
2031 } else {
2032
2033
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
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
2048 text = text.substring(0, startLoc + index2) + aDiff.getText()
2049 + text.substring(startLoc + index2);
2050 } else if (aDiff.getOperation() == Operation.DELETE) {
2051
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
2067 text = text.substring(nullPadding.length(), text.length()
2068 - nullPadding.length());
2069 return new Object[]{text, results};
2070 }
2071
2072
2073
2074
2075
2076
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
2087 for (Patch aPatch : patches) {
2088 aPatch.setStart1(aPatch.getStart1() + paddingLength);
2089 aPatch.setStart2(aPatch.getStart2() + paddingLength);
2090 }
2091
2092
2093 Patch patch = patches.getFirst();
2094 Deque<Diff> diffs = patch.getDiffs();
2095 if (diffs.isEmpty() || diffs.getFirst().getOperation() != Operation.EQUAL) {
2096
2097 diffs.addFirst(new Diff(Operation.EQUAL, nullPadding.toString()));
2098 patch.setStart1(patch.getStart1() - paddingLength);
2099 patch.setStart2(patch.getStart2() - paddingLength);
2100 patch.setLength1(patch.getLength1() + paddingLength);
2101 patch.setLength2(patch.getLength2() + paddingLength);
2102 } else if (paddingLength > diffs.getFirst().getText().length()) {
2103
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
2115 patch = patches.getLast();
2116 diffs = patch.getDiffs();
2117 if (diffs.isEmpty() || diffs.getLast().getOperation() != Operation.EQUAL) {
2118
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
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
2136
2137
2138
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
2160 pointer.remove();
2161 start1 = bigpatch.getStart1();
2162 start2 = bigpatch.getStart2();
2163 precontext = StringUtils.EMPTY;
2164
2165 while (!bigpatch.getDiffs().isEmpty()) {
2166
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
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
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
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
2217 precontext = this.diffText2(patch.getDiffs());
2218 precontext = precontext.substring(Math.max(0, precontext.length()
2219 - DiffMatchPatch.PATCH_MARGIN));
2220
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
2248
2249
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
2262
2263
2264
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
2320 text.removeFirst();
2321 continue;
2322 }
2323
2324 line = text.getFirst().substring(1);
2325 line = line.replace("+", "%2B");
2326 try {
2327 line = URLDecoder.decode(line, StandardCharsets.UTF_8);
2328 } catch (IllegalArgumentException e) {
2329
2330 throw new IllegalArgumentException(
2331 "Illegal escape in patch_fromText: " + line, e);
2332 }
2333
2334 if (sign == '-') {
2335
2336 patch.getDiffs().add(new Diff(Operation.DELETE, line));
2337 } else if (sign == '+') {
2338
2339 patch.getDiffs().add(new Diff(Operation.INSERT, line));
2340 } else if (sign == ' ') {
2341
2342 patch.getDiffs().add(new Diff(Operation.EQUAL, line));
2343 } else if (sign == '@') {
2344
2345 break;
2346 } else {
2347
2348 throw new IllegalArgumentException(
2349 "Invalid patch mode '" + sign + "' in: " + line);
2350 }
2351 text.removeFirst();
2352 }
2353 }
2354 return patches;
2355 }
2356 }