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