KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > finalist > util > Diff


1 /* Copyright (C) 2003 Finalist IT Group
2  *
3  * This file is part of JAG - the Java J2EE Application Generator
4  *
5  * JAG is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * JAG is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with JAG; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */

17
18 package com.finalist.util;
19
20 import com.finalist.jaggenerator.HtmlContentPopUp;
21
22 import java.io.*;
23 import java.util.*;
24
25 /**
26  * This class performs a 'diff' on two files: i.e. compares the two files and returns a result
27  * containing information about the lines that differ.
28  * <p>
29  * <b>NOTE:</b> This diff tool does not use the same algorithm as the familiar command-line diff,
30  * so the results will not always be identical.
31  *
32  * @todo The output of the diff report if hardcoded to HTML, for the time being.
33  * @todo It would be nice if performDiff() generated XML and createReport() used XSLT..
34  *
35  * @author Michael O'Connor - Finalist IT Group.
36  */

37 public class Diff {
38    private File file1;
39    private File file2;
40    private BufferedReader in1;
41    private BufferedReader in2;
42    private HashMap lineCounter = new HashMap(2);
43
44    private static final String JavaDoc REPORT_HEADER =
45          "<html><head><style type='text/css'>" +
46          "body, table { font-size: 9px; font-family: Verdana, Arial, Helvetica, sans-serif }" +
47          "font.file1 {color:#ff0000;}" +
48          "font.file1-code {color:#ff0000; font-family: 'courier new',monospace;}" +
49          "font.file2 {color:#0000ff;}" +
50          "font.file2-code {color:#0000ff; font-family: 'courier new',monospace;}" +
51          "</style></head><body>" +
52          "<table><tr><td width='30'><font class='file1'>&lt;</td><td>";
53
54    /**
55     * Creates a new Diff, given two files that will be compared.
56     *
57     * @param file1 the first file.
58     * @param file2 the second file.
59     * @throws IOException if either of the files doesn't exist.
60     */

61    public Diff(File file1, File file2) throws IOException {
62       this.file1 = file1;
63       this.file2 = file2;
64       if (!file1.exists()) {
65          throw new IOException("File " + file1 + " doesn't exist!");
66       }
67       if (!file2.exists()) {
68          throw new IOException("File " + file2 + " doesn't exist!");
69       }
70       in1 = new BufferedReader(new FileReader(file1));
71       in2 = new BufferedReader(new FileReader(file2));
72       lineCounter.put(in1, new Integer JavaDoc(1));
73       lineCounter.put(in2, new Integer JavaDoc(1));
74    }
75
76    /**
77     * Performs the diff on the two files specified in the constructor, returning a
78     * formatted human-readable report (HTML, in this case).
79     *
80     * @return a String representation of the diff report, or <code>null</code> if the
81     * two files were identical (excluding whitespace differences).
82     * @throws IOException if the files couldn't be read.
83     */

84    public String JavaDoc performDiff() throws IOException {
85       List diffLines = getDiffLines();
86       return createReport(diffLines);
87    }
88
89    /**
90     * Performs the diff and returns the result as a List of DiffConflictLine objects.
91     *
92     * @return
93     * @throws IOException if the files couldn't be read.
94     */

95    public List getDiffLines() throws IOException {
96       ArrayList diffLines = new ArrayList();
97       try {
98          DiffConflictLine line1 = nextLine(in1);
99          DiffConflictLine line2 = nextLine(in2);
100          while (!(line1.isEof() && line2.isEof())) {
101             if (line1.isEof()) {
102                diffLines.add(line2);
103                line2 = nextLine(in2);
104                continue;
105             } else if (line2.isEof()) {
106                diffLines.add(line1);
107                line1 = nextLine(in1);
108                continue;
109             }
110             if (line1.lineEquals(line2)) {
111                //System.out.println("equals: current=" + line1.getLine() + "-x-" + line2.getLine());
112
line1 = nextLine(in1);
113                line2 = nextLine(in2);
114             } else {
115                DiffConflictLine conflictLine = null;
116                if (containsLine(file2, line1.getLine(), line2.getLineNumber())) {
117                   //System.out.println("a is in #2: current=" + line1.getLine() + "-x-" + line2.getLine());
118
MatchResult betterMatch = bestMatch(line1.getLineNumber(), line2.getLineNumber());
119                   //System.out.println("current=" + line1.getLine() + "-x-" + line2.getLine() + "\tbetterMatch? " + betterMatch);
120
if (betterMatch != null) {
121                      diffLines.add(line1);
122                      while(line1.getLineNumber() < betterMatch.endFile1Sequence) {
123                         line1 = nextLine(in1);
124                         //System.out.println(">>adding '" + line1 + "' from file#1 to conflicts");
125
diffLines.add(line1);
126                      }
127                      line1 = nextLine(in1);
128                      continue;
129                   } else {
130                      conflictLine = line2;
131                      line2 = nextLine(in2);
132                   }
133                } else {
134                   //System.out.println("a isn't in #2: current=" + line1.getLine() + "-x-" + line2.getLine());
135
conflictLine = line1;
136                   line1 = nextLine(in1);
137                }
138                diffLines.add(conflictLine);
139             }
140          }
141          //new HtmlContentPopUp(null, "Diff report:", true, createReport(diffLines)).show();
142

143       } finally {
144          if (in1 != null) try { in1.close(); } catch (IOException _) {}
145          if (in2 != null) try { in2.close(); } catch (IOException _) {}
146       }
147       return diffLines;
148    }
149
150    /**
151     * Finds the longest sequence of lines in file#1 starting with line# 'count1',
152     * which matches a sequence in file#2 starting with line# 'count2'.
153     * A match is deemed valid if the distance from count1 to the start of the match is less than
154     * the length of the sequence.
155     * @param count1
156     * @param count2
157     * @return
158     */

159    private MatchResult bestMatch(int count1, int count2) {
160       int matchDistance = count1;
161       int matchLength = 0;
162       BufferedReader in1 = null;
163       BufferedReader in2 = null;
164       try {
165          in1 = new BufferedReader(new FileReader(file1));
166          in2 = new BufferedReader(new FileReader(file2));
167
168          //1: read through upto the count1 and count2 starting points:
169
for (int i = 1; i < count1; i++) {
170             in1.readLine();
171          }
172          for (int i = 1; i < count2; i++) {
173             in2.readLine();
174          }
175
176          //2: read through file#1 until a match is found with the line from file#2:
177
String JavaDoc s1 = in1.readLine();
178          String JavaDoc s2 = in2.readLine();
179          while (s1 != null) {
180             if (s1.trim().equals(s2.trim())) {
181                break;
182             }
183             s1 = in1.readLine();
184             count1++;
185          }
186          if (s1 == null) {
187             //no match was found!
188
return null;
189          }
190          matchDistance = count1 - matchDistance;
191
192          //3: follow through the match until it stops:
193
while (!(s1 == null || s2 == null) &&
194                s1.trim().equals(s2.trim())) {
195             matchLength++;
196             s1 = in1.readLine();
197             s2 = in2.readLine();
198          }
199       } catch (FileNotFoundException e) {
200          //this won't happen!
201
} catch (IOException e) {
202          e.printStackTrace();
203       } finally {
204          if (in1 != null) try { in1.close(); } catch (IOException _) {}
205          if (in2 != null) try { in2.close(); } catch (IOException _) {}
206       }
207       if (matchLength < matchDistance) {
208          return null;
209       }
210
211       return new MatchResult(matchLength, count1 - 1);
212    }
213
214    private boolean containsLine(File file, String JavaDoc line, int startPos) {
215       int lineCount = 0;
216       BufferedReader in = null;
217       try {
218          in = new BufferedReader(new FileReader(file));
219          String JavaDoc s = in.readLine();
220          lineCount++;
221          while (s != null) {
222             if (lineCount > startPos && s != null && line.trim().equals(s.trim())) {
223                return true;
224             }
225             s = in.readLine();
226             lineCount++;
227          }
228       } catch (FileNotFoundException e) {
229          //this won't happen!
230
} catch (IOException e) {
231          e.printStackTrace();
232       } finally {
233          if (in != null) try { in.close(); } catch (IOException _) {}
234       }
235       return false;
236    }
237
238    private DiffConflictLine nextLine(BufferedReader reader) throws IOException {
239       String JavaDoc line = "";
240       int lineNumber = currentLineNumber(reader);
241       while (line != null && "".equals(line.trim())) {
242          line = reader.readLine();
243          lineNumber++;
244       }
245       lineCounter.put(reader, new Integer JavaDoc(lineNumber));
246       return line == null ? DiffConflictLine.EOF : new DiffConflictLine(reader == in1, lineNumber - 1, line);
247    }
248
249    private int currentLineNumber(BufferedReader reader) {
250       return ((Integer JavaDoc) lineCounter.get(reader)).intValue();
251    }
252
253    private String JavaDoc createReport(List diffLines) throws IOException {
254       if (diffLines.isEmpty()) {
255          return null;
256       }
257       StringBuffer JavaDoc report = new StringBuffer JavaDoc(REPORT_HEADER);
258       report.append(file1.getCanonicalPath());
259       report.append("</font></td></tr><tr><td width='30'><font class='file2'>&gt;</td><td>");
260       report.append(file2.getCanonicalPath()).append("</font></td></tr></table><br>");
261       ArrayList temp = new ArrayList();
262       for (int i = 0; i < diffLines.size(); i++) {
263          DiffConflictLine line = (DiffConflictLine) diffLines.get(i);
264          DiffConflictLine next = (i == diffLines.size() - 1) ? null : (DiffConflictLine) diffLines.get(i + 1);
265          if (line.precedes(next)) {
266             DiffConflictLine last = lastLine(temp);
267             if (last != null && !last.precedes(line)) {
268                reportLineGroup(report, temp);
269             }
270             temp.add(line);
271             continue;
272          }
273          if (temp.isEmpty()) {
274             reportSingleLine(report, line);
275          } else {
276             DiffConflictLine last = lastLine(temp);
277             if (last.precedes(line)) {
278                temp.add(line);
279             } else {
280                reportLineGroup(report, temp);
281                temp.add(line);
282             }
283          }
284       }
285       if (temp.size() > 1) {
286          reportLineGroup(report, temp);
287       } else if (temp.size() == 1) {
288          reportSingleLine(report, (DiffConflictLine) temp.get(0));
289       }
290
291       report.append("</body></html>");
292       return report.toString();
293    }
294
295    private void reportSingleLine(StringBuffer JavaDoc report, DiffConflictLine line) {
296       report.append("<font class=");
297       report.append(line.isFirstFile() ? "'file1'" : "'file2'");
298       report.append(">").append(line.getLineNumber()).append("</font><br>");
299       report.append(line.toString());
300    }
301
302    private void reportLineGroup(StringBuffer JavaDoc report, ArrayList temp) {
303       DiffConflictLine last = lastLine(temp);
304       DiffConflictLine first = (DiffConflictLine) temp.get(0);
305       report.append("<font class=");
306       report.append(first.isFirstFile() ? "'file1'" : "'file2'");
307       report.append(">").append(first.getLineNumber()).append(',').append(last.getLineNumber());
308       report.append("</font><br>");
309       Iterator j = temp.iterator();
310       while (j.hasNext()) {
311          DiffConflictLine groupedLine = (DiffConflictLine) j.next();
312          report.append(groupedLine.toString());
313       }
314       temp.clear();
315    }
316
317    private DiffConflictLine lastLine(List group) {
318       return (group == null || group.isEmpty()) ? null : (DiffConflictLine) group.get(group.size() - 1);
319    }
320
321    /**
322     * For testing purposes - I distance off with JUnit, honestly - but it's too slow!
323     *
324     * @param args [1] file#1, [2] file#2.
325     */

326    public static void main(String JavaDoc[] args) {
327       try {
328          Diff diff = new Diff(new File(args[0]), new File(args[1]));
329          System.out.println(diff.performDiff());
330       } catch (IOException e) {
331          e.printStackTrace();
332       }
333    }
334
335 }
336
337 class MatchResult {
338    public MatchResult(int length, int endFile1Sequence) {
339       this.length = length;
340       this.endFile1Sequence = endFile1Sequence;
341    }
342
343    int endFile1Sequence;
344    int length;
345
346    public String JavaDoc toString() {
347       return "Match: end#1=" + endFile1Sequence + ", length=" + length;
348    }
349
350 }
351
Popular Tags