1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.ximtec.igesture.util;
21
22 import java.io.UnsupportedEncodingException;
23 import java.net.URLEncoder;
24 import java.net.URLDecoder;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.LinkedList;
30 import java.util.List;
31 import java.util.ListIterator;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.Stack;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 public class diff_match_patch {
52
53
54
55
56
57
58
59 public float Diff_Timeout = 1.0f;
60
61
62
63 public short Diff_EditCost = 4;
64
65
66
67
68 public short Diff_DualThreshold = 32;
69
70
71
72 public float Match_Threshold = 0.5f;
73
74
75
76
77
78 public int Match_Distance = 1000;
79
80
81
82
83
84
85 public float Patch_DeleteThreshold = 0.5f;
86
87
88
89 public short Patch_Margin = 4;
90
91
92
93
94 private int Match_MaxBits = 32;
95
96
97
98
99
100 protected static class LinesToCharsResult {
101 protected String chars1;
102 protected String chars2;
103 protected List<String> lineArray;
104
105 protected LinesToCharsResult(String chars1, String chars2,
106 List<String> lineArray) {
107 this.chars1 = chars1;
108 this.chars2 = chars2;
109 this.lineArray = lineArray;
110 }
111 }
112
113
114
115
116
117
118
119
120
121
122
123 public enum Operation {
124 DELETE, INSERT, EQUAL
125 }
126
127
128
129
130
131
132
133
134
135
136
137 public LinkedList<Diff> diff_main(String text1, String text2) {
138 return diff_main(text1, text2, true);
139 }
140
141
142
143
144
145
146
147
148
149
150
151 public LinkedList<Diff> diff_main(String text1, String text2,
152 boolean checklines) {
153
154 LinkedList<Diff> diffs;
155 if (text1.equals(text2)) {
156 diffs = new LinkedList<Diff>();
157 diffs.add(new Diff(Operation.EQUAL, text1));
158 return diffs;
159 }
160
161
162 int commonlength = diff_commonPrefix(text1, text2);
163 String commonprefix = text1.substring(0, commonlength);
164 text1 = text1.substring(commonlength);
165 text2 = text2.substring(commonlength);
166
167
168 commonlength = diff_commonSuffix(text1, text2);
169 String commonsuffix = text1.substring(text1.length() - commonlength);
170 text1 = text1.substring(0, text1.length() - commonlength);
171 text2 = text2.substring(0, text2.length() - commonlength);
172
173
174 diffs = diff_compute(text1, text2, checklines);
175
176
177 if (commonprefix.length() != 0) {
178 diffs.addFirst(new Diff(Operation.EQUAL, commonprefix));
179 }
180 if (commonsuffix.length() != 0) {
181 diffs.addLast(new Diff(Operation.EQUAL, commonsuffix));
182 }
183
184 diff_cleanupMerge(diffs);
185 return diffs;
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199 protected LinkedList<Diff> diff_compute(String text1, String text2,
200 boolean checklines) {
201 LinkedList<Diff> diffs = new LinkedList<Diff>();
202
203 if (text1.length() == 0) {
204
205 diffs.add(new Diff(Operation.INSERT, text2));
206 return diffs;
207 }
208
209 if (text2.length() == 0) {
210
211 diffs.add(new Diff(Operation.DELETE, text1));
212 return diffs;
213 }
214
215 String longtext = text1.length() > text2.length() ? text1 : text2;
216 String shorttext = text1.length() > text2.length() ? text2 : text1;
217 int i = longtext.indexOf(shorttext);
218 if (i != -1) {
219
220 Operation op = (text1.length() > text2.length()) ?
221 Operation.DELETE : Operation.INSERT;
222 diffs.add(new Diff(op, longtext.substring(0, i)));
223 diffs.add(new Diff(Operation.EQUAL, shorttext));
224 diffs.add(new Diff(op, longtext.substring(i + shorttext.length())));
225 return diffs;
226 }
227 longtext = shorttext = null;
228
229
230 String[] hm = diff_halfMatch(text1, text2);
231 if (hm != null) {
232
233 String text1_a = hm[0];
234 String text1_b = hm[1];
235 String text2_a = hm[2];
236 String text2_b = hm[3];
237 String mid_common = hm[4];
238
239 LinkedList<Diff> diffs_a = diff_main(text1_a, text2_a, checklines);
240 LinkedList<Diff> diffs_b = diff_main(text1_b, text2_b, checklines);
241
242 diffs = diffs_a;
243 diffs.add(new Diff(Operation.EQUAL, mid_common));
244 diffs.addAll(diffs_b);
245 return diffs;
246 }
247
248
249 if (checklines && (text1.length() < 100 || text2.length() < 100)) {
250 checklines = false;
251 }
252 List<String> linearray = null;
253 if (checklines) {
254
255 LinesToCharsResult b = diff_linesToChars(text1, text2);
256 text1 = b.chars1;
257 text2 = b.chars2;
258 linearray = b.lineArray;
259 }
260
261 diffs = diff_map(text1, text2);
262 if (diffs == null) {
263
264 diffs = new LinkedList<Diff>();
265 diffs.add(new Diff(Operation.DELETE, text1));
266 diffs.add(new Diff(Operation.INSERT, text2));
267 }
268
269 if (checklines) {
270
271 diff_charsToLines(diffs, linearray);
272
273 diff_cleanupSemantic(diffs);
274
275
276
277 diffs.add(new Diff(Operation.EQUAL, ""));
278 int count_delete = 0;
279 int count_insert = 0;
280 String text_delete = "";
281 String text_insert = "";
282 ListIterator<Diff> pointer = diffs.listIterator();
283 Diff thisDiff = pointer.next();
284 while (thisDiff != null) {
285 switch (thisDiff.operation) {
286 case INSERT:
287 count_insert++;
288 text_insert += thisDiff.text;
289 break;
290 case DELETE:
291 count_delete++;
292 text_delete += thisDiff.text;
293 break;
294 case EQUAL:
295
296 if (count_delete >= 1 && count_insert >= 1) {
297
298 pointer.previous();
299 for (int j = 0; j < count_delete + count_insert; j++) {
300 pointer.previous();
301 pointer.remove();
302 }
303 for (Diff newDiff : diff_main(text_delete, text_insert, false)) {
304 pointer.add(newDiff);
305 }
306 }
307 count_insert = 0;
308 count_delete = 0;
309 text_delete = "";
310 text_insert = "";
311 break;
312 }
313 thisDiff = pointer.hasNext() ? pointer.next() : null;
314 }
315 diffs.removeLast();
316 }
317 return diffs;
318 }
319
320
321
322
323
324
325
326
327
328
329
330 protected LinesToCharsResult diff_linesToChars(String text1, String text2) {
331 List<String> lineArray = new ArrayList<String>();
332 Map<String, Integer> lineHash = new HashMap<String, Integer>();
333
334
335
336
337
338 lineArray.add("");
339
340 String chars1 = diff_linesToCharsMunge(text1, lineArray, lineHash);
341 String chars2 = diff_linesToCharsMunge(text2, lineArray, lineHash);
342 return new LinesToCharsResult(chars1, chars2, lineArray);
343 }
344
345
346
347
348
349
350
351
352
353
354 private String diff_linesToCharsMunge(String text, List<String> lineArray,
355 Map<String, Integer> lineHash) {
356 int lineStart = 0;
357 int lineEnd = -1;
358 String line;
359 StringBuilder chars = new StringBuilder();
360
361
362
363 while (lineEnd < text.length() - 1) {
364 lineEnd = text.indexOf('\n', lineStart);
365 if (lineEnd == -1) {
366 lineEnd = text.length() - 1;
367 }
368 line = text.substring(lineStart, lineEnd + 1);
369 lineStart = lineEnd + 1;
370
371 if (lineHash.containsKey(line)) {
372 chars.append(String.valueOf((char) (int) lineHash.get(line)));
373 } else {
374 lineArray.add(line);
375 lineHash.put(line, lineArray.size() - 1);
376 chars.append(String.valueOf((char) (lineArray.size() - 1)));
377 }
378 }
379 return chars.toString();
380 }
381
382
383
384
385
386
387
388
389 protected void diff_charsToLines(LinkedList<Diff> diffs,
390 List<String> lineArray) {
391 StringBuilder text;
392 for (Diff diff : diffs) {
393 text = new StringBuilder();
394 for (int y = 0; y < diff.text.length(); y++) {
395 text.append(lineArray.get(diff.text.charAt(y)));
396 }
397 diff.text = text.toString();
398 }
399 }
400
401
402
403
404
405
406
407
408 protected LinkedList<Diff> diff_map(String text1, String text2) {
409 long ms_end = System.currentTimeMillis() + (long) (Diff_Timeout * 1000);
410
411 int text1_length = text1.length();
412 int text2_length = text2.length();
413 int max_d = text1_length + text2_length - 1;
414 boolean doubleEnd = Diff_DualThreshold * 2 < max_d;
415 List<Set<Long>> v_map1 = new ArrayList<Set<Long>>();
416 List<Set<Long>> v_map2 = new ArrayList<Set<Long>>();
417 Map<Integer, Integer> v1 = new HashMap<Integer, Integer>();
418 Map<Integer, Integer> v2 = new HashMap<Integer, Integer>();
419 v1.put(1, 0);
420 v2.put(1, 0);
421 int x, y;
422 Long footstep = 0L;
423 Map<Long, Integer> footsteps = new HashMap<Long, Integer>();
424 boolean done = false;
425
426
427 boolean front = ((text1_length + text2_length) % 2 == 1);
428 for (int d = 0; d < max_d; d++) {
429
430 if (Diff_Timeout > 0 && System.currentTimeMillis() > ms_end) {
431 return null;
432 }
433
434
435 v_map1.add(new HashSet<Long>());
436 for (int k = -d; k <= d; k += 2) {
437 if (k == -d || k != d && v1.get(k - 1) < v1.get(k + 1)) {
438 x = v1.get(k + 1);
439 } else {
440 x = v1.get(k - 1) + 1;
441 }
442 y = x - k;
443 if (doubleEnd) {
444 footstep = diff_footprint(x, y);
445 if (front && (footsteps.containsKey(footstep))) {
446 done = true;
447 }
448 if (!front) {
449 footsteps.put(footstep, d);
450 }
451 }
452 while (!done && x < text1_length && y < text2_length
453 && text1.charAt(x) == text2.charAt(y)) {
454 x++;
455 y++;
456 if (doubleEnd) {
457 footstep = diff_footprint(x, y);
458 if (front && (footsteps.containsKey(footstep))) {
459 done = true;
460 }
461 if (!front) {
462 footsteps.put(footstep, d);
463 }
464 }
465 }
466 v1.put(k, x);
467 v_map1.get(d).add(diff_footprint(x, y));
468 if (x == text1_length && y == text2_length) {
469
470 return diff_path1(v_map1, text1, text2);
471 } else if (done) {
472
473 v_map2 = v_map2.subList(0, footsteps.get(footstep) + 1);
474 LinkedList<Diff> a = diff_path1(v_map1, text1.substring(0, x),
475 text2.substring(0, y));
476 a.addAll(diff_path2(v_map2, text1.substring(x), text2.substring(y)));
477 return a;
478 }
479 }
480
481 if (doubleEnd) {
482
483 v_map2.add(new HashSet<Long>());
484 for (int k = -d; k <= d; k += 2) {
485 if (k == -d || k != d && v2.get(k - 1) < v2.get(k + 1)) {
486 x = v2.get(k + 1);
487 } else {
488 x = v2.get(k - 1) + 1;
489 }
490 y = x - k;
491 footstep = diff_footprint(text1_length - x, text2_length - y);
492 if (!front && (footsteps.containsKey(footstep))) {
493 done = true;
494 }
495 if (front) {
496 footsteps.put(footstep, d);
497 }
498 while (!done && x < text1_length && y < text2_length
499 && text1.charAt(text1_length - x - 1)
500 == text2.charAt(text2_length - y - 1)) {
501 x++;
502 y++;
503 footstep = diff_footprint(text1_length - x, text2_length - y);
504 if (!front && (footsteps.containsKey(footstep))) {
505 done = true;
506 }
507 if (front) {
508 footsteps.put(footstep, d);
509 }
510 }
511 v2.put(k, x);
512 v_map2.get(d).add(diff_footprint(x, y));
513 if (done) {
514
515 v_map1 = v_map1.subList(0, footsteps.get(footstep) + 1);
516 LinkedList<Diff> a
517 = diff_path1(v_map1, text1.substring(0, text1_length - x),
518 text2.substring(0, text2_length - y));
519 a.addAll(diff_path2(v_map2, text1.substring(text1_length - x),
520 text2.substring(text2_length - y)));
521 return a;
522 }
523 }
524 }
525 }
526
527 return null;
528 }
529
530
531
532
533
534
535
536
537
538 protected LinkedList<Diff> diff_path1(List<Set<Long>> v_map,
539 String text1, String text2) {
540 LinkedList<Diff> path = new LinkedList<Diff>();
541 int x = text1.length();
542 int y = text2.length();
543 Operation last_op = null;
544 for (int d = v_map.size() - 2; d >= 0; d--) {
545 while (true) {
546 if (v_map.get(d).contains(diff_footprint(x - 1, y))) {
547 x--;
548 if (last_op == Operation.DELETE) {
549 path.getFirst().text = text1.charAt(x) + path.getFirst().text;
550 } else {
551 path.addFirst(new Diff(Operation.DELETE,
552 text1.substring(x, x + 1)));
553 }
554 last_op = Operation.DELETE;
555 break;
556 } else if (v_map.get(d).contains(diff_footprint(x, y - 1))) {
557 y--;
558 if (last_op == Operation.INSERT) {
559 path.getFirst().text = text2.charAt(y) + path.getFirst().text;
560 } else {
561 path.addFirst(new Diff(Operation.INSERT,
562 text2.substring(y, y + 1)));
563 }
564 last_op = Operation.INSERT;
565 break;
566 } else {
567 x--;
568 y--;
569 assert (text1.charAt(x) == text2.charAt(y))
570 : "No diagonal. Can't happen. (diff_path1)";
571 if (last_op == Operation.EQUAL) {
572 path.getFirst().text = text1.charAt(x) + path.getFirst().text;
573 } else {
574 path.addFirst(new Diff(Operation.EQUAL, text1.substring(x, x + 1)));
575 }
576 last_op = Operation.EQUAL;
577 }
578 }
579 }
580 return path;
581 }
582
583
584
585
586
587
588
589
590
591 protected LinkedList<Diff> diff_path2(List<Set<Long>> v_map,
592 String text1, String text2) {
593 LinkedList<Diff> path = new LinkedList<Diff>();
594 int x = text1.length();
595 int y = text2.length();
596 Operation last_op = null;
597 for (int d = v_map.size() - 2; d >= 0; d--) {
598 while (true) {
599 if (v_map.get(d).contains(diff_footprint(x - 1, y))) {
600 x--;
601 if (last_op == Operation.DELETE) {
602 path.getLast().text += text1.charAt(text1.length() - x - 1);
603 } else {
604 path.addLast(new Diff(Operation.DELETE,
605 text1.substring(text1.length() - x - 1, text1.length() - x)));
606 }
607 last_op = Operation.DELETE;
608 break;
609 } else if (v_map.get(d).contains(diff_footprint(x, y - 1))) {
610 y--;
611 if (last_op == Operation.INSERT) {
612 path.getLast().text += text2.charAt(text2.length() - y - 1);
613 } else {
614 path.addLast(new Diff(Operation.INSERT,
615 text2.substring(text2.length() - y - 1, text2.length() - y)));
616 }
617 last_op = Operation.INSERT;
618 break;
619 } else {
620 x--;
621 y--;
622 assert (text1.charAt(text1.length() - x - 1)
623 == text2.charAt(text2.length() - y - 1))
624 : "No diagonal. Can't happen. (diff_path2)";
625 if (last_op == Operation.EQUAL) {
626 path.getLast().text += text1.charAt(text1.length() - x - 1);
627 } else {
628 path.addLast(new Diff(Operation.EQUAL,
629 text1.substring(text1.length() - x - 1, text1.length() - x)));
630 }
631 last_op = Operation.EQUAL;
632 }
633 }
634 }
635 return path;
636 }
637
638
639
640
641
642
643
644
645 protected long diff_footprint(int x, int y) {
646
647
648
649 long result = x;
650 result = result << 32;
651 result += y;
652 return result;
653 }
654
655
656
657
658
659
660
661
662 public int diff_commonPrefix(String text1, String text2) {
663
664 int n = Math.min(text1.length(), text2.length());
665 for (int i = 0; i < n; i++) {
666 if (text1.charAt(i) != text2.charAt(i)) {
667 return i;
668 }
669 }
670 return n;
671 }
672
673
674
675
676
677
678
679
680 public int diff_commonSuffix(String text1, String text2) {
681
682 int text1_length = text1.length();
683 int text2_length = text2.length();
684 int n = Math.min(text1_length, text2_length);
685 for (int i = 1; i <= n; i++) {
686 if (text1.charAt(text1_length - i) != text2.charAt(text2_length - i)) {
687 return i - 1;
688 }
689 }
690 return n;
691 }
692
693
694
695
696
697
698
699
700
701
702
703 protected String[] diff_halfMatch(String text1, String text2) {
704 String longtext = text1.length() > text2.length() ? text1 : text2;
705 String shorttext = text1.length() > text2.length() ? text2 : text1;
706 if (longtext.length() < 10 || shorttext.length() < 1) {
707 return null;
708 }
709
710
711 String[] hm1 = diff_halfMatchI(longtext, shorttext,
712 (longtext.length() + 3) / 4);
713
714 String[] hm2 = diff_halfMatchI(longtext, shorttext,
715 (longtext.length() + 1) / 2);
716 String[] hm;
717 if (hm1 == null && hm2 == null) {
718 return null;
719 } else if (hm2 == null) {
720 hm = hm1;
721 } else if (hm1 == null) {
722 hm = hm2;
723 } else {
724
725 hm = hm1[4].length() > hm2[4].length() ? hm1 : hm2;
726 }
727
728
729 if (text1.length() > text2.length()) {
730 return hm;
731
732 } else {
733 return new String[]{hm[2], hm[3], hm[0], hm[1], hm[4]};
734 }
735 }
736
737
738
739
740
741
742
743
744
745
746
747
748 private String[] diff_halfMatchI(String longtext, String shorttext, int i) {
749
750 String seed = longtext.substring(i, i + longtext.length() / 4);
751 int j = -1;
752 String best_common = "";
753 String best_longtext_a = "", best_longtext_b = "";
754 String best_shorttext_a = "", best_shorttext_b = "";
755 while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
756 int prefixLength = diff_commonPrefix(longtext.substring(i),
757 shorttext.substring(j));
758 int suffixLength = diff_commonSuffix(longtext.substring(0, i),
759 shorttext.substring(0, j));
760 if (best_common.length() < suffixLength + prefixLength) {
761 best_common = shorttext.substring(j - suffixLength, j)
762 + shorttext.substring(j, j + prefixLength);
763 best_longtext_a = longtext.substring(0, i - suffixLength);
764 best_longtext_b = longtext.substring(i + prefixLength);
765 best_shorttext_a = shorttext.substring(0, j - suffixLength);
766 best_shorttext_b = shorttext.substring(j + prefixLength);
767 }
768 }
769 if (best_common.length() >= longtext.length() / 2) {
770 return new String[]{best_longtext_a, best_longtext_b,
771 best_shorttext_a, best_shorttext_b, best_common};
772 } else {
773 return null;
774 }
775 }
776
777
778
779
780
781
782 public void diff_cleanupSemantic(LinkedList<Diff> diffs) {
783 if (diffs.isEmpty()) {
784 return;
785 }
786 boolean changes = false;
787 Stack<Diff> equalities = new Stack<Diff>();
788 String lastequality = null;
789 ListIterator<Diff> pointer = diffs.listIterator();
790
791 int length_changes1 = 0;
792
793 int length_changes2 = 0;
794 Diff thisDiff = pointer.next();
795 while (thisDiff != null) {
796 if (thisDiff.operation == Operation.EQUAL) {
797
798 equalities.push(thisDiff);
799 length_changes1 = length_changes2;
800 length_changes2 = 0;
801 lastequality = thisDiff.text;
802 } else {
803
804 length_changes2 += thisDiff.text.length();
805 if (lastequality != null && (lastequality.length() <= length_changes1)
806 && (lastequality.length() <= length_changes2)) {
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 length_changes1 = 0;
838 length_changes2 = 0;
839 lastequality = null;
840 changes = true;
841 }
842 }
843 thisDiff = pointer.hasNext() ? pointer.next() : null;
844 }
845
846 if (changes) {
847 diff_cleanupMerge(diffs);
848 }
849 diff_cleanupSemanticLossless(diffs);
850 }
851
852
853
854
855
856
857
858
859 public void diff_cleanupSemanticLossless(LinkedList<Diff> diffs) {
860 String equality1, edit, equality2;
861 String commonString;
862 int commonOffset;
863 int score, bestScore;
864 String bestEquality1, bestEdit, bestEquality2;
865
866 ListIterator<Diff> pointer = diffs.listIterator();
867 Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
868 Diff thisDiff = pointer.hasNext() ? pointer.next() : null;
869 Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
870
871 while (nextDiff != null) {
872 if (prevDiff.operation == Operation.EQUAL &&
873 nextDiff.operation == Operation.EQUAL) {
874
875 equality1 = prevDiff.text;
876 edit = thisDiff.text;
877 equality2 = nextDiff.text;
878
879
880 commonOffset = diff_commonSuffix(equality1, edit);
881 if (commonOffset != 0) {
882 commonString = edit.substring(edit.length() - commonOffset);
883 equality1 = equality1.substring(0, equality1.length() - commonOffset);
884 edit = commonString + edit.substring(0, edit.length() - commonOffset);
885 equality2 = commonString + equality2;
886 }
887
888
889 bestEquality1 = equality1;
890 bestEdit = edit;
891 bestEquality2 = equality2;
892 bestScore = diff_cleanupSemanticScore(equality1, edit)
893 + diff_cleanupSemanticScore(edit, equality2);
894 while (edit.length() != 0 && equality2.length() != 0
895 && edit.charAt(0) == equality2.charAt(0)) {
896 equality1 += edit.charAt(0);
897 edit = edit.substring(1) + equality2.charAt(0);
898 equality2 = equality2.substring(1);
899 score = diff_cleanupSemanticScore(equality1, edit)
900 + diff_cleanupSemanticScore(edit, equality2);
901
902 if (score >= bestScore) {
903 bestScore = score;
904 bestEquality1 = equality1;
905 bestEdit = edit;
906 bestEquality2 = equality2;
907 }
908 }
909
910 if (!prevDiff.text.equals(bestEquality1)) {
911
912 if (bestEquality1.length() != 0) {
913 prevDiff.text = bestEquality1;
914 } else {
915 pointer.previous();
916 pointer.previous();
917 pointer.previous();
918 pointer.remove();
919 pointer.next();
920 pointer.next();
921 }
922 thisDiff.text = bestEdit;
923 if (bestEquality2.length() != 0) {
924 nextDiff.text = bestEquality2;
925 } else {
926 pointer.remove();
927 nextDiff = thisDiff;
928 thisDiff = prevDiff;
929 }
930 }
931 }
932 prevDiff = thisDiff;
933 thisDiff = nextDiff;
934 nextDiff = pointer.hasNext() ? pointer.next() : null;
935 }
936 }
937
938
939
940
941
942
943
944
945
946
947 private int diff_cleanupSemanticScore(String one, String two) {
948 if (one.length() == 0 || two.length() == 0) {
949
950 return 5;
951 }
952
953
954
955
956
957
958 int score = 0;
959
960 if (!Character.isLetterOrDigit(one.charAt(one.length() - 1))
961 || !Character.isLetterOrDigit(two.charAt(0))) {
962 score++;
963
964 if (Character.isWhitespace(one.charAt(one.length() - 1))
965 || Character.isWhitespace(two.charAt(0))) {
966 score++;
967
968 if (Character.getType(one.charAt(one.length() - 1)) == Character.CONTROL
969 || Character.getType(two.charAt(0)) == Character.CONTROL) {
970 score++;
971
972 if (BLANKLINEEND.matcher(one).find()
973 || BLANKLINESTART.matcher(two).find()) {
974 score++;
975 }
976 }
977 }
978 }
979 return score;
980 }
981
982
983 private Pattern BLANKLINEEND
984 = Pattern.compile("\\n\\r?\\n\\Z", Pattern.DOTALL);
985 private Pattern BLANKLINESTART
986 = Pattern.compile("\\A\\r?\\n\\r?\\n", Pattern.DOTALL);
987
988
989
990
991
992
993 public void diff_cleanupEfficiency(LinkedList<Diff> diffs) {
994 if (diffs.isEmpty()) {
995 return;
996 }
997 boolean changes = false;
998 Stack<Diff> equalities = new Stack<Diff>();
999 String lastequality = null;
1000 ListIterator<Diff> pointer = diffs.listIterator();
1001
1002 boolean pre_ins = false;
1003
1004 boolean pre_del = false;
1005
1006 boolean post_ins = false;
1007
1008 boolean post_del = false;
1009 Diff thisDiff = pointer.next();
1010 Diff safeDiff = thisDiff;
1011 while (thisDiff != null) {
1012 if (thisDiff.operation == Operation.EQUAL) {
1013
1014 if (thisDiff.text.length() < Diff_EditCost && (post_ins || post_del)) {
1015
1016 equalities.push(thisDiff);
1017 pre_ins = post_ins;
1018 pre_del = post_del;
1019 lastequality = thisDiff.text;
1020 } else {
1021
1022 equalities.clear();
1023 lastequality = null;
1024 safeDiff = thisDiff;
1025 }
1026 post_ins = post_del = false;
1027 } else {
1028
1029 if (thisDiff.operation == Operation.DELETE) {
1030 post_del = true;
1031 } else {
1032 post_ins = true;
1033 }
1034
1035
1036
1037
1038
1039
1040
1041
1042 if (lastequality != null
1043 && ((pre_ins && pre_del && post_ins && post_del)
1044 || ((lastequality.length() < Diff_EditCost / 2)
1045 && ((pre_ins ? 1 : 0) + (pre_del ? 1 : 0)
1046 + (post_ins ? 1 : 0) + (post_del ? 1 : 0)) == 3))) {
1047
1048
1049 while (thisDiff != equalities.lastElement()) {
1050 thisDiff = pointer.previous();
1051 }
1052 pointer.next();
1053
1054
1055 pointer.set(new Diff(Operation.DELETE, lastequality));
1056
1057 pointer.add(thisDiff = new Diff(Operation.INSERT, lastequality));
1058
1059 equalities.pop();
1060 lastequality = null;
1061 if (pre_ins && pre_del) {
1062
1063 post_ins = post_del = true;
1064 equalities.clear();
1065 safeDiff = thisDiff;
1066 } else {
1067 if (!equalities.empty()) {
1068
1069 equalities.pop();
1070 }
1071 if (equalities.empty()) {
1072
1073
1074 thisDiff = safeDiff;
1075 } else {
1076
1077 thisDiff = equalities.lastElement();
1078 }
1079 while (thisDiff != pointer.previous()) {
1080
1081 }
1082 post_ins = post_del = false;
1083 }
1084
1085 changes = true;
1086 }
1087 }
1088 thisDiff = pointer.hasNext() ? pointer.next() : null;
1089 }
1090
1091 if (changes) {
1092 diff_cleanupMerge(diffs);
1093 }
1094 }
1095
1096
1097
1098
1099
1100
1101
1102 public void diff_cleanupMerge(LinkedList<Diff> diffs) {
1103 diffs.add(new Diff(Operation.EQUAL, ""));
1104 ListIterator<Diff> pointer = diffs.listIterator();
1105 int count_delete = 0;
1106 int count_insert = 0;
1107 String text_delete = "";
1108 String text_insert = "";
1109 Diff thisDiff = pointer.next();
1110 Diff prevEqual = null;
1111 int commonlength;
1112 while (thisDiff != null) {
1113 switch (thisDiff.operation) {
1114 case INSERT:
1115 count_insert++;
1116 text_insert += thisDiff.text;
1117 prevEqual = null;
1118 break;
1119 case DELETE:
1120 count_delete++;
1121 text_delete += thisDiff.text;
1122 prevEqual = null;
1123 break;
1124 case EQUAL:
1125 if (count_delete != 0 || count_insert != 0) {
1126
1127 pointer.previous();
1128 while (count_delete-- > 0) {
1129 pointer.previous();
1130 pointer.remove();
1131 }
1132 while (count_insert-- > 0) {
1133 pointer.previous();
1134 pointer.remove();
1135 }
1136 if (count_delete != 0 && count_insert != 0) {
1137
1138 commonlength = diff_commonPrefix(text_insert, text_delete);
1139 if (commonlength != 0) {
1140 if (pointer.hasPrevious()) {
1141 thisDiff = pointer.previous();
1142 assert thisDiff.operation == Operation.EQUAL
1143 : "Previous diff should have been an equality.";
1144 thisDiff.text += text_insert.substring(0, commonlength);
1145 pointer.next();
1146 } else {
1147 pointer.add(new Diff(Operation.EQUAL,
1148 text_insert.substring(0, commonlength)));
1149 }
1150 text_insert = text_insert.substring(commonlength);
1151 text_delete = text_delete.substring(commonlength);
1152 }
1153
1154 commonlength = diff_commonSuffix(text_insert, text_delete);
1155 if (commonlength != 0) {
1156 thisDiff = pointer.next();
1157 thisDiff.text = text_insert.substring(text_insert.length()
1158 - commonlength) + thisDiff.text;
1159 text_insert = text_insert.substring(0, text_insert.length()
1160 - commonlength);
1161 text_delete = text_delete.substring(0, text_delete.length()
1162 - commonlength);
1163 pointer.previous();
1164 }
1165 }
1166
1167 if (text_delete.length() != 0) {
1168 pointer.add(new Diff(Operation.DELETE, text_delete));
1169 }
1170 if (text_insert.length() != 0) {
1171 pointer.add(new Diff(Operation.INSERT, text_insert));
1172 }
1173
1174 thisDiff = pointer.hasNext() ? pointer.next() : null;
1175 } else if (prevEqual != null) {
1176
1177 prevEqual.text += thisDiff.text;
1178 pointer.remove();
1179 thisDiff = pointer.previous();
1180 pointer.next();
1181 }
1182 count_insert = 0;
1183 count_delete = 0;
1184 text_delete = "";
1185 text_insert = "";
1186 prevEqual = thisDiff;
1187 break;
1188 }
1189 thisDiff = pointer.hasNext() ? pointer.next() : null;
1190 }
1191
1192 if (diffs.getLast().text.length() == 0) {
1193 diffs.removeLast();
1194 }
1195
1196
1197
1198
1199
1200
1201 boolean changes = false;
1202
1203
1204 pointer = diffs.listIterator();
1205 Diff prevDiff = pointer.hasNext() ? pointer.next() : null;
1206 thisDiff = pointer.hasNext() ? pointer.next() : null;
1207 Diff nextDiff = pointer.hasNext() ? pointer.next() : null;
1208
1209 while (nextDiff != null) {
1210 if (prevDiff.operation == Operation.EQUAL &&
1211 nextDiff.operation == Operation.EQUAL) {
1212
1213 if (thisDiff.text.endsWith(prevDiff.text)) {
1214
1215 thisDiff.text = prevDiff.text
1216 + thisDiff.text.substring(0, thisDiff.text.length()
1217 - prevDiff.text.length());
1218 nextDiff.text = prevDiff.text + nextDiff.text;
1219 pointer.previous();
1220 pointer.previous();
1221 pointer.previous();
1222 pointer.remove();
1223 pointer.next();
1224 thisDiff = pointer.next();
1225 nextDiff = pointer.hasNext() ? pointer.next() : null;
1226 changes = true;
1227 } else if (thisDiff.text.startsWith(nextDiff.text)) {
1228
1229 prevDiff.text += nextDiff.text;
1230 thisDiff.text = thisDiff.text.substring(nextDiff.text.length())
1231 + nextDiff.text;
1232 pointer.remove();
1233 nextDiff = pointer.hasNext() ? pointer.next() : null;
1234 changes = true;
1235 }
1236 }
1237 prevDiff = thisDiff;
1238 thisDiff = nextDiff;
1239 nextDiff = pointer.hasNext() ? pointer.next() : null;
1240 }
1241
1242 if (changes) {
1243 diff_cleanupMerge(diffs);
1244 }
1245 }
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256 public int diff_xIndex(LinkedList<Diff> diffs, int loc) {
1257 int chars1 = 0;
1258 int chars2 = 0;
1259 int last_chars1 = 0;
1260 int last_chars2 = 0;
1261 Diff lastDiff = null;
1262 for (Diff aDiff : diffs) {
1263 if (aDiff.operation != Operation.INSERT) {
1264
1265 chars1 += aDiff.text.length();
1266 }
1267 if (aDiff.operation != Operation.DELETE) {
1268
1269 chars2 += aDiff.text.length();
1270 }
1271 if (chars1 > loc) {
1272
1273 lastDiff = aDiff;
1274 break;
1275 }
1276 last_chars1 = chars1;
1277 last_chars2 = chars2;
1278 }
1279 if (lastDiff != null && lastDiff.operation == Operation.DELETE) {
1280
1281 return last_chars2;
1282 }
1283
1284 return last_chars2 + (loc - last_chars1);
1285 }
1286
1287
1288
1289
1290
1291
1292
1293 public String diff_prettyHtml(LinkedList<Diff> diffs) {
1294 StringBuilder html = new StringBuilder();
1295 int i = 0;
1296 for (Diff aDiff : diffs) {
1297 String text = aDiff.text.replace("&", "&").replace("<", "<")
1298 .replace(">", ">").replace("\n", "¶<BR>");
1299 switch (aDiff.operation) {
1300 case INSERT:
1301 html.append("<INS STYLE=\"background:#E6FFE6;\" TITLE=\"i=").append(i)
1302 .append("\">").append(text).append("</INS>");
1303 break;
1304 case DELETE:
1305 html.append("<DEL STYLE=\"background:#FFE6E6;\" TITLE=\"i=").append(i)
1306 .append("\">").append(text).append("</DEL>");
1307 break;
1308 case EQUAL:
1309 html.append("<SPAN TITLE=\"i=").append(i).append("\">").append(text)
1310 .append("</SPAN>");
1311 break;
1312 }
1313 if (aDiff.operation != Operation.DELETE) {
1314 i += aDiff.text.length();
1315 }
1316 }
1317 return html.toString();
1318 }
1319
1320
1321
1322
1323
1324
1325
1326 public String diff_text1(LinkedList<Diff> diffs) {
1327 StringBuilder text = new StringBuilder();
1328 for (Diff aDiff : diffs) {
1329 if (aDiff.operation != Operation.INSERT) {
1330 text.append(aDiff.text);
1331 }
1332 }
1333 return text.toString();
1334 }
1335
1336
1337
1338
1339
1340
1341
1342 public String diff_text2(LinkedList<Diff> diffs) {
1343 StringBuilder text = new StringBuilder();
1344 for (Diff aDiff : diffs) {
1345 if (aDiff.operation != Operation.DELETE) {
1346 text.append(aDiff.text);
1347 }
1348 }
1349 return text.toString();
1350 }
1351
1352
1353
1354
1355
1356
1357
1358
1359 public int diff_levenshtein(LinkedList<Diff> diffs) {
1360 int levenshtein = 0;
1361 int insertions = 0;
1362 int deletions = 0;
1363 for (Diff aDiff : diffs) {
1364 switch (aDiff.operation) {
1365 case INSERT:
1366 insertions += aDiff.text.length();
1367 break;
1368 case DELETE:
1369 deletions += aDiff.text.length();
1370 break;
1371 case EQUAL:
1372
1373 levenshtein += Math.max(insertions, deletions);
1374 insertions = 0;
1375 deletions = 0;
1376 break;
1377 }
1378 }
1379 levenshtein += Math.max(insertions, deletions);
1380 return levenshtein;
1381 }
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392 public String diff_toDelta(LinkedList<Diff> diffs) {
1393 StringBuilder text = new StringBuilder();
1394 for (Diff aDiff : diffs) {
1395 switch (aDiff.operation) {
1396 case INSERT:
1397 try {
1398 text.append("+").append(URLEncoder.encode(aDiff.text, "UTF-8")
1399 .replace('+', ' ')).append("\t");
1400 } catch (UnsupportedEncodingException e) {
1401
1402 throw new Error("This system does not support UTF-8.", e);
1403 }
1404 break;
1405 case DELETE:
1406 text.append("-").append(aDiff.text.length()).append("\t");
1407 break;
1408 case EQUAL:
1409 text.append("=").append(aDiff.text.length()).append("\t");
1410 break;
1411 }
1412 }
1413 String delta = text.toString();
1414 if (delta.length() != 0) {
1415
1416 delta = delta.substring(0, delta.length() - 1);
1417 delta = unescapeForEncodeUriCompatability(delta);
1418 }
1419 return delta;
1420 }
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431 public LinkedList<Diff> diff_fromDelta(String text1, String delta)
1432 throws IllegalArgumentException {
1433 LinkedList<Diff> diffs = new LinkedList<Diff>();
1434 int pointer = 0;
1435 String[] tokens = delta.split("\t");
1436 for (String token : tokens) {
1437 if (token.length() == 0) {
1438
1439 continue;
1440 }
1441
1442
1443 String param = token.substring(1);
1444 switch (token.charAt(0)) {
1445 case '+':
1446
1447 param = param.replace("+", "%2B");
1448 try {
1449 param = URLDecoder.decode(param, "UTF-8");
1450 } catch (UnsupportedEncodingException e) {
1451
1452 throw new Error("This system does not support UTF-8.", e);
1453 } catch (IllegalArgumentException e) {
1454
1455 throw new IllegalArgumentException(
1456 "Illegal escape in diff_fromDelta: " + param, e);
1457 }
1458 diffs.add(new Diff(Operation.INSERT, param));
1459 break;
1460 case '-':
1461
1462 case '=':
1463 int n;
1464 try {
1465 n = Integer.parseInt(param);
1466 } catch (NumberFormatException e) {
1467 throw new IllegalArgumentException(
1468 "Invalid number in diff_fromDelta: " + param, e);
1469 }
1470 if (n < 0) {
1471 throw new IllegalArgumentException(
1472 "Negative number in diff_fromDelta: " + param);
1473 }
1474 String text;
1475 try {
1476 text = text1.substring(pointer, pointer += n);
1477 } catch (StringIndexOutOfBoundsException e) {
1478 throw new IllegalArgumentException("Delta length (" + pointer
1479 + ") larger than source text length (" + text1.length()
1480 + ").", e);
1481 }
1482 if (token.charAt(0) == '=') {
1483 diffs.add(new Diff(Operation.EQUAL, text));
1484 } else {
1485 diffs.add(new Diff(Operation.DELETE, text));
1486 }
1487 break;
1488 default:
1489
1490 throw new IllegalArgumentException(
1491 "Invalid diff operation in diff_fromDelta: " + token.charAt(0));
1492 }
1493 }
1494 if (pointer != text1.length()) {
1495 throw new IllegalArgumentException("Delta length (" + pointer
1496 + ") smaller than source text length (" + text1.length() + ").");
1497 }
1498 return diffs;
1499 }
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513 public int match_main(String text, String pattern, int loc) {
1514 loc = Math.max(0, Math.min(loc, text.length()));
1515 if (text.equals(pattern)) {
1516
1517 return 0;
1518 } else if (text.length() == 0) {
1519
1520 return -1;
1521 } else if (loc + pattern.length() <= text.length()
1522 && text.substring(loc, loc + pattern.length()).equals(pattern)) {
1523
1524 return loc;
1525 } else {
1526
1527 return match_bitap(text, pattern, loc);
1528 }
1529 }
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540 protected int match_bitap(String text, String pattern, int loc) {
1541 assert (Match_MaxBits == 0 || pattern.length() <= Match_MaxBits)
1542 : "Pattern too long for this application.";
1543
1544
1545 Map<Character, Integer> s = match_alphabet(pattern);
1546
1547
1548 double score_threshold = Match_Threshold;
1549
1550 int best_loc = text.indexOf(pattern, loc);
1551 if (best_loc != -1) {
1552 score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern),
1553 score_threshold);
1554
1555 best_loc = text.lastIndexOf(pattern, loc + pattern.length());
1556 if (best_loc != -1) {
1557 score_threshold = Math.min(match_bitapScore(0, best_loc, loc, pattern),
1558 score_threshold);
1559 }
1560 }
1561
1562
1563 int matchmask = 1 << (pattern.length() - 1);
1564 best_loc = -1;
1565
1566 int bin_min, bin_mid;
1567 int bin_max = pattern.length() + text.length();
1568
1569 int[] last_rd = new int[0];
1570 for (int d = 0; d < pattern.length(); d++) {
1571
1572
1573
1574 bin_min = 0;
1575 bin_mid = bin_max;
1576 while (bin_min < bin_mid) {
1577 if (match_bitapScore(d, loc + bin_mid, loc, pattern)
1578 <= score_threshold) {
1579 bin_min = bin_mid;
1580 } else {
1581 bin_max = bin_mid;
1582 }
1583 bin_mid = (bin_max - bin_min) / 2 + bin_min;
1584 }
1585
1586 bin_max = bin_mid;
1587 int start = Math.max(1, loc - bin_mid + 1);
1588 int finish = Math.min(loc + bin_mid, text.length()) + pattern.length();
1589
1590 int[] rd = new int[finish + 2];
1591 rd[finish + 1] = (1 << d) - 1;
1592 for (int j = finish; j >= start; j--) {
1593 int charMatch;
1594 if (text.length() <= j - 1 || !s.containsKey(text.charAt(j - 1))) {
1595
1596 charMatch = 0;
1597 } else {
1598 charMatch = s.get(text.charAt(j - 1));
1599 }
1600 if (d == 0) {
1601
1602 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
1603 } else {
1604
1605 rd[j] = ((rd[j + 1] << 1) | 1) & charMatch
1606 | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | last_rd[j + 1];
1607 }
1608 if ((rd[j] & matchmask) != 0) {
1609 double score = match_bitapScore(d, j - 1, loc, pattern);
1610
1611
1612 if (score <= score_threshold) {
1613
1614 score_threshold = score;
1615 best_loc = j - 1;
1616 if (best_loc > loc) {
1617
1618 start = Math.max(1, 2 * loc - best_loc);
1619 } else {
1620
1621 break;
1622 }
1623 }
1624 }
1625 }
1626 if (match_bitapScore(d + 1, loc, loc, pattern) > score_threshold) {
1627
1628 break;
1629 }
1630 last_rd = rd;
1631 }
1632 return best_loc;
1633 }
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644 private double match_bitapScore(int e, int x, int loc, String pattern) {
1645 float accuracy = (float) e / pattern.length();
1646 int proximity = Math.abs(loc - x);
1647 if (Match_Distance == 0) {
1648
1649 return proximity == 0 ? accuracy : 1.0;
1650 }
1651 return accuracy + (proximity / (float) Match_Distance);
1652 }
1653
1654
1655
1656
1657
1658
1659
1660 protected Map<Character, Integer> match_alphabet(String pattern) {
1661 Map<Character, Integer> s = new HashMap<Character, Integer>();
1662 char[] char_pattern = pattern.toCharArray();
1663 for (char c : char_pattern) {
1664 s.put(c, 0);
1665 }
1666 int i = 0;
1667 for (char c : char_pattern) {
1668 s.put(c, s.get(c) | (1 << (pattern.length() - i - 1)));
1669 i++;
1670 }
1671 return s;
1672 }
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684 protected void patch_addContext(Patch patch, String text) {
1685 if (text.length() == 0) {
1686 return;
1687 }
1688 String pattern = text.substring(patch.start2, patch.start2 + patch.length1);
1689 int padding = 0;
1690
1691
1692
1693 while (text.indexOf(pattern) != text.lastIndexOf(pattern)
1694 && pattern.length() < Match_MaxBits - Patch_Margin - Patch_Margin) {
1695 padding += Patch_Margin;
1696 pattern = text.substring(Math.max(0, patch.start2 - padding),
1697 Math.min(text.length(), patch.start2 + patch.length1 + padding));
1698 }
1699
1700 padding += Patch_Margin;
1701
1702
1703 String prefix = text.substring(Math.max(0, patch.start2 - padding),
1704 patch.start2);
1705 if (prefix.length() != 0) {
1706 patch.diffs.addFirst(new Diff(Operation.EQUAL, prefix));
1707 }
1708
1709 String suffix = text.substring(patch.start2 + patch.length1,
1710 Math.min(text.length(), patch.start2 + patch.length1 + padding));
1711 if (suffix.length() != 0) {
1712 patch.diffs.addLast(new Diff(Operation.EQUAL, suffix));
1713 }
1714
1715
1716 patch.start1 -= prefix.length();
1717 patch.start2 -= prefix.length();
1718
1719 patch.length1 += prefix.length() + suffix.length();
1720 patch.length2 += prefix.length() + suffix.length();
1721 }
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731 public LinkedList<Patch> patch_make(String text1, String text2) {
1732
1733 LinkedList<Diff> diffs = diff_main(text1, text2, true);
1734 if (diffs.size() > 2) {
1735 diff_cleanupSemantic(diffs);
1736 diff_cleanupEfficiency(diffs);
1737 }
1738 return patch_make(text1, diffs);
1739 }
1740
1741
1742
1743
1744
1745
1746
1747
1748 public LinkedList<Patch> patch_make(LinkedList<Diff> diffs) {
1749
1750 String text1 = diff_text1(diffs);
1751 return patch_make(text1, diffs);
1752 }
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764 public LinkedList<Patch> patch_make(String text1, String text2,
1765 LinkedList<Diff> diffs) {
1766 return patch_make(text1, diffs);
1767 }
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777 public LinkedList<Patch> patch_make(String text1, LinkedList<Diff> diffs) {
1778 LinkedList<Patch> patches = new LinkedList<Patch>();
1779 if (diffs.isEmpty()) {
1780 return patches;
1781 }
1782 Patch patch = new Patch();
1783 int char_count1 = 0;
1784 int char_count2 = 0;
1785
1786
1787
1788 String prepatch_text = text1;
1789 String postpatch_text = text1;
1790 for (Diff aDiff : diffs) {
1791 if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) {
1792
1793 patch.start1 = char_count1;
1794 patch.start2 = char_count2;
1795 }
1796
1797 switch (aDiff.operation) {
1798 case INSERT:
1799 patch.diffs.add(aDiff);
1800 patch.length2 += aDiff.text.length();
1801 postpatch_text = postpatch_text.substring(0, char_count2)
1802 + aDiff.text + postpatch_text.substring(char_count2);
1803 break;
1804 case DELETE:
1805 patch.length1 += aDiff.text.length();
1806 patch.diffs.add(aDiff);
1807 postpatch_text = postpatch_text.substring(0, char_count2)
1808 + postpatch_text.substring(char_count2 + aDiff.text.length());
1809 break;
1810 case EQUAL:
1811 if (aDiff.text.length() <= 2 * Patch_Margin
1812 && !patch.diffs.isEmpty() && aDiff != diffs.getLast()) {
1813
1814 patch.diffs.add(aDiff);
1815 patch.length1 += aDiff.text.length();
1816 patch.length2 += aDiff.text.length();
1817 }
1818
1819 if (aDiff.text.length() >= 2 * Patch_Margin) {
1820
1821 if (!patch.diffs.isEmpty()) {
1822 patch_addContext(patch, prepatch_text);
1823 patches.add(patch);
1824 patch = new Patch();
1825
1826
1827
1828
1829 prepatch_text = postpatch_text;
1830 char_count1 = char_count2;
1831 }
1832 }
1833 break;
1834 }
1835
1836
1837 if (aDiff.operation != Operation.INSERT) {
1838 char_count1 += aDiff.text.length();
1839 }
1840 if (aDiff.operation != Operation.DELETE) {
1841 char_count2 += aDiff.text.length();
1842 }
1843 }
1844
1845 if (!patch.diffs.isEmpty()) {
1846 patch_addContext(patch, prepatch_text);
1847 patches.add(patch);
1848 }
1849
1850 return patches;
1851 }
1852
1853
1854
1855
1856
1857
1858
1859 public LinkedList<Patch> patch_deepCopy(LinkedList<Patch> patches) {
1860 LinkedList<Patch> patchesCopy = new LinkedList<Patch>();
1861 for (Patch aPatch : patches) {
1862 Patch patchCopy = new Patch();
1863 for (Diff aDiff : aPatch.diffs) {
1864 Diff diffCopy = new Diff(aDiff.operation, aDiff.text);
1865 patchCopy.diffs.add(diffCopy);
1866 }
1867 patchCopy.start1 = aPatch.start1;
1868 patchCopy.start2 = aPatch.start2;
1869 patchCopy.length1 = aPatch.length1;
1870 patchCopy.length2 = aPatch.length2;
1871 patchesCopy.add(patchCopy);
1872 }
1873 return patchesCopy;
1874 }
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885 public Object[] patch_apply(LinkedList<Patch> patches, String text) {
1886 if (patches.isEmpty()) {
1887 return new Object[]{text, new boolean[0]};
1888 }
1889
1890
1891 patches = patch_deepCopy(patches);
1892
1893 String nullPadding = patch_addPadding(patches);
1894 text = nullPadding + text + nullPadding;
1895 patch_splitMax(patches);
1896
1897 int x = 0;
1898
1899
1900
1901
1902 int delta = 0;
1903 boolean[] results = new boolean[patches.size()];
1904 for (Patch aPatch : patches) {
1905 int expected_loc = aPatch.start2 + delta;
1906 String text1 = diff_text1(aPatch.diffs);
1907 int start_loc;
1908 int end_loc = -1;
1909 if (text1.length() > this.Match_MaxBits) {
1910
1911
1912 start_loc = match_main(text,
1913 text1.substring(0, this.Match_MaxBits), expected_loc);
1914 if (start_loc != -1) {
1915 end_loc = match_main(text,
1916 text1.substring(text1.length() - this.Match_MaxBits),
1917 expected_loc + text1.length() - this.Match_MaxBits);
1918 if (end_loc == -1 || start_loc >= end_loc) {
1919
1920 start_loc = -1;
1921 }
1922 }
1923 } else {
1924 start_loc = match_main(text, text1, expected_loc);
1925 }
1926 if (start_loc == -1) {
1927
1928 results[x] = false;
1929
1930 delta -= aPatch.length2 - aPatch.length1;
1931 } else {
1932
1933 results[x] = true;
1934 delta = start_loc - expected_loc;
1935 String text2;
1936 if (end_loc == -1) {
1937 text2 = text.substring(start_loc,
1938 Math.min(start_loc + text1.length(), text.length()));
1939 } else {
1940 text2 = text.substring(start_loc,
1941 Math.min(end_loc + this.Match_MaxBits, text.length()));
1942 }
1943 if (text1.equals(text2)) {
1944
1945 text = text.substring(0, start_loc) + diff_text2(aPatch.diffs)
1946 + text.substring(start_loc + text1.length());
1947 } else {
1948
1949
1950 LinkedList<Diff> diffs = diff_main(text1, text2, false);
1951 if (text1.length() > this.Match_MaxBits
1952 && diff_levenshtein(diffs) / (float) text1.length()
1953 > this.Patch_DeleteThreshold) {
1954
1955 results[x] = false;
1956 } else {
1957 diff_cleanupSemanticLossless(diffs);
1958 int index1 = 0;
1959 for (Diff aDiff : aPatch.diffs) {
1960 if (aDiff.operation != Operation.EQUAL) {
1961 int index2 = diff_xIndex(diffs, index1);
1962 if (aDiff.operation == Operation.INSERT) {
1963
1964 text = text.substring(0, start_loc + index2) + aDiff.text
1965 + text.substring(start_loc + index2);
1966 } else if (aDiff.operation == Operation.DELETE) {
1967
1968 text = text.substring(0, start_loc + index2)
1969 + text.substring(start_loc + diff_xIndex(diffs,
1970 index1 + aDiff.text.length()));
1971 }
1972 }
1973 if (aDiff.operation != Operation.DELETE) {
1974 index1 += aDiff.text.length();
1975 }
1976 }
1977 }
1978 }
1979 }
1980 x++;
1981 }
1982
1983 text = text.substring(nullPadding.length(), text.length()
1984 - nullPadding.length());
1985 return new Object[]{text, results};
1986 }
1987
1988
1989
1990
1991
1992
1993
1994
1995 public String patch_addPadding(LinkedList<Patch> patches) {
1996 int paddingLength = this.Patch_Margin;
1997 String nullPadding = "";
1998 for (int x = 1; x <= paddingLength; x++) {
1999 nullPadding += String.valueOf((char) x);
2000 }
2001
2002
2003 for (Patch aPatch : patches) {
2004 aPatch.start1 += paddingLength;
2005 aPatch.start2 += paddingLength;
2006 }
2007
2008
2009 Patch patch = patches.getFirst();
2010 LinkedList<Diff> diffs = patch.diffs;
2011 if (diffs.isEmpty() || diffs.getFirst().operation != Operation.EQUAL) {
2012
2013 diffs.addFirst(new Diff(Operation.EQUAL, nullPadding));
2014 patch.start1 -= paddingLength;
2015 patch.start2 -= paddingLength;
2016 patch.length1 += paddingLength;
2017 patch.length2 += paddingLength;
2018 } else if (paddingLength > diffs.getFirst().text.length()) {
2019
2020 Diff firstDiff = diffs.getFirst();
2021 int extraLength = paddingLength - firstDiff.text.length();
2022 firstDiff.text = nullPadding.substring(firstDiff.text.length())
2023 + firstDiff.text;
2024 patch.start1 -= extraLength;
2025 patch.start2 -= extraLength;
2026 patch.length1 += extraLength;
2027 patch.length2 += extraLength;
2028 }
2029
2030
2031 patch = patches.getLast();
2032 diffs = patch.diffs;
2033 if (diffs.isEmpty() || diffs.getLast().operation != Operation.EQUAL) {
2034
2035 diffs.addLast(new Diff(Operation.EQUAL, nullPadding));
2036 patch.length1 += paddingLength;
2037 patch.length2 += paddingLength;
2038 } else if (paddingLength > diffs.getLast().text.length()) {
2039
2040 Diff lastDiff = diffs.getLast();
2041 int extraLength = paddingLength - lastDiff.text.length();
2042 lastDiff.text += nullPadding.substring(0, extraLength);
2043 patch.length1 += extraLength;
2044 patch.length2 += extraLength;
2045 }
2046
2047 return nullPadding;
2048 }
2049
2050
2051
2052
2053
2054
2055
2056 public void patch_splitMax(LinkedList<Patch> patches) {
2057 int patch_size;
2058 String precontext, postcontext;
2059 Patch patch;
2060 int start1, start2;
2061 boolean empty;
2062 Operation diff_type;
2063 String diff_text;
2064 ListIterator<Patch> pointer = patches.listIterator();
2065 Patch bigpatch = pointer.hasNext() ? pointer.next() : null;
2066 while (bigpatch != null) {
2067 if (bigpatch.length1 <= Match_MaxBits) {
2068 bigpatch = pointer.hasNext() ? pointer.next() : null;
2069 continue;
2070 }
2071
2072 pointer.remove();
2073 patch_size = Match_MaxBits;
2074 start1 = bigpatch.start1;
2075 start2 = bigpatch.start2;
2076 precontext = "";
2077 while (!bigpatch.diffs.isEmpty()) {
2078
2079 patch = new Patch();
2080 empty = true;
2081 patch.start1 = start1 - precontext.length();
2082 patch.start2 = start2 - precontext.length();
2083 if (precontext.length() != 0) {
2084 patch.length1 = patch.length2 = precontext.length();
2085 patch.diffs.add(new Diff(Operation.EQUAL, precontext));
2086 }
2087 while (!bigpatch.diffs.isEmpty()
2088 && patch.length1 < patch_size - Patch_Margin) {
2089 diff_type = bigpatch.diffs.getFirst().operation;
2090 diff_text = bigpatch.diffs.getFirst().text;
2091 if (diff_type == Operation.INSERT) {
2092
2093 patch.length2 += diff_text.length();
2094 start2 += diff_text.length();
2095 patch.diffs.addLast(bigpatch.diffs.removeFirst());
2096 empty = false;
2097 } else if (diff_type == Operation.DELETE && patch.diffs.size() == 1
2098 && patch.diffs.getFirst().operation == Operation.EQUAL
2099 && diff_text.length() > 2 * patch_size) {
2100
2101 patch.length1 += diff_text.length();
2102 start1 += diff_text.length();
2103 empty = false;
2104 patch.diffs.add(new Diff(diff_type, diff_text));
2105 bigpatch.diffs.removeFirst();
2106 } else {
2107
2108 diff_text = diff_text.substring(0, Math.min(diff_text.length(),
2109 patch_size - patch.length1 - Patch_Margin));
2110 patch.length1 += diff_text.length();
2111 start1 += diff_text.length();
2112 if (diff_type == Operation.EQUAL) {
2113 patch.length2 += diff_text.length();
2114 start2 += diff_text.length();
2115 } else {
2116 empty = false;
2117 }
2118 patch.diffs.add(new Diff(diff_type, diff_text));
2119 if (diff_text.equals(bigpatch.diffs.getFirst().text)) {
2120 bigpatch.diffs.removeFirst();
2121 } else {
2122 bigpatch.diffs.getFirst().text = bigpatch.diffs.getFirst().text
2123 .substring(diff_text.length());
2124 }
2125 }
2126 }
2127
2128 precontext = diff_text2(patch.diffs);
2129 precontext = precontext.substring(Math.max(0, precontext.length()
2130 - Patch_Margin));
2131
2132 if (diff_text1(bigpatch.diffs).length() > Patch_Margin) {
2133 postcontext = diff_text1(bigpatch.diffs).substring(0, Patch_Margin);
2134 } else {
2135 postcontext = diff_text1(bigpatch.diffs);
2136 }
2137 if (postcontext.length() != 0) {
2138 patch.length1 += postcontext.length();
2139 patch.length2 += postcontext.length();
2140 if (!patch.diffs.isEmpty()
2141 && patch.diffs.getLast().operation == Operation.EQUAL) {
2142 patch.diffs.getLast().text += postcontext;
2143 } else {
2144 patch.diffs.add(new Diff(Operation.EQUAL, postcontext));
2145 }
2146 }
2147 if (!empty) {
2148 pointer.add(patch);
2149 }
2150 }
2151 bigpatch = pointer.hasNext() ? pointer.next() : null;
2152 }
2153 }
2154
2155
2156
2157
2158
2159
2160
2161 public String patch_toText(List<Patch> patches) {
2162 StringBuilder text = new StringBuilder();
2163 for (Patch aPatch : patches) {
2164 text.append(aPatch);
2165 }
2166 return text.toString();
2167 }
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177 public List<Patch> patch_fromText(String textline)
2178 throws IllegalArgumentException {
2179 List<Patch> patches = new LinkedList<Patch>();
2180 if (textline.length() == 0) {
2181 return patches;
2182 }
2183 List<String> textList = Arrays.asList(textline.split("\n"));
2184 LinkedList<String> text = new LinkedList<String>(textList);
2185 Patch patch;
2186 Pattern patchHeader
2187 = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$");
2188 Matcher m;
2189 char sign;
2190 String line;
2191 while (!text.isEmpty()) {
2192 m = patchHeader.matcher(text.getFirst());
2193 if (!m.matches()) {
2194 throw new IllegalArgumentException(
2195 "Invalid patch string: " + text.getFirst());
2196 }
2197 patch = new Patch();
2198 patches.add(patch);
2199 patch.start1 = Integer.parseInt(m.group(1));
2200 if (m.group(2).length() == 0) {
2201 patch.start1--;
2202 patch.length1 = 1;
2203 } else if (m.group(2).equals("0")) {
2204 patch.length1 = 0;
2205 } else {
2206 patch.start1--;
2207 patch.length1 = Integer.parseInt(m.group(2));
2208 }
2209
2210 patch.start2 = Integer.parseInt(m.group(3));
2211 if (m.group(4).length() == 0) {
2212 patch.start2--;
2213 patch.length2 = 1;
2214 } else if (m.group(4).equals("0")) {
2215 patch.length2 = 0;
2216 } else {
2217 patch.start2--;
2218 patch.length2 = Integer.parseInt(m.group(4));
2219 }
2220 text.removeFirst();
2221
2222 while (!text.isEmpty()) {
2223 try {
2224 sign = text.getFirst().charAt(0);
2225 } catch (IndexOutOfBoundsException e) {
2226
2227 text.removeFirst();
2228 continue;
2229 }
2230 line = text.getFirst().substring(1);
2231 line = line.replace("+", "%2B");
2232 try {
2233 line = URLDecoder.decode(line, "UTF-8");
2234 } catch (UnsupportedEncodingException e) {
2235
2236 throw new Error("This system does not support UTF-8.", e);
2237 } catch (IllegalArgumentException e) {
2238
2239 throw new IllegalArgumentException(
2240 "Illegal escape in patch_fromText: " + line, e);
2241 }
2242 if (sign == '-') {
2243
2244 patch.diffs.add(new Diff(Operation.DELETE, line));
2245 } else if (sign == '+') {
2246
2247 patch.diffs.add(new Diff(Operation.INSERT, line));
2248 } else if (sign == ' ') {
2249
2250 patch.diffs.add(new Diff(Operation.EQUAL, line));
2251 } else if (sign == '@') {
2252
2253 break;
2254 } else {
2255
2256 throw new IllegalArgumentException(
2257 "Invalid patch mode '" + sign + "' in: " + line);
2258 }
2259 text.removeFirst();
2260 }
2261 }
2262 return patches;
2263 }
2264
2265
2266
2267
2268
2269 public static class Diff {
2270
2271
2272
2273 public Operation operation;
2274
2275
2276
2277 public String text;
2278
2279
2280
2281
2282
2283
2284 public Diff(Operation operation, String text) {
2285
2286 this.operation = operation;
2287 this.text = text;
2288 }
2289
2290
2291
2292
2293
2294
2295 public String toString() {
2296 String prettyText = this.text.replace('\n', '\u00b6');
2297 return "Diff(" + this.operation + ",\"" + prettyText + "\")";
2298 }
2299
2300
2301
2302
2303
2304
2305
2306 public boolean equals(Object d) {
2307 try {
2308 return (((Diff) d).operation == this.operation)
2309 && (((Diff) d).text.equals(this.text));
2310 } catch (ClassCastException e) {
2311 return false;
2312 }
2313 }
2314 }
2315
2316
2317
2318
2319
2320 public static class Patch {
2321 public LinkedList<Diff> diffs;
2322 public int start1;
2323 public int start2;
2324 public int length1;
2325 public int length2;
2326
2327
2328
2329
2330
2331 public Patch() {
2332 this.diffs = new LinkedList<Diff>();
2333 }
2334
2335
2336
2337
2338
2339
2340
2341
2342 public String toString() {
2343 String coords1, coords2;
2344 if (this.length1 == 0) {
2345 coords1 = this.start1 + ",0";
2346 } else if (this.length1 == 1) {
2347 coords1 = Integer.toString(this.start1 + 1);
2348 } else {
2349 coords1 = (this.start1 + 1) + "," + this.length1;
2350 }
2351 if (this.length2 == 0) {
2352 coords2 = this.start2 + ",0";
2353 } else if (this.length2 == 1) {
2354 coords2 = Integer.toString(this.start2 + 1);
2355 } else {
2356 coords2 = (this.start2 + 1) + "," + this.length2;
2357 }
2358 StringBuilder text = new StringBuilder();
2359 text.append("@@ -").append(coords1).append(" +").append(coords2)
2360 .append(" @@\n");
2361
2362 for (Diff aDiff : this.diffs) {
2363 switch (aDiff.operation) {
2364 case INSERT:
2365 text.append('+');
2366 break;
2367 case DELETE:
2368 text.append('-');
2369 break;
2370 case EQUAL:
2371 text.append(' ');
2372 break;
2373 }
2374 try {
2375 text.append(URLEncoder.encode(aDiff.text, "UTF-8").replace('+', ' '))
2376 .append("\n");
2377 } catch (UnsupportedEncodingException e) {
2378
2379 throw new Error("This system does not support UTF-8.", e);
2380 }
2381 }
2382 return unescapeForEncodeUriCompatability(text.toString());
2383 }
2384 }
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400 private static String unescapeForEncodeUriCompatability(String str) {
2401 return str.replace("%21", "!").replace("%7E", "~")
2402 .replace("%27", "'").replace("%28", "(").replace("%29", ")")
2403 .replace("%3B", ";").replace("%2F", "/").replace("%3F", "?")
2404 .replace("%3A", ":").replace("%40", "@").replace("%26", "&")
2405 .replace("%3D", "=").replace("%2B", "+").replace("%24", "$")
2406 .replace("%2C", ",").replace("%23", "#");
2407 }
2408 }