1 package org.catacomb.util;
2
3
4 /*
5 * $Log: DiffPrint.java,v $
6 * Revision 1.2 2010-05-31 11:01:45 rcc
7 * rodrigos version
8 *
9 * Revision 1.1 2009-06-23 03:37:27 rcc
10 * first commit
11 *
12 * Revision 1.2 2007-08-31 20:01:54 rcc
13 * misc
14 *
15 * Revision 1.1 2007-08-13 13:20:39 rcc
16 * first
17 *
18 * Revision 1.5 2004/01/29 02:35:35 stuart
19 * Test for out of bounds exception in UnifiedPrint.print_hunk.
20 * Add setOutput() to DiffPrint.Base.
21 *
22 * Revision 1.4 2003/04/22 01:50:47 stuart
23 * add Unified format diff
24 *
25 * Revision 1.3 2003/04/22 01:00:32 stuart
26 * added context diff format
27 *
28 * Revision 1.2 2000/03/02 16:59:54 stuart
29 * add GPL
30 *
31 */
32 import java.io.*;
33 import java.util.Date;
34 import java.util.Vector;
35
36 //import com.objectspace.jgl.predicates.UnaryPredicate;
37
38
39 interface UnaryPredicate {
40 boolean execute(Object obj);
41 }
42
43 /** A simple framework for printing change lists produced by <code>Diff</code>.
44 @see bmsi.util.Diff
45 @author Stuart D. Gathman
46 Copyright (C) 2000 Business Management Systems, Inc.
47 <p>
48 This program is free software; you can redistribute it and/or modify
49 it under the terms of the GNU General Public License as published by
50 the Free Software Foundation; either version 1, or (at your option)
51 any later version.
52 <p>
53 This program is distributed in the hope that it will be useful,
54 but WITHOUT ANY WARRANTY; without even the implied warranty of
55 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 GNU General Public License for more details.
57 <p>
58 You should have received a copy of the GNU General Public License
59 along with this program; if not, write to the Free Software
60 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
61 */
62
63
64 @SuppressWarnings("all")
65 public class DiffPrint {
66 /** A Base class for printing edit scripts produced by Diff.
67 This class divides the change list into "hunks", and calls
68 <code>print_hunk</code> for each hunk. Various utility methods
69 are provided as well.
70 */
71 public static abstract class Base {
72 protected PrintWriter outfile;
73
74 public void setOutput(Writer wtr) {
75 outfile = new PrintWriter(wtr);
76 }
77 public void setupOutput() {
78 if (outfile == null)
79 outfile = new PrintWriter(new OutputStreamWriter(System.out));
80 }
81 protected Base(Object[] a,Object[] b) {
82 file0 = a;
83 file1 = b;
84 }
85 /** Set to ignore certain kinds of lines when printing
86 an edit script. For example, ignoring blank lines or comments.
87 */
88 protected UnaryPredicate ignore = null;
89
90 /** Set to the lines of the files being compared.
91 */
92 protected Object[] file0, file1;
93
94 /** Divide SCRIPT into pieces by calling HUNKFUN and
95 print each piece with PRINTFUN.
96 Both functions take one arg, an edit script.
97
98 PRINTFUN takes a subscript which belongs together (with a null
99 link at the end) and prints it. */
100 public void print_script(Diff.change script) {
101 setupOutput();
102 Diff.change next = script;
103
104 while (next != null)
105 {
106 Diff.change t, end;
107
108 /* Find a set of changes that belong together. */
109 t = next;
110 end = hunkfun(next);
111
112 /* Disconnect them from the rest of the changes,
113 making them a hunk, and remember the rest for next iteration. */
114 next = end.link;
115 end.link = null;
116 //if (DEBUG)
117 // debug_script(t);
118
119 /* Print this hunk. */
120 print_hunk(t);
121
122 /* Reconnect the script so it will all be freed properly. */
123 end.link = next;
124 }
125 outfile.flush();
126 }
127
128 /** Called with the tail of the script
129 and returns the last link that belongs together with the start
130 of the tail. */
131
132 protected Diff.change hunkfun(Diff.change hunk) {
133 return hunk;
134 }
135
136 protected int first0, last0, first1, last1, deletes, inserts;
137
138 /** Look at a hunk of edit script and report the range of lines in each file
139 that it applies to. HUNK is the start of the hunk, which is a chain
140 of `struct change'. The first and last line numbers of file 0 are stored
141 in *FIRST0 and *LAST0, and likewise for file 1 in *FIRST1 and *LAST1.
142 Note that these are internal line numbers that count from 0.
143
144 If no lines from file 0 are deleted, then FIRST0 is LAST0+1.
145
146 Also set *DELETES nonzero if any lines of file 0 are deleted
147 and set *INSERTS nonzero if any lines of file 1 are inserted.
148 If only ignorable lines are inserted or deleted, both are
149 set to 0. */
150
151 protected void analyze_hunk(Diff.change hunk) {
152 int f0, l0 = 0, f1, l1 = 0, show_from = 0, show_to = 0;
153 int i;
154 Diff.change next;
155 boolean nontrivial = (ignore == null);
156
157 show_from = show_to = 0;
158
159 f0 = hunk.line0;
160 f1 = hunk.line1;
161
162 for (next = hunk; next != null; next = next.link)
163 {
164 l0 = next.line0 + next.deleted - 1;
165 l1 = next.line1 + next.inserted - 1;
166 show_from += next.deleted;
167 show_to += next.inserted;
168 for (i = next.line0; i <= l0 && ! nontrivial; i++)
169 if (!ignore.execute(file0[i]))
170 nontrivial = true;
171 for (i = next.line1; i <= l1 && ! nontrivial; i++)
172 if (!ignore.execute(file1[i]))
173 nontrivial = true;
174 }
175
176 first0 = f0;
177 last0 = l0;
178 first1 = f1;
179 last1 = l1;
180
181 /* If all inserted or deleted lines are ignorable,
182 tell the caller to ignore this hunk. */
183
184 if (!nontrivial)
185 show_from = show_to = 0;
186
187 deletes = show_from;
188 inserts = show_to;
189 }
190
191 /** Print the script header which identifies the files compared. */
192 protected void print_header(String filea, @SuppressWarnings("unused")
193 String fileb) {
194 setupOutput();
195 }
196
197 protected abstract void print_hunk(Diff.change hunk);
198
199 protected void print_1_line(String pre,Object linbuf) {
200 outfile.println(pre + linbuf.toString());
201 }
202
203 /** Print a pair of line numbers with SEPCHAR, translated for file FILE.
204 If the two numbers are identical, print just one number.
205
206 Args A and B are internal line numbers.
207 We print the translated (real) line numbers. */
208
209 protected void print_number_range(char sepchar, int a, int b) {
210 /* Note: we can have B < A in the case of a range of no lines.
211 In this case, we should print the line number before the range,
212 which is B. */
213 if (++b > ++a)
214 outfile.print("" + a + sepchar + b);
215 else
216 outfile.print(b);
217 }
218
219 public static char change_letter(int inserts, int deletes) {
220 if (inserts == 0)
221 return 'd';
222 else if (deletes == 0)
223 return 'a';
224 else
225 return 'c';
226 }
227 }
228
229 /** Print a change list in the standard diff format.
230 */
231 public static class NormalPrint extends Base {
232
233 public NormalPrint(Object[] a,Object[] b) {
234 super(a,b);
235 }
236
237 /** Print a hunk of a normal diff.
238 This is a contiguous portion of a complete edit script,
239 describing changes in consecutive lines. */
240
241 protected void print_hunk(Diff.change hunk) {
242
243 /* Determine range of line numbers involved in each file. */
244 analyze_hunk(hunk);
245 if (deletes == 0 && inserts == 0)
246 return;
247
248 /* Print out the line number header for this hunk */
249 print_number_range(',', first0, last0);
250 outfile.print(change_letter(inserts, deletes));
251 print_number_range(',', first1, last1);
252 outfile.println();
253
254 /* Print the lines that the first file has. */
255 if (deletes != 0)
256 for (int i = first0; i <= last0; i++)
257 print_1_line("< ", file0[i]);
258
259 if (inserts != 0 && deletes != 0)
260 outfile.println("---");
261
262 /* Print the lines that the second file has. */
263 if (inserts != 0)
264 for (int i = first1; i <= last1; i++)
265 print_1_line("> ", file1[i]);
266 }
267 }
268
269 /** Prints an edit script in a format suitable for input to <code>ed</code>.
270 The edit script must be generated with the reverse option to
271 be useful as actual <code>ed</code> input.
272 */
273 public static class EdPrint extends Base {
274
275 public EdPrint(Object[] a,Object[] b) {
276 super(a,b);
277 }
278
279 /** Print a hunk of an ed diff */
280 protected void print_hunk(Diff.change hunk) {
281
282 /* Determine range of line numbers involved in each file. */
283 analyze_hunk(hunk);
284 if (deletes == 0 && inserts == 0)
285 return;
286
287 /* Print out the line number header for this hunk */
288 print_number_range(',', first0, last0);
289 outfile.println(change_letter(inserts, deletes));
290
291 /* Print new/changed lines from second file, if needed */
292 if (inserts != 0)
293 {
294 boolean inserting = true;
295 for (int i = first1; i <= last1; i++)
296 {
297 /* Resume the insert, if we stopped. */
298 if (! inserting)
299 outfile.println(i - first1 + first0 + "a");
300 inserting = true;
301
302 /* If the file's line is just a dot, it would confuse `ed'.
303 So output it with a double dot, and set the flag LEADING_DOT
304 so that we will output another ed-command later
305 to change the double dot into a single dot. */
306
307 if (".".equals(file1[i]))
308 {
309 outfile.println("..");
310 outfile.println(".");
311 /* Now change that double dot to the desired single dot. */
312 outfile.println(i - first1 + first0 + 1 + "s/^\\.\\././");
313 inserting = false;
314 }
315 else
316 /* Line is not `.', so output it unmodified. */
317 print_1_line("", file1[i]);
318 }
319
320 /* End insert mode, if we are still in it. */
321 if (inserting)
322 outfile.println(".");
323 }
324 }
325 }
326
327 /** Prints an edit script in context diff format. This and its
328 'unified' variation is used for source code patches.
329 */
330 public static class ContextPrint extends Base {
331
332 protected int context = 3;
333
334 public ContextPrint(Object[] a,Object[] b) {
335 super(a,b);
336 }
337
338 protected void print_context_label(String mark, File inf, String label) {
339 setupOutput();
340 if (label != null)
341 outfile.println(mark + ' ' + label);
342 else if (inf.lastModified() > 0)
343
344 outfile.println(
345 mark + ' ' + inf.getPath() + '\t' + new Date(inf.lastModified())
346 );
347 else
348 /* Don't pretend that standard input is ancient. */
349 outfile.println(mark + ' ' + inf.getPath());
350 }
351
352 public void print_header(String filea,String fileb) {
353 print_context_label("***", new File(filea), filea);
354 print_context_label("---", new File(fileb), fileb);
355 }
356
357 /** If function_regexp defined, search for start of function. */
358 private String find_function(Object[] lines, int start) {
359 return null;
360 }
361
362 protected void print_function(Object[] file,int start) {
363 String function = find_function(file0, first0);
364 if (function != null) {
365 outfile.print(" ");
366 outfile.print(
367 (function.length() < 40) ? function : function.substring(0,40)
368 );
369 }
370 }
371
372 protected void print_hunk(Diff.change hunk) {
373
374 /* Determine range of line numbers involved in each file. */
375
376 analyze_hunk(hunk);
377
378 if (deletes == 0 && inserts == 0)
379 return;
380
381 /* Include a context's width before and after. */
382
383 first0 = Math.max(first0 - context, 0);
384 first1 = Math.max(first1 - context, 0);
385 last0 = Math.min(last0 + context, file0.length - 1);
386 last1 = Math.min(last1 + context, file1.length - 1);
387
388
389 outfile.print("***************");
390
391 /* If we looked for and found a function this is part of,
392 include its name in the header of the diff section. */
393 print_function(file0, first0);
394
395 outfile.println();
396 outfile.print("*** ");
397 print_number_range(',', first0, last0);
398 outfile.println(" ****");
399
400 if (deletes != 0) {
401 Diff.change next = hunk;
402
403 for (int i = first0; i <= last0; i++) {
404 /* Skip past changes that apply (in file 0)
405 only to lines before line I. */
406
407 while (next != null && next.line0 + next.deleted <= i)
408 next = next.link;
409
410 /* Compute the marking for line I. */
411
412 String prefix = " ";
413 if (next != null && next.line0 <= i)
414 /* The change NEXT covers this line.
415 If lines were inserted here in file 1, this is "changed".
416 Otherwise it is "deleted". */
417 prefix = (next.inserted > 0) ? "!" : "-";
418
419 print_1_line(prefix, file0[i]);
420 }
421 }
422
423 outfile.print("--- ");
424 print_number_range(',', first1, last1);
425 outfile.println(" ----");
426
427 if (inserts != 0) {
428 Diff.change next = hunk;
429
430 for (int i = first1; i <= last1; i++) {
431 /* Skip past changes that apply (in file 1)
432 only to lines before line I. */
433
434 while (next != null && next.line1 + next.inserted <= i)
435 next = next.link;
436
437 /* Compute the marking for line I. */
438
439 String prefix = " ";
440 if (next != null && next.line1 <= i)
441 /* The change NEXT covers this line.
442 If lines were deleted here in file 0, this is "changed".
443 Otherwise it is "inserted". */
444 prefix = (next.deleted > 0) ? "!" : "+";
445
446 print_1_line(prefix, file1[i]);
447 }
448 }
449 }
450 }
451
452 /** Prints an edit script in context diff format. This and its
453 'unified' variation is used for source code patches.
454 */
455 public static class UnifiedPrint extends ContextPrint {
456
457 public UnifiedPrint(Object[] a,Object[] b) {
458 super(a,b);
459 }
460
461 public void print_header(String filea,String fileb) {
462 print_context_label("---", new File(filea), filea);
463 print_context_label("+++", new File(fileb), fileb);
464 }
465
466 private void print_number_range(int a, int b) {
467 //translate_range (file, a, b, &trans_a, &trans_b);
468
469 /* Note: we can have B < A in the case of a range of no lines.
470 In this case, we should print the line number before the range,
471 which is B. */
472 if (b < a)
473 outfile.print(b + ",0");
474 else
475 super.print_number_range(',',a,b);
476 }
477
478 protected void print_hunk(Diff.change hunk) {
479 /* Determine range of line numbers involved in each file. */
480 analyze_hunk(hunk);
481
482 if (deletes == 0 && inserts == 0)
483 return;
484
485 /* Include a context's width before and after. */
486
487 first0 = Math.max(first0 - context, 0);
488 first1 = Math.max(first1 - context, 0);
489 last0 = Math.min(last0 + context, file0.length - 1);
490 last1 = Math.min(last1 + context, file1.length - 1);
491
492 outfile.print("@@ -");
493 print_number_range(first0, last0);
494 outfile.print(" +");
495 print_number_range(first1, last1);
496 outfile.print(" @@");
497
498 /* If we looked for and found a function this is part of,
499 include its name in the header of the diff section. */
500 print_function(file0,first0);
501
502 outfile.println();
503
504 Diff.change next = hunk;
505 int i = first0;
506 int j = first1;
507
508 while (i <= last0 || j <= last1) {
509
510 /* If the line isn't a difference, output the context from file 0. */
511
512 if (next == null || i < next.line0) {
513 if (i < file0.length) {
514 outfile.print(' ');
515 print_1_line("", file0[i++]);
516 }
517 j++;
518 }
519 else {
520 /* For each difference, first output the deleted part. */
521
522 int k = next.deleted;
523 while (k-- > 0) {
524 outfile.print('-');
525 print_1_line("", file0[i++]);
526 }
527
528 /* Then output the inserted part. */
529
530 k = next.inserted;
531 while (k-- > 0) {
532 outfile.print('+');
533 print_1_line("", file1[j++]);
534 }
535
536 /* We're done with this hunk, so on to the next! */
537
538 next = next.link;
539 }
540 }
541 }
542 }
543
544
545 /** Read a text file into an array of String. This provides basic diff
546 functionality. A more advanced diff utility will use specialized
547 objects to represent the text lines, with options to, for example,
548 convert sequences of whitespace to a single space for comparison
549 purposes.
550 */
551 static String[] slurp(String file) throws IOException {
552 BufferedReader rdr = new BufferedReader(new FileReader(file));
553 Vector<String> s = new Vector<String>();
554 for (;;) {
555 String line = rdr.readLine();
556 if (line == null) break;
557 s.addElement(line);
558 }
559 String[] a = new String[s.size()];
560 s.copyInto(a);
561 return a;
562 }
563
564 public static void main(String[] argv) throws IOException {
565 String filea = argv[argv.length - 2];
566 String fileb = argv[argv.length - 1];
567 String[] a = slurp(filea);
568 String[] b = slurp(fileb);
569 Diff d = new Diff(a,b);
570 char style = 'n';
571 for (int i = 0; i < argv.length - 2; ++i) {
572 String f = argv[i];
573 if (f.startsWith("-")) {
574 for (int j = 1; j < f.length(); ++j) {
575 switch (f.charAt(j)) {
576 case 'e': // Ed style
577 style = 'e';
578 break;
579 case 'c': // Context diff
580 style = 'c';
581 break;
582 case 'u':
583 style = 'u';
584 break;
585 }
586 }
587 }
588 }
589 boolean reverse = style == 'e';
590 Diff.change script = d.diff_2(reverse);
591 if (script == null)
592 System.err.println("No differences");
593 else {
594 Base p;
595 switch (style) {
596 case 'e':
597 p = new EdPrint(a,b);
598 break;
599 case 'c':
600 p = new ContextPrint(a,b);
601 break;
602 case 'u':
603 p = new UnifiedPrint(a,b);
604 break;
605 default:
606 p = new NormalPrint(a,b);
607 }
608 p.print_header(filea,fileb);
609 p.print_script(script);
610 }
611 }
612
613 }