/*
* (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.netbeans.lib.cvsclient.file;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import org.netbeans.lib.cvsclient.util.BugLog;
import org.netbeans.lib.cvsclient.util.ByteArray;
/**
* File Processor that handles processing of rcs-diff response. (aka diff -n format) The original file is read and a merge is created. At
* the same time the correct line-ending is processed. It's possible to set the processor the preffered line-ending. The default is
* dependant on the platform.
*
* @author Milos Kleint
*/
public class WriteRcsDiffFilePreprocessor implements WriteTextFilePreprocessor {
private static final int CHUNK_SIZE = 32768;
private static final int READ_REMAINING = -2;
/**
* line ending that will be used when writing the final result file.
*/
private String lineEnding = System.getProperty("line.separator");
public WriteRcsDiffFilePreprocessor() {
}
/**
* Gets the value of lineending used when writing the resulting file.
*
* @return Value of property lineEnding.
*/
public String getLineEnding() {
return lineEnding;
}
/**
* Setter for lineEnding used to write the file.
*
* @param lineEnding
* New value of property lineEnding.
*/
public void setLineEnding(String lineEnding) {
this.lineEnding = lineEnding;
}
/**
* Processes the diff and merges it with the original file.
*
* @param processedInput
* the stored diff.
* @param fileToWrite
* the resulting file and the original that is merged as the same time.
* @param customOutput
* @throws IOException
* if any IO operation fails. The original file is replaced by the merge only if no excpetion is thrown.
*/
@Override
public void copyTextFileToLocation(InputStream processedInput, File fileToWrite, OutputStreamProvider customOutput) throws IOException {
// Here we read the temp file in again, doing any processing required
// (for example, unzipping). We must not convert the bytes to characters
// because this would corrupt files, that were written in an encoding
// different from the current encoding.
ReadInfo tempFileReader = null;
OutputStream out = null;
ReadInfo tempDiffReader = null;
File tempFile = null;
// BUGLOG.. assert the fileToWrite Exists.. otherwise it has no sense.
try {
tempDiffReader = new ReadInfo(new BufferedInputStream(processedInput));
tempFileReader = new ReadInfo(new BufferedInputStream(new FileInputStream(fileToWrite)));
tempFile = File.createTempFile(".#merg", "cvs"); // NOI18N
out = new BufferedOutputStream(new FileOutputStream(tempFile));
int fileStart = 0;
int diffCount = 0;
byte[] diff = tempDiffReader.readLine();
while (diff != null && diff.length > 0) {
// System.out.println("diffline=" + diff);
if (diff[0] == 'd') {
// now do delete..
int startLine = getStart(diff);
int count = getLength(diff);
// System.out.println("deleting. start =" + startLine + " count=" + count);
if (startLine >= 0 && count > 0) {
// read from file only if deletion is not some
// from the beginning..
readToLine(startLine - 1, tempFileReader, out);
readToLine(startLine - 1 + count, tempFileReader, null);
// null skips the lines..
} else {
// BugLog..
BugLog.getInstance().bug("wrong parsing.." + new String(diff));
throw new IOException(); // Interrupt the merging process so that the file is not corrupted.
}
} else if (diff[0] == 'a') {
// now add lines from the diff..
int startLine = getStart(diff);
int count = getLength(diff);
// System.out.println("adding.. start =" + startLine + " count=" + count);
if (startLine >= 0 && count > 0) {
readToLine(startLine, tempFileReader, out);
tempDiffReader.setLineNumber(0);
readToLine(count, tempDiffReader, out);
} else {
// BugLog..
BugLog.getInstance().bug("wrong parsing.." + new String(diff));
throw new IOException(); // Interrupt the merging process so that the file is not corrupted.
}
}
// now process next difference.
diff = tempDiffReader.readLine();
}
// read what's remaining..
readToLine(READ_REMAINING, tempFileReader, out);
if (tempFile != null) {
tempFileReader.close();
out.close();
InputStream in = null;
OutputStream customOutputStream = customOutput.createOutputStream();
try {
in = new BufferedInputStream(new FileInputStream(tempFile));
while (true) {
int ch = in.read();
if (ch == -1) {
break;
}
customOutputStream.write(ch);
}
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
}
}
try {
customOutputStream.close();
} catch (IOException ex) {
}
}
}
} catch (Exception exc) {
BugLog.getInstance().showException(exc);
} finally {
if (tempDiffReader != null) {
try {
tempDiffReader.close();
} catch (IOException ex) {
// ignore
}
}
if (tempFileReader != null) {
try {
tempFileReader.close();
} catch (IOException ex) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (IOException ex) {
// ignore
}
}
if (tempFile != null) {
tempFile.delete();
}
}
}
/**
* Reads lines from the reader until the specified line number is reached. Writes the lines to the writer
*
* @param finalLine
* the last line to read/write. Special value -2 reads all that is remaining in the reader.
* @param reader
* - does not accept null.
* @param writer
* the writer that the read lines are written to. Accepts null. In such a case the read lines are discarded.
*/
private void readToLine(int finalLine, final ReadInfo reader, final OutputStream out) throws IOException {
byte[] line;
while (reader.getLineNumber() < finalLine || finalLine == READ_REMAINING) {
line = reader.readLine();
if (line == null) {
// end of file..
return;
}
if (out != null) {
out.write(line);
out.write(getLineEnding().getBytes());
}
}
}
private static int indexOf(byte[] bytes, byte b) {
return indexOf(bytes, b, 0);
}
private static int indexOf(byte[] bytes, byte b, int start) {
int index = -1;
for (int i = start; i < bytes.length; i++) {
if (bytes[i] == b) {
index = i;
break;
}
}
return index;
}
/**
* Extracts the line where the diff starts.
*/
private static int getStart(byte[] diffLine) {
int spacePos = indexOf(diffLine, (byte) ' ');
if (spacePos > 0) {
String number = new String(diffLine, 1, spacePos - 1);
try {
int toReturn = Integer.parseInt(number);
return toReturn;
} catch (NumberFormatException exc) {
// System.out.println("diffLine=" + diffLine);
return -1;
}
}
// System.out.println("no space in diffline=" + diffLine);
return -1;
}
/**
* Extracts the length of the diff. For "delete" it means how many lines to delete from original. For "add" it means how many lines are
* added from the diff to original file.
*/
private static int getLength(byte[] diffLine) {
// String trimmed = diffLine.trim();
int spacePos = indexOf(diffLine, (byte) ' ');
if (spacePos > 0) {
int end = indexOf(diffLine, (byte) ' ', spacePos + 1);
if (end < 0) {
end = diffLine.length;
}
String number = new String(diffLine, spacePos + 1, end - spacePos - 1);
try {
int toReturn = Integer.parseInt(number);
return toReturn;
} catch (NumberFormatException exc) {
// System.out.println("numformat exception..=" + diffLine);
return -1;
}
}
// System.out.println("no space in diffline=" + diffLine);
return -1;
}
private static class ReadInfo {
private static final boolean crLines = "\r".equals(System.getProperty("line.separator"));
private PushbackInputStream in;
private int readLength;
private int startIndex;
private int lineNumber;
private ByteArray line;
public ReadInfo(InputStream in) {
this.in = new PushbackInputStream(in, 1);
readLength = -1;
startIndex = 0;
lineNumber = 0;
line = new ByteArray();
}
public int getLineNumber() {
return lineNumber;
}
public void setLineNumber(int lineNumber) {
this.lineNumber = lineNumber;
}
/**
* Reads a line. The line-termination bytes are not added.
*
* @return null When the end of the stream is reached.
*/
public byte[] readLine() throws IOException {
line.reset();
boolean end = false;
do {
int b = in.read();
if (b == -1) {
end = true;
break;
}
if (b == '\n') {
lineNumber++;
break;
}
if (b == '\r') {
int next = in.read();
if (next == '\n') {
lineNumber++;
break;
}
in.unread(next);
if (crLines) {
lineNumber++;
break;
}
}
line.add((byte) b);
} while (true);
byte[] bytes = line.getBytes();
if (end && bytes.length == 0) {
bytes = null;
}
return bytes;
/*
StringBuffer toReturn = new StringBuffer();
while (true) {
if (startIndex > readLength) {
// System.out.println("reading..");
readLength = reader.read(cchunk);
// System.out.println("read=" + readLength);
startIndex = 0;
}
if (startIndex >= readLength) {
readLength = -1;
if (toReturn.length() == 0) {
return null;
}
else {
lineNumber = lineNumber + 1;
return toReturn.toString();
}
}
// System.out.println("startindex = " + startIndex + " length=" + readLength);
for (int i = startIndex; i < readLength; i++) {
if (cchunk[i] == '\n') {
toReturn.append(WriteRcsDiffFilePreprocessor.this.getLineEnding());
startIndex = i + 1;
lineNumber = lineNumber + 1;
// System.out.println("linenum=" + lineNumber);
// System.out.println("line=" + toReturn.toString() + "--");
return toReturn.toString();
}
else {
// could be maybe made faster by appending the whole array when
// read all or encountering the newline..
toReturn.append(cchunk[i]);
}
}
startIndex = readLength;
}
*/
}
public void close() throws IOException {
if (in != null) {
in.close();
}
}
} // end of inner class..
}