/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.diff; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringReader; import java.util.Vector; interface UnaryPredicate { boolean execute(Object obj); } /** * A simple framework for printing change lists produced by <code>Diff</code>. * * @see bmsi.util.Diff * @author Stuart D. Gathman Copyright (C) 2000 Business Management Systems, Inc. * <p> * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 1, or (at your option) any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * <p> * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ public class DiffPrint { /** * A Base class for printing edit scripts produced by Diff. This class divides the change list into "hunks", and calls * <code>print_hunk</code> for each hunk. Various utility methods are provided as well. */ public static abstract class Base { protected Base(Object[] a, Object[] b) { outfile = new PrintWriter(new OutputStreamWriter(System.out)); diffResult = new StringBuffer(); file0 = a; file1 = b; } /** * Set to ignore certain kinds of lines when printing an edit script. For example, ignoring blank lines or comments. */ protected UnaryPredicate ignore = null; /** * Set to the lines of the files being compared. */ protected Object[] file0, file1; /** * Divide SCRIPT into pieces by calling HUNKFUN and print each piece with PRINTFUN. Both functions take one arg, an edit script. * * PRINTFUN takes a subscript which belongs together (with a null link at the end) and prints it. */ public void print_script(Diff.change script) { Diff.change next = script; while (next != null) { Diff.change t, end; /* Find a set of changes that belong together. */ t = next; end = hunkfun(next); /* Disconnect them from the rest of the changes, making them a hunk, and remember the rest for next iteration. */ next = end.link; end.link = null; // if (DEBUG) // debug_script(t); /* Print this hunk. */ print_hunk(t); /* Reconnect the script so it will all be freed properly. */ end.link = next; } outfile.flush(); } /** * If using LinePrint class results are returned as string */ public String getDiffResult() { return diffResult != null ? diffResult.toString() : null; } /** * Called with the tail of the script and returns the last link that belongs together with the start of the tail. */ protected Diff.change hunkfun(Diff.change hunk) { return hunk; } protected int first0, last0, first1, last1, deletes, inserts; protected PrintWriter outfile; protected StringBuffer diffResult; /** * Look at a hunk of edit script and report the range of lines in each file that it applies to. HUNK is the start of the hunk, which * is a chain of `struct change'. The first and last line numbers of file 0 are stored in *FIRST0 and *LAST0, and likewise for file * 1 in *FIRST1 and *LAST1. Note that these are internal line numbers that count from 0. * * If no lines from file 0 are deleted, then FIRST0 is LAST0+1. * * Also set *DELETES nonzero if any lines of file 0 are deleted and set *INSERTS nonzero if any lines of file 1 are inserted. If * only ignorable lines are inserted or deleted, both are set to 0. */ protected void analyze_hunk(Diff.change hunk) { int f0, l0 = 0, f1, l1 = 0, show_from = 0, show_to = 0; int i; Diff.change next; boolean nontrivial = ignore == null; show_from = show_to = 0; f0 = hunk.line0; f1 = hunk.line1; for (next = hunk; next != null; next = next.link) { l0 = next.line0 + next.deleted - 1; l1 = next.line1 + next.inserted - 1; show_from += next.deleted; show_to += next.inserted; for (i = next.line0; i <= l0 && !nontrivial; i++) { if (!ignore.execute(file0[i])) { nontrivial = true; } } for (i = next.line1; i <= l1 && !nontrivial; i++) { if (!ignore.execute(file1[i])) { nontrivial = true; } } } first0 = f0; last0 = l0; first1 = f1; last1 = l1; /* If all inserted or deleted lines are ignorable, tell the caller to ignore this hunk. */ if (!nontrivial) { show_from = show_to = 0; } deletes = show_from; inserts = show_to; } protected abstract void print_hunk(Diff.change hunk); protected void print_1_line(String pre, Object linbuf) { outfile.println(pre + linbuf.toString()); diffResult.append(pre + linbuf.toString() + "\n"); } /** * Print a pair of line numbers with SEPCHAR, translated for file FILE. If the two numbers are identical, print just one number. * * Args A and B are internal line numbers. We print the translated (real) line numbers. */ protected void print_number_range(char sepchar, int a, int b) { /* Note: we can have B < A in the case of a range of no lines. In this case, we should print the line number before the range, which is B. */ if (++b > ++a) { outfile.print("" + a + sepchar + b); diffResult.append("" + a + sepchar + b); } else { outfile.print(b); diffResult.append(b); } } public static char change_letter(int inserts, int deletes) { if (inserts == 0) { return 'd'; } else if (deletes == 0) { return 'a'; } else { return 'c'; } } } /** * Print a change list in the Verse format used for Jscripture * * @author Patrick Lacson (patrick@lacson.net) */ public static class LinePrint extends Base { public LinePrint(Object[] a, Object[] b) { super(a, b); } /** * Print a hunk of a normal diff. This is a contiguous portion of a complete edit script, describing changes in consecutive lines. */ @Override protected void print_hunk(Diff.change hunk) { /* Determine range of line numbers involved in each file. */ analyze_hunk(hunk); if (deletes == 0 && inserts == 0) { return; } /* Print out the line number header for this hunk */ print_number_range(',', first0, last0); outfile.print(change_letter(inserts, deletes)); diffResult.append(change_letter(inserts, deletes)); print_number_range(',', first1, last1); outfile.println(); diffResult.append("\n"); /* Print the lines that the first file has. */ if (deletes != 0) { for (int i = first0; i <= last0; i++) { print_1_line("< ", file0[i]); } } if (inserts != 0 && deletes != 0) { outfile.println("---"); diffResult.append("---\n"); } /* Print the lines that the second file has. */ if (inserts != 0) { for (int i = first1; i <= last1; i++) { print_1_line("> ", file1[i]); } } } } /** * Print a change list in the standard diff format. */ public static class NormalPrint extends Base { public NormalPrint(Object[] a, Object[] b) { super(a, b); } /** * Print a hunk of a normal diff. This is a contiguous portion of a complete edit script, describing changes in consecutive lines. */ @Override protected void print_hunk(Diff.change hunk) { /* Determine range of line numbers involved in each file. */ analyze_hunk(hunk); if (deletes == 0 && inserts == 0) { return; } /* Print out the line number header for this hunk */ print_number_range(',', first0, last0); outfile.print(change_letter(inserts, deletes)); print_number_range(',', first1, last1); outfile.println(); /* Print the lines that the first file has. */ if (deletes != 0) { for (int i = first0; i <= last0; i++) { print_1_line("< ", file0[i]); } } if (inserts != 0 && deletes != 0) { outfile.println("---"); } /* Print the lines that the second file has. */ if (inserts != 0) { for (int i = first1; i <= last1; i++) { print_1_line("> ", file1[i]); } } } } /** * Prints an edit script in a format suitable for input to <code>ed</code>. The edit script must be generated with the reverse option to * be useful as actual <code>ed</code> input. */ public static class EdPrint extends Base { public EdPrint(Object[] a, Object[] b) { super(a, b); } /** Print a hunk of an ed diff */ @Override protected void print_hunk(Diff.change hunk) { /* Determine range of line numbers involved in each file. */ analyze_hunk(hunk); if (deletes == 0 && inserts == 0) { return; } /* Print out the line number header for this hunk */ print_number_range(',', first0, last0); outfile.println(change_letter(inserts, deletes)); /* Print new/changed lines from second file, if needed */ if (inserts != 0) { boolean inserting = true; for (int i = first1; i <= last1; i++) { /* Resume the insert, if we stopped. */ if (!inserting) { outfile.println(i - first1 + first0 + "a"); } inserting = true; /* If the file's line is just a dot, it would confuse `ed'. So output it with a double dot, and set the flag LEADING_DOT so that we will output another ed-command later to change the double dot into a single dot. */ if (".".equals(file1[i])) { outfile.println(".."); outfile.println("."); /* Now change that double dot to the desired single dot. */ outfile.println(i - first1 + first0 + 1 + "s/^\\.\\././"); inserting = false; } else { /* Line is not `.', so output it unmodified. */ print_1_line("", file1[i]); } } /* End insert mode, if we are still in it. */ if (inserting) { outfile.println("."); } } } } /** * Read a text file into an array of String. This provides basic diff functionality. A more advanced diff utility will use specialized * objects to represent the text lines, with options to, for example, convert sequences of whitespace to a single space for comparison * purposes. */ public static String[] slurpFile(File file) throws IOException { BufferedReader rdr = new BufferedReader(new FileReader(file)); Vector<String> s = new Vector<String>(); for (;;) { String line = rdr.readLine(); if (line == null) { break; } s.addElement(line); // System.out.println("File: add line "+line); } String[] a = new String[s.size()]; s.copyInto(a); return a; } /** * Same as above, but ignore first ignoredCols cols */ public static String[] slurpFile(File file, int ignoredCols) throws IOException { BufferedReader rdr = new BufferedReader(new FileReader(file)); Vector<String> s = new Vector<String>(); for (;;) { String line = rdr.readLine(); if (line == null) { break; } s.addElement(line.substring(ignoredCols)); // System.out.println("File: add line "+line); } String[] a = new String[s.size()]; s.copyInto(a); return a; } /** * Read a text file into an array of String. This provides basic diff functionality. A more advanced diff utility will use specialized * objects to represent the text lines, with options to, for example, convert sequences of whitespace to a single space for comparison * purposes. */ public static String[] slurpFile(String file) throws IOException { return slurpFile(new File(file)); } /** * Convert a String to an array of String (use \n) */ public static String[] slurpString(String aString) throws IOException { BufferedReader rdr = new BufferedReader(new StringReader(aString)); Vector<String> s = new Vector<String>(); for (;;) { String line = rdr.readLine(); if (line == null) { break; } s.addElement(line); // System.out.println("String: add line "+line); } String[] a = new String[s.size()]; s.copyInto(a); return a; } /** * Same as above, but ignore first ignoredCols cols */ public static String[] slurpString(String aString, int ignoredCols) throws IOException { BufferedReader rdr = new BufferedReader(new StringReader(aString)); Vector<String> s = new Vector<String>(); for (;;) { String line = rdr.readLine(); if (line == null) { break; } s.addElement(line.substring(ignoredCols)); } String[] a = new String[s.size()]; s.copyInto(a); return a; } public static void main(String[] argv) throws IOException { if (argv.length <= 1) { System.out.println("Usage: DiffPrint <file1> <file2>"); System.exit(0); } String[] a = slurpFile(argv[argv.length - 2]); String[] b = slurpFile(argv[argv.length - 1]); Diff d = new Diff(a, b); boolean edstyle = "-e".equals(argv[0]); Diff.change script = d.diff_2(edstyle); if (script == null) { System.err.println("No differences"); } else { Base p = null; if (edstyle) { p = new EdPrint(a, b); } else { p = new NormalPrint(a, b); } p.print_script(script); } } // end main public static boolean diff(File aFile, String aString) throws IOException { String[] a = slurpFile(aFile); String[] b = slurpString(aString); Diff d = new Diff(a, b); Diff.change script = d.diff_2(false); if (script == null) { // No differences return false; } else { Base p = null; p = new NormalPrint(a, b); p.print_script(script); return true; } } public static boolean diff(String aString, String anOtherString) throws IOException { String[] a = slurpString(aString); String[] b = slurpString(anOtherString); Diff d = new Diff(a, b); Diff.change script = d.diff_2(false); if (script == null) { // No differences return false; } else { Base p = null; p = new NormalPrint(a, b); p.print_script(script); return true; } } public static boolean diff(File aFile, String aString, int ignoredCols) throws IOException { String[] a = slurpFile(aFile, ignoredCols); String[] b = slurpString(aString, ignoredCols); Diff d = new Diff(a, b); Diff.change script = d.diff_2(false); if (script == null) { // No differences return false; } else { Base p = null; p = new NormalPrint(a, b); p.print_script(script); return true; } } }