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-2007 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: package org.netbeans.modules.diff.builtin.visualizer;
042:
043: import org.netbeans.api.diff.Difference;
044: import org.netbeans.modules.diff.builtin.Hunk;
045:
046: import java.io.*;
047:
048: /**
049: * Unified diff engine.
050: *
051: * @author Maros Sandor
052: */
053: final class UnifiedDiff {
054:
055: private final TextDiffVisualizer.TextDiffInfo diffInfo;
056: private BufferedReader baseReader;
057: private BufferedReader modifiedReader;
058: private final String newline;
059: private boolean baseEndsWithNewline;
060: private boolean modifiedEndsWithNewline;
061:
062: private int currentBaseLine;
063: private int currentModifiedLine;
064:
065: public UnifiedDiff(TextDiffVisualizer.TextDiffInfo diffInfo) {
066: this .diffInfo = diffInfo;
067: currentBaseLine = 1;
068: currentModifiedLine = 1;
069: this .newline = System.getProperty("line.separator");
070: }
071:
072: public String computeDiff() throws IOException {
073: baseReader = checkEndingNewline(diffInfo.createFirstReader(),
074: true);
075: modifiedReader = checkEndingNewline(diffInfo
076: .createSecondReader(), false);
077:
078: StringBuilder buffer = new StringBuilder();
079: buffer.append("--- ");
080: buffer.append(diffInfo.getName1());
081: buffer.append(newline);
082: buffer.append("+++ ");
083: buffer.append(diffInfo.getName2());
084: buffer.append(newline);
085:
086: Difference[] diffs = diffInfo.getDifferences();
087:
088: for (int currentDifference = 0; currentDifference < diffs.length;) {
089: // the new hunk will span differences from currentDifference to lastDifference (exclusively)
090: int lastDifference = getLastIndex(currentDifference);
091: Hunk hunk = computeHunk(currentDifference, lastDifference);
092: dumpHunk(buffer, hunk);
093: currentDifference = lastDifference;
094: }
095:
096: return buffer.toString();
097: }
098:
099: private BufferedReader checkEndingNewline(Reader reader,
100: boolean isBase) throws IOException {
101: StringWriter sw = new StringWriter();
102: copyStreamsCloseAll(sw, reader);
103: String s = sw.toString();
104: char endingChar = s.length() == 0 ? 0 : s
105: .charAt(s.length() - 1);
106: if (isBase) {
107: baseEndsWithNewline = endingChar == '\n'
108: || endingChar == '\r';
109: } else {
110: modifiedEndsWithNewline = endingChar == '\n'
111: || endingChar == '\r';
112: }
113: return new BufferedReader(new StringReader(s));
114: }
115:
116: private Hunk computeHunk(int firstDifference, int lastDifference)
117: throws IOException {
118:
119: Hunk hunk = new Hunk();
120:
121: Difference firstDiff = diffInfo.getDifferences()[firstDifference];
122: int contextLines = diffInfo.getContextNumLines();
123:
124: int skipLines;
125: if (firstDiff.getType() == Difference.ADD) {
126: if (contextLines > firstDiff.getFirstStart()) {
127: contextLines = firstDiff.getFirstStart();
128: }
129: skipLines = firstDiff.getFirstStart() - contextLines
130: - currentBaseLine + 1;
131: } else {
132: if (contextLines >= firstDiff.getFirstStart()) {
133: contextLines = firstDiff.getFirstStart() - 1;
134: }
135: skipLines = firstDiff.getFirstStart() - contextLines
136: - currentBaseLine;
137: }
138:
139: // move file pointers to the beginning of hunk
140: while (skipLines-- > 0) {
141: readLine(baseReader);
142: readLine(modifiedReader);
143: }
144:
145: hunk.baseStart = currentBaseLine;
146: hunk.modifiedStart = currentModifiedLine;
147:
148: // output differences with possible contextual lines in-between
149: for (int i = firstDifference; i < lastDifference; i++) {
150: Difference diff = diffInfo.getDifferences()[i];
151: writeContextLines(hunk, diff.getFirstStart()
152: - currentBaseLine
153: + ((diff.getType() == Difference.ADD) ? 1 : 0));
154:
155: if (diff.getFirstEnd() > 0) {
156: int n = diff.getFirstEnd() - diff.getFirstStart() + 1;
157: outputLines(hunk, baseReader, "-", n);
158: hunk.baseCount += n;
159: if (!baseEndsWithNewline
160: && i == diffInfo.getDifferences().length - 1
161: && diff.getFirstEnd() == currentBaseLine - 1) {
162: hunk.lines.add(Hunk.ENDING_NEWLINE);
163: }
164: }
165: if (diff.getSecondEnd() > 0) {
166: int n = diff.getSecondEnd() - diff.getSecondStart() + 1;
167: outputLines(hunk, modifiedReader, "+", n);
168: hunk.modifiedCount += n;
169: if (!modifiedEndsWithNewline
170: && i == diffInfo.getDifferences().length - 1
171: && diff.getSecondEnd() == currentModifiedLine - 1) {
172: hunk.lines.add(Hunk.ENDING_NEWLINE);
173: }
174: }
175: }
176:
177: // output bottom context lines
178: writeContextLines(hunk, diffInfo.getContextNumLines());
179:
180: if (hunk.modifiedCount == 0)
181: hunk.modifiedStart = 0; // empty file
182: if (hunk.baseCount == 0)
183: hunk.baseStart = 0; // empty file
184: return hunk;
185: }
186:
187: private void writeContextLines(Hunk hunk, int count)
188: throws IOException {
189: while (count-- > 0) {
190: String line = readLine(baseReader);
191: if (line == null)
192: return;
193: hunk.lines.add(" " + line);
194: readLine(modifiedReader); // move the modified file pointer as well
195: hunk.baseCount++;
196: hunk.modifiedCount++;
197: }
198: }
199:
200: private String readLine(BufferedReader reader) throws IOException {
201: String s = reader.readLine();
202: if (s != null) {
203: if (reader == baseReader)
204: currentBaseLine++;
205: if (reader == modifiedReader)
206: currentModifiedLine++;
207: }
208: return s;
209: }
210:
211: private void outputLines(Hunk hunk, BufferedReader reader,
212: String mode, int n) throws IOException {
213: while (n-- > 0) {
214: String line = readLine(reader);
215: hunk.lines.add(mode + line);
216: }
217: }
218:
219: private int getLastIndex(int firstIndex) {
220: int contextLines = diffInfo.getContextNumLines() * 2;
221: Difference[] diffs = diffInfo.getDifferences();
222: for (++firstIndex; firstIndex < diffs.length; firstIndex++) {
223: Difference prevDiff = diffs[firstIndex - 1];
224: Difference currentDiff = diffs[firstIndex];
225: int prevEnd = 1 + ((prevDiff.getType() == Difference.ADD) ? prevDiff
226: .getFirstStart()
227: : prevDiff.getFirstEnd());
228: int curStart = (currentDiff.getType() == Difference.ADD) ? (currentDiff
229: .getFirstStart() + 1)
230: : currentDiff.getFirstStart();
231: if (curStart - prevEnd > contextLines) {
232: break;
233: }
234: }
235: return firstIndex;
236: }
237:
238: private void dumpHunk(StringBuilder buffer, Hunk hunk) {
239: buffer.append("@@ -");
240: buffer.append(Integer.toString(hunk.baseStart));
241: if (hunk.baseCount != 1) {
242: buffer.append(",");
243: buffer.append(Integer.toString(hunk.baseCount));
244: }
245: buffer.append(" +");
246: buffer.append(Integer.toString(hunk.modifiedStart));
247: if (hunk.modifiedCount != 1) {
248: buffer.append(",");
249: buffer.append(Integer.toString(hunk.modifiedCount));
250: }
251: buffer.append(" @@");
252: buffer.append(newline);
253: for (String line : hunk.lines) {
254: buffer.append(line);
255: buffer.append(newline);
256: }
257: }
258:
259: private static void copyStreamsCloseAll(Writer writer, Reader reader)
260: throws IOException {
261: char[] buffer = new char[4096];
262: int n;
263: while ((n = reader.read(buffer)) != -1) {
264: writer.write(buffer, 0, n);
265: }
266: writer.close();
267: reader.close();
268: }
269: }
|