Java tutorial
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package com.cloudbees.diff; import org.apache.commons.io.IOUtils; import java.io.*; import java.util.List; /** * Unified diff engine. * * @author Maros Sandor */ final class UnifiedDiff { private final TextDiffInfo diffInfo; private final int numContextLine; private final String newline; private BufferedReader baseReader; private BufferedReader modifiedReader; private boolean baseEndsWithNewline; private boolean modifiedEndsWithNewline; private int currentBaseLine; private int currentModifiedLine; public UnifiedDiff(TextDiffInfo diffInfo, int numContextLine) { this.diffInfo = diffInfo; this.numContextLine = numContextLine; currentBaseLine = 1; currentModifiedLine = 1; this.newline = System.getProperty("line.separator"); } private static void copyStreamsCloseAll(Writer writer, Reader reader) throws IOException { IOUtils.copy(reader, writer); writer.close(); reader.close(); } public String computeDiff() throws IOException { baseReader = checkEndingNewline(diffInfo.createFirstReader(), true); modifiedReader = checkEndingNewline(diffInfo.createSecondReader(), false); StringBuilder buffer = new StringBuilder(); buffer.append("--- "); buffer.append(diffInfo.getName1()); buffer.append(newline); buffer.append("+++ "); buffer.append(diffInfo.getName2()); buffer.append(newline); List<Difference> diffs = diffInfo.getDifferences(); for (int currentDifference = 0; currentDifference < diffs.size();) { // the new hunk will span differences from currentDifference to lastDifference (exclusively) int lastDifference = getLastIndex(currentDifference); Hunk hunk = computeHunk(currentDifference, lastDifference); dumpHunk(buffer, hunk); currentDifference = lastDifference; } return buffer.toString(); } private BufferedReader checkEndingNewline(Reader reader, boolean isBase) throws IOException { StringWriter sw = new StringWriter(); copyStreamsCloseAll(sw, reader); String s = sw.toString(); char endingChar = s.length() == 0 ? 0 : s.charAt(s.length() - 1); if (isBase) { baseEndsWithNewline = endingChar == '\n' || endingChar == '\r'; } else { modifiedEndsWithNewline = endingChar == '\n' || endingChar == '\r'; } return new BufferedReader(new StringReader(s)); } private Hunk computeHunk(int firstDifference, int lastDifference) throws IOException { Hunk hunk = new Hunk(); Difference firstDiff = diffInfo.getDifferences().get(firstDifference); int contextLines = numContextLine; int skipLines; if (firstDiff.getType() == Difference.ADD) { if (contextLines > firstDiff.getFirstStart()) { contextLines = firstDiff.getFirstStart(); } skipLines = firstDiff.getFirstStart() - contextLines - currentBaseLine + 1; } else { if (contextLines >= firstDiff.getFirstStart()) { contextLines = firstDiff.getFirstStart() - 1; } skipLines = firstDiff.getFirstStart() - contextLines - currentBaseLine; } // move file pointers to the beginning of hunk while (skipLines-- > 0) { readLine(baseReader); readLine(modifiedReader); } hunk.baseStart = currentBaseLine; hunk.modifiedStart = currentModifiedLine; // output differences with possible contextual lines in-between for (int i = firstDifference; i < lastDifference; i++) { Difference diff = diffInfo.getDifferences().get(i); writeContextLines(hunk, diff.getFirstStart() - currentBaseLine + ((diff.getType() == Difference.ADD) ? 1 : 0)); if (diff.getFirstEnd() > 0) { int n = diff.getFirstEnd() - diff.getFirstStart() + 1; outputLines(hunk, baseReader, "-", n); hunk.baseCount += n; if (!baseEndsWithNewline && i == diffInfo.getDifferences().size() - 1 && diff.getFirstEnd() == currentBaseLine - 1) { hunk.lines.add(Hunk.ENDING_NEWLINE); } } if (diff.getSecondEnd() > 0) { int n = diff.getSecondEnd() - diff.getSecondStart() + 1; outputLines(hunk, modifiedReader, "+", n); hunk.modifiedCount += n; if (!modifiedEndsWithNewline && i == diffInfo.getDifferences().size() - 1 && diff.getSecondEnd() == currentModifiedLine - 1) { hunk.lines.add(Hunk.ENDING_NEWLINE); } } } // output bottom context lines writeContextLines(hunk, numContextLine); if (hunk.modifiedCount == 0) hunk.modifiedStart = 0; // empty file if (hunk.baseCount == 0) hunk.baseStart = 0; // empty file return hunk; } private void writeContextLines(Hunk hunk, int count) throws IOException { while (count-- > 0) { String line = readLine(baseReader); if (line == null) return; hunk.lines.add(" " + line); readLine(modifiedReader); // move the modified file pointer as well hunk.baseCount++; hunk.modifiedCount++; } } private String readLine(BufferedReader reader) throws IOException { String s = reader.readLine(); if (s != null) { if (reader == baseReader) currentBaseLine++; if (reader == modifiedReader) currentModifiedLine++; } return s; } private void outputLines(Hunk hunk, BufferedReader reader, String mode, int n) throws IOException { while (n-- > 0) { String line = readLine(reader); hunk.lines.add(mode + line); } } private int getLastIndex(int firstIndex) { int contextLines = numContextLine * 2; List<Difference> diffs = diffInfo.getDifferences(); for (++firstIndex; firstIndex < diffs.size(); firstIndex++) { Difference prevDiff = diffs.get(firstIndex - 1); Difference currentDiff = diffs.get(firstIndex); int prevEnd = 1 + ((prevDiff.getType() == Difference.ADD) ? prevDiff.getFirstStart() : prevDiff.getFirstEnd()); int curStart = (currentDiff.getType() == Difference.ADD) ? (currentDiff.getFirstStart() + 1) : currentDiff.getFirstStart(); if (curStart - prevEnd > contextLines) { break; } } return firstIndex; } private void dumpHunk(StringBuilder buffer, Hunk hunk) { buffer.append("@@ -"); buffer.append(Integer.toString(hunk.baseStart)); if (hunk.baseCount != 1) { buffer.append(","); buffer.append(Integer.toString(hunk.baseCount)); } buffer.append(" +"); buffer.append(Integer.toString(hunk.modifiedStart)); if (hunk.modifiedCount != 1) { buffer.append(","); buffer.append(Integer.toString(hunk.modifiedCount)); } buffer.append(" @@"); buffer.append(newline); for (String line : hunk.lines) { buffer.append(line); buffer.append(newline); } } }