/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.delta;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.zip.InflaterInputStream;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.io.ISVNDeltaConsumer;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* Reads diff windows from stream and feeds them to the ISVNDeltaConsumer instance.
*
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNDeltaReader {
private ByteBuffer myBuffer;
private int myHeaderBytes;
private long myLastSourceOffset;
private int myLastSourceLength;
private boolean myIsWindowSent;
private byte myVersion;
public SVNDeltaReader() {
myBuffer = ByteBuffer.allocate(4096);
myBuffer.clear();
myBuffer.limit(0);
}
public void reset(String path, ISVNDeltaConsumer consumer) throws SVNException {
// if header was read, but data was not -> fire empty window.
if (myHeaderBytes == 4 && !myIsWindowSent) {
OutputStream os = consumer.textDeltaChunk(path, SVNDiffWindow.EMPTY);
SVNFileUtil.closeFile(os);
}
myLastSourceLength = 0;
myLastSourceOffset = 0;
myHeaderBytes = 0;
myIsWindowSent = false;
myVersion = 0;
myBuffer.clear();
myBuffer.limit(0);
}
public void nextWindow(byte[] data, int offset, int length, String path, ISVNDeltaConsumer consumer) throws SVNException {
appendToBuffer(data, offset, length);
if (myHeaderBytes < 4) {
if (myBuffer.remaining() < 4) {
return;
}
if (myBuffer.get(0) != 'S' || myBuffer.get(1) != 'V' || myBuffer.get(2) != 'N' ||
(myBuffer.get(3) != '\0' && myBuffer.get(3) != '\1')) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SVNDIFF_CORRUPT_WINDOW, "Svndiff has invalid header");
SVNErrorManager.error(err, SVNLogType.DEFAULT);
}
myVersion = myBuffer.get(3);
myBuffer.position(4);
int remainging = myBuffer.remaining();
myBuffer.compact();
myBuffer.position(0);
myBuffer.limit(remainging);
myHeaderBytes = 4;
}
while(true) {
long sourceOffset = readLongOffset();
if (sourceOffset < 0) {
return;
}
int sourceLength = readOffset();
if (sourceLength < 0) {
return;
}
int targetLength = readOffset();
if (targetLength < 0) {
return;
}
int instructionsLength = readOffset();
if (instructionsLength < 0) {
return;
}
int newDataLength = readOffset();
if (newDataLength < 0) {
return;
}
if (sourceLength > 0 &&
(sourceOffset < myLastSourceOffset ||
sourceOffset + sourceLength < myLastSourceOffset + myLastSourceLength)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.SVNDIFF_CORRUPT_WINDOW, "Svndiff has backwards-sliding source views");
SVNErrorManager.error(err, SVNLogType.DEFAULT);
}
if (myBuffer.remaining() < instructionsLength + newDataLength) {
return;
}
myLastSourceOffset = sourceOffset;
myLastSourceLength = sourceLength;
SVNDiffWindow window = null;
int allDataLength = newDataLength + instructionsLength;
if (myVersion == 1) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int bufferPosition = myBuffer.position();
try {
instructionsLength = deflate(instructionsLength, out);
newDataLength = deflate(newDataLength, out);
} catch (IOException e) {
SVNDebugLog.getDefaultLog().logSevere(SVNLogType.DEFAULT, e);
SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e);
SVNErrorManager.error(errorMessage, SVNLogType.NETWORK);
}
byte[] bytes = out.toByteArray();
ByteBuffer decompressed = ByteBuffer.wrap(bytes);
decompressed.position(0);
window = new SVNDiffWindow(sourceOffset, sourceLength, targetLength, instructionsLength, newDataLength);
window.setData(decompressed);
myBuffer.position(bufferPosition);
} else {
window = new SVNDiffWindow(sourceOffset, sourceLength, targetLength, instructionsLength, newDataLength);
window.setData(myBuffer);
}
int position = myBuffer.position();
OutputStream os = consumer.textDeltaChunk(path, window);
SVNFileUtil.closeFile(os);
myBuffer.position(position + allDataLength);
int remains = myBuffer.remaining();
myIsWindowSent = true;
// then clear the buffer, shift remaining to the beginning.
myBuffer.compact();
myBuffer.position(0);
myBuffer.limit(remains);
}
}
private int deflate(int compressedLength, OutputStream out) throws IOException {
int originalPosition = myBuffer.position();
int uncompressedLength = readOffset();
// substract offset length from the total length.
if (uncompressedLength == (compressedLength - (myBuffer.position() - originalPosition))) {
int offset = myBuffer.arrayOffset() + myBuffer.position();
out.write(myBuffer.array(), offset, uncompressedLength);
} else {
byte[] uncompressedData = new byte[uncompressedLength];
byte[] compressed = myBuffer.array();
int offset = myBuffer.arrayOffset() + myBuffer.position();
InputStream in = new InflaterInputStream(new ByteArrayInputStream(compressed, offset, compressedLength));
int read = 0;
while(read < uncompressedLength) {
int r = in.read(uncompressedData, read, uncompressedLength - read);
if (r < 0) {
break;
}
read += r;
}
out.write(uncompressedData);
}
myBuffer.position(originalPosition + compressedLength);
return uncompressedLength;
}
private void appendToBuffer(byte[] data, int offset, int length) {
int limit = myBuffer.limit(); // amount of pending data?
if (myBuffer.capacity() < limit + length) {
ByteBuffer newBuffer = ByteBuffer.allocate((limit + length)*3/2);
myBuffer.position(0);
newBuffer.put(myBuffer);
myBuffer = newBuffer;
} else {
myBuffer.limit(limit + length);
myBuffer.position(limit);
}
myBuffer.put(data, offset, length);
myBuffer.position(0);
myBuffer.limit(limit + length);
}
private int readOffset() {
myBuffer.mark();
int offset = 0;
byte b;
while(myBuffer.hasRemaining()) {
b = myBuffer.get();
offset = (offset << 7) | (b & 0x7F);
if ((b & 0x80) != 0) {
continue;
}
return offset;
}
myBuffer.reset();
return -1;
}
private long readLongOffset() {
myBuffer.mark();
long offset = 0;
byte b;
while(myBuffer.hasRemaining()) {
b = myBuffer.get();
offset = (offset << 7) | (b & 0x7F);
if ((b & 0x80) != 0) {
continue;
}
return offset;
}
myBuffer.reset();
return -1;
}
}