View Javadoc

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 }