001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.junit.diff;
043:
044: import java.io.File;
045: import java.io.FileOutputStream;
046: import java.io.FileReader;
047: import java.io.IOException;
048: import java.io.LineNumberReader;
049: import java.io.PrintStream;
050: import java.util.ArrayList;
051: import java.util.List;
052:
053: /**
054: * Line Diff with formated textual output.
055: * Number of context lines in output is configurable through system property 'nbjunit.linediff.context'.
056: *
057: * @author ehucka
058: */
059: public class LineDiff implements Diff {
060:
061: public static int CONTEXT = 3;
062:
063: boolean ignoreCase;
064: boolean ignoreEmptyLines;
065: int contextLines;
066:
067: public LineDiff() {
068: this (false, false);
069: }
070:
071: public LineDiff(boolean ignoreCase) {
072: this (ignoreCase, false);
073: }
074:
075: public LineDiff(boolean ignoreCase, boolean ignoreEmptyLines) {
076: this .ignoreCase = ignoreCase;
077: this .ignoreEmptyLines = ignoreEmptyLines;
078: //set number of context lines
079: String value = System.getProperty("nbjunit.linediff.context");
080: int number = -1;
081: if (value != null) {
082: try {
083: number = Integer.parseInt(value);
084: } catch (NumberFormatException ex) {
085: ex.printStackTrace();
086: }
087: }
088: if (number < 0) {
089: number = CONTEXT;
090: }
091: contextLines = number;
092: }
093:
094: public boolean getIgnoreCase() {
095: return ignoreCase;
096: }
097:
098: /**
099: * @param l1 first line to compare
100: * @param l2 second line to compare
101: * @return true if lines equal
102: */
103: protected boolean compareLines(String l1, String l2) {
104: if (getIgnoreCase()) {
105: if (l1.equalsIgnoreCase(l2))
106: return true;
107: } else {
108: if (l1.equals(l2))
109: return true;
110: }
111: return false;
112: }
113:
114: public int getNContextLines() {
115: return contextLines;
116: }
117:
118: /**
119: * @param ref first file to compare
120: * @param pass second file to compare
121: * @param diff difference file, caller can pass null value, when results are not needed.
122: * @return true iff files differ
123: * @throws IOException when readin of files fails
124: */
125: public boolean diff(String ref, String pass, String diff)
126: throws IOException {
127: File fFirst = new File(ref);
128: File fSecond = new File(pass);
129: File fDiff = null != diff ? new File(diff) : null;
130: return diff(fFirst, fSecond, fDiff);
131: }
132:
133: /**
134: * @param refFile first file to compare -- ref
135: * @param passFile second file to compare -- golden
136: * @param diffFile difference file, caller can pass null value, when results are not needed.
137: * @return true iff files differ
138: */
139: public boolean diff(File refFile, File passFile, File diffFile)
140: throws IOException {
141: LineNumberReader first = new LineNumberReader(new FileReader(
142: refFile));
143: LineNumberReader second = new LineNumberReader(new FileReader(
144: passFile));
145: String line;
146:
147: String[] refLines, passLines;
148:
149: //read golden file
150: List<String> tmp = new ArrayList<String>(64);
151: while ((line = second.readLine()) != null) {
152: if (ignoreEmptyLines && line.trim().length() == 0) {
153: continue;
154: }
155: tmp.add(line);
156: }
157: passLines = tmp.toArray(new String[tmp.size()]);
158: tmp.clear();
159: second.close();
160: //read ref file
161: tmp = new ArrayList<String>(64);
162: while ((line = first.readLine()) != null) {
163: if (ignoreEmptyLines && line.trim().length() == 0) {
164: continue;
165: }
166: tmp.add(line);
167: }
168: refLines = tmp.toArray(new String[tmp.size()]);
169: tmp.clear();
170: first.close();
171: //collect differences
172: List<Result> results = findDifferences(passLines, refLines);
173: //without differences it can be finished here
174: if (results.size() == 0)
175: return false;
176: if (diffFile == null)
177: return results.size() > 0;
178: //merge
179: merge(results);
180: //print
181: printResults(passLines, refLines, results, diffFile);
182: return results.size() > 0;
183: }
184:
185: /**
186: * compare right to left lines (pass to ref) and left to right lines
187: *
188: */
189: private List<Result> findDifferences(String[] passLines,
190: String[] refLines) {
191: int stepLeft = 0;
192: int stepRight = 0;
193: boolean jump = false;
194: //test is left, pass is right
195: List<Result> results = new ArrayList<Result>(64);
196: //start right
197: boolean right = true;
198:
199: while (stepRight < passLines.length
200: || stepLeft < refLines.length) {
201: if (right) {
202: if (stepRight >= passLines.length) {
203: if (stepLeft < refLines.length) {
204: results.add(new Result(stepLeft,
205: refLines.length, stepRight, true)); //add new lines
206: }
207: break;
208: }
209: String v = passLines[stepRight];
210: int found = find(v, refLines, stepLeft);
211: if (found >= 0) {
212: if (found > stepLeft) {
213: if (!jump && found - stepLeft >= 2) { //could be wrong jump - try tp skip left
214: jump = true;
215: right = false;
216: continue;
217: } else {
218: results.add(new Result(stepLeft, found,
219: stepRight, true)); //add new lines
220: }
221: }
222: stepLeft = found + 1;
223: } else {
224: results.add(new Result(stepRight, stepRight + 1,
225: false)); //add one missing
226: //switch to left
227: right = false;
228: }
229: stepRight++;
230: } else {
231: if (stepLeft >= refLines.length) {
232: if (stepRight < passLines.length) {
233: results.add(new Result(stepRight,
234: passLines.length - 1, false)); //add missing lines
235: }
236: break;
237: }
238: String v = refLines[stepLeft];
239: int found = find(v, passLines, stepRight);
240: if (found >= 0) {
241: if (!jump && found - stepRight >= 2) { //eliminate wrong jumps
242: jump = true;
243: right = true;
244: continue;
245: }
246: if (found > stepRight) {
247: results
248: .add(new Result(stepRight, found, false)); //add missing lines
249: }
250: stepRight = found + 1;
251: //switch to right
252: right = true;
253: } else {
254: results.add(new Result(stepLeft, stepLeft + 1,
255: stepRight, true)); //add one new
256: right = true;
257: }
258: stepLeft++;
259: }
260: jump = false;
261: }
262: return results;
263: }
264:
265: private void printResults(String[] passLines, String[] refLines,
266: List<Result> results, File diffFile) throws IOException {
267: int numLength = (refLines.length > passLines.length) ? String
268: .valueOf(refLines.length).length() : String.valueOf(
269: passLines.length).length();
270: PrintStream ps = new PrintStream(new FileOutputStream(diffFile));
271: boolean precontext = false;
272: for (int i = 0; i < results.size(); i++) {
273: Result rs = results.get(i);
274: if (!precontext) {
275: int si = rs.passIndex - contextLines;
276: if (si < 0)
277: si = 0;
278: for (int j = si; j < rs.passIndex; j++) {
279: printContext(passLines, ps, j, numLength);
280: }
281: } else {
282: precontext = false;
283: }
284: results.get(i).print(passLines, refLines, ps, numLength);
285: int e1 = (rs.newLine) ? rs.passIndex : rs.end;
286: int e2 = e1 + contextLines;
287: if (i < results.size() - 1
288: && results.get(i + 1).passIndex < e2) {
289: e2 = results.get(i + 1).passIndex;
290: precontext = true;
291: } else if (e2 > passLines.length) {
292: e2 = passLines.length;
293: }
294: for (int j = e1; j < e2; j++) {
295: printContext(passLines, ps, j, numLength);
296: }
297: }
298: ps.close();
299: }
300:
301: private int find(String value, String[] lines, int startIndex) {
302: for (int i = startIndex; i < lines.length; i++) {
303: if (compareLines(value, lines[i])) {
304: return i;
305: }
306: }
307: return -1;
308: }
309:
310: private void merge(List<Result> results) {
311: for (int i = 0; i < results.size() - 1; i++) {
312: if (results.get(i).newLine && results.get(i + 1).newLine
313: && results.get(i).end == results.get(i + 1).start) {
314: results.get(i).end = results.get(i + 1).end;
315: results.remove(i + 1);
316: i--;
317: }
318: }
319: }
320:
321: private void printContext(String[] passLines, PrintStream ps,
322: int lineNumber, int numLength) {
323: String num = String.valueOf(lineNumber + 1);
324: int rem = numLength + 1 - num.length();
325: for (int j = 0; j < rem; j++)
326: ps.print(' ');
327: ps.print(num);
328: ps.print(" ");
329: ps.println(passLines[lineNumber]);
330: }
331:
332: static class Result {
333:
334: boolean newLine = false;
335: int start, end;
336: int passIndex;
337:
338: public Result(int start, int end, int passIndex, boolean newLine) {
339: this .start = start;
340: this .end = end;
341: this .passIndex = passIndex;
342: this .newLine = newLine;
343: }
344:
345: public Result(int start, int end, boolean newLine) {
346: this .passIndex = start;
347: this .start = start;
348: this .end = end;
349: this .newLine = newLine;
350: }
351:
352: public void print(String[] passLines, String[] refLines,
353: PrintStream ps, int numLength) {
354: for (int i = start; i < end; i++) {
355: if (newLine) {
356: for (int j = 0; j < numLength + 2; j++)
357: ps.print(' ');
358: ps.print("+ ");
359: ps.println(refLines[i]);
360: } else {
361: String num = String.valueOf(i + 1);
362: int rem = numLength + 1 - num.length();
363: for (int j = 0; j < rem; j++)
364: ps.print(' ');
365: ps.print(num);
366: ps.print(" - ");
367: ps.println(passLines[i]);
368: }
369: }
370: }
371: }
372: }
|