/*
* ====================================================================
* 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.io.diff;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.zip.DeflaterOutputStream;
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.util.SVNLogType;
/**
* The <b>SVNDiffWindow</b> class represents a diff window that
* contains instructions and new data of a delta to apply to a file.
*
* <p>
* Instructions are not immediately contained in a window. A diff window
* provides an iterator that reads and constructs one <b>SVNDiffInstruction</b>
* from provided raw bytes per one iteration. There is even an ability to
* use a single <b>SVNDiffInstruction</b> object for read and decoded instructions:
* for subsequent iterations an iterator simply uses the same instruction object
* to return as a newly read and decoded instruction.
*
* @version 1.3
* @author TMate Software Ltd.
* @since 1.2
* @see SVNDiffInstruction
*/
public class SVNDiffWindow {
/**
* Bytes of the delta header of an uncompressed diff window.
*/
public static final byte[] SVN_HEADER = new byte[] {'S', 'V', 'N', '\0'};
/**
* Bytes of the delta header of a compressed diff window.
* @since 1.1, new in Subversion 1.4
*/
public static final byte[] SVN1_HEADER = new byte[] {'S', 'V', 'N', '\1'};
/**
* An empty window (in particular, its instructions length = 0). Corresponds
* to the case of an empty delta, so, it's passed to a delta consumer to
* create an empty file.
*/
public static final SVNDiffWindow EMPTY = new SVNDiffWindow(0,0,0,0,0);
private final long mySourceViewOffset;
private final int mySourceViewLength;
private final int myTargetViewLength;
private final int myNewDataLength;
private int myInstructionsLength;
private SVNDiffInstruction myTemplateInstruction = new SVNDiffInstruction(0,0,0);
private SVNDiffInstruction myTemplateNextInstruction = new SVNDiffInstruction(0,0,0);
private byte[] myData;
private int myDataOffset;
private int myInstructionsCount;
/**
* Constructs an <b>SVNDiffWindow</b> object. This constructor is
* used when bytes of instructions are not decoded and converted to
* <b>SVNDiffInstruction</b> objects yet, but are kept elsewhere
* along with new data.
*
* @param sourceViewOffset an offset in the source view
* @param sourceViewLength a number of bytes to read from the
* source view
* @param targetViewLength a length in bytes of the target view
* it must have after copying bytes
* @param instructionsLength a number of instructions bytes
* @param newDataLength a number of bytes of new data
* @see SVNDiffInstruction
*/
public SVNDiffWindow(long sourceViewOffset, int sourceViewLength, int targetViewLength, int instructionsLength, int newDataLength) {
mySourceViewOffset = sourceViewOffset;
mySourceViewLength = sourceViewLength;
myTargetViewLength = targetViewLength;
myInstructionsLength = instructionsLength;
myNewDataLength = newDataLength;
}
/**
* Returns the length of instructions in bytes.
*
* @return a number of instructions bytes
*/
public int getInstructionsLength() {
return myInstructionsLength;
}
/**
* Returns the source view offset.
*
* @return an offset in the source from where the source bytes
* must be copied
*/
public long getSourceViewOffset() {
return mySourceViewOffset;
}
/**
* Returns the number of bytes to copy from the source view to the target one.
*
* @return a number of source bytes to copy
*/
public int getSourceViewLength() {
return mySourceViewLength;
}
/**
* Returns the length in bytes of the target view. The length of the target
* view is actually the number of bytes that should be totally copied by all the
* instructions of this window.
*
* @return a length in bytes of the target view
*/
public int getTargetViewLength() {
return myTargetViewLength;
}
/**
* Returns the number of new data bytes to copy to the target view.
*
* @return a number of new data bytes
*/
public int getNewDataLength() {
return myNewDataLength;
}
/**
* Returns an iterator to read instructions in series.
* Objects returned by an iterator's <code>next()</code> method
* are separate <b>SVNDiffInstruction</b> objects.
*
* <p>
* Instructions as well as new data are read from a byte
* buffer that is passed to this window object via the
* {@link #setData(ByteBuffer) setData()} method.
*
* <p>
* A call to this routine is equivalent to a call
* <code>instructions(false)</code>.
*
* @return an instructions iterator
* @see #instructions(boolean)
* @see SVNDiffInstruction
*/
public Iterator instructions() {
return instructions(false);
}
/**
* Returns an iterator to read instructions in series.
*
* <p>
* If <code>template</code> is <span class="javakeyword">true</span>
* then each instruction returned by the iterator is actually the
* same <b>SVNDiffInstruction</b> object, but with proper options.
* This prevents from allocating new memory.
*
* <p>
* On the other hand, if <code>template</code> is <span class="javakeyword">false</span>
* then the iterator returns a new allocated <b>SVNDiffInstruction</b> object per
* each instruction read and decoded.
*
* <p>
* Instructions as well as new data are read from a byte buffer that is
* passed to this window object via the
* {@link #setData(ByteBuffer) setData()} method.
*
* @param template to use a single/multiple instruction objects
* @return an instructions iterator
* @see #instructions()
* @see SVNDiffInstruction
*/
public Iterator instructions(boolean template) {
return new InstructionsIterator(template);
}
/**
* Applies this window's instructions. The source and target streams
* are provided by <code>applyBaton</code>.
*
* <p>
* If this window has got any {@link SVNDiffInstruction#COPY_FROM_SOURCE} instructions, then:
* <ol>
* <li>At first copies a source view from the source stream
* of <code>applyBaton</code> to the baton's inner source buffer.
* {@link SVNDiffInstruction#COPY_FROM_SOURCE} instructions of this window are
* relative to the bounds of that source buffer (source view, in other words).
* <li>Second, according to instructions, copies source bytes from the source buffer
* to the baton's target buffer (or target view, in other words).
* <li>Then, if <code>applyBaton</code> is supplied with an MD5 digest, updates it with those bytes
* in the target buffer. So, after instructions applying completes, it will be the checksum for
* the full text expanded.
* <li>The last step - appends the target buffer bytes to the baton's
* target stream.
* </ol>
*
* <p>
* {@link SVNDiffInstruction#COPY_FROM_NEW_DATA} instructions rule to copy bytes from
* the instructions & new data buffer provided to this window object via a call to the
* {@link #setData(ByteBuffer) setData()} method.
*
* <p>
* {@link SVNDiffInstruction#COPY_FROM_TARGET} instructions are relative to the bounds of
* the target buffer.
*
* @param applyBaton a baton that provides the source and target
* views as well as holds the source and targed
* streams
* @throws SVNException
* @see #apply(byte[], byte[])
*/
public void apply(SVNDiffWindowApplyBaton applyBaton) throws SVNException {
// here we have streams and buffer from the previous calls (or nulls).
// 1. buffer for target.
if (applyBaton.myTargetBuffer == null || applyBaton.myTargetViewSize < getTargetViewLength()) {
applyBaton.myTargetBuffer = new byte[getTargetViewLength()];
}
applyBaton.myTargetViewSize = getTargetViewLength();
// 2. buffer for source.
int length = 0;
if (getSourceViewOffset() != applyBaton.mySourceViewOffset || getSourceViewLength() > applyBaton.mySourceViewLength) {
byte[] oldSourceBuffer = applyBaton.mySourceBuffer;
// create a new buffer
applyBaton.mySourceBuffer = new byte[getSourceViewLength()];
// copy from the old buffer.
if (applyBaton.mySourceViewOffset + applyBaton.mySourceViewLength > getSourceViewOffset()) {
// copy overlapping part to the new buffer
int start = (int) (getSourceViewOffset() - applyBaton.mySourceViewOffset);
System.arraycopy(oldSourceBuffer, start, applyBaton.mySourceBuffer, 0, (applyBaton.mySourceViewLength - start));
length = (applyBaton.mySourceViewLength - start);
}
}
if (length < getSourceViewLength()) {
// fill what remains.
try {
int toSkip = (int) (getSourceViewOffset() - (applyBaton.mySourceViewOffset + applyBaton.mySourceViewLength));
if (toSkip > 0) {
applyBaton.mySourceStream.skip(toSkip);
}
SVNFileUtil.readIntoBuffer(applyBaton.mySourceStream, applyBaton.mySourceBuffer, length, applyBaton.mySourceBuffer.length - length);
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
}
}
// update offsets in baton.
applyBaton.mySourceViewLength = getSourceViewLength();
applyBaton.mySourceViewOffset = getSourceViewOffset();
// apply instructions.
int tpos = 0;
int npos = myInstructionsLength;
try {
for (Iterator instructions = instructions(true); instructions.hasNext();) {
SVNDiffInstruction instruction = (SVNDiffInstruction) instructions.next();
int iLength = instruction.length < getTargetViewLength() - tpos ? (int) instruction.length : getTargetViewLength() - tpos;
switch (instruction.type) {
case SVNDiffInstruction.COPY_FROM_NEW_DATA:
System.arraycopy(myData, myDataOffset + npos, applyBaton.myTargetBuffer, tpos, iLength);
npos += iLength;
break;
case SVNDiffInstruction.COPY_FROM_TARGET:
int start = instruction.offset;
int end = instruction.offset + iLength;
int tIndex = tpos;
for(int j = start; j < end; j++) {
applyBaton.myTargetBuffer[tIndex] = applyBaton.myTargetBuffer[j];
tIndex++;
}
break;
case SVNDiffInstruction.COPY_FROM_SOURCE:
System.arraycopy(applyBaton.mySourceBuffer, instruction.offset, applyBaton.myTargetBuffer, tpos, iLength);
break;
default:
}
tpos += instruction.length;
if (tpos >= getTargetViewLength()) {
break;
}
}
// save tbuffer.
if (applyBaton.myDigest != null) {
applyBaton.myDigest.update(applyBaton.myTargetBuffer, 0, getTargetViewLength());
}
applyBaton.myTargetStream.write(applyBaton.myTargetBuffer, 0, getTargetViewLength());
} catch (IOException e) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
SVNErrorManager.error(err, e, SVNLogType.DEFAULT);
}
}
/**
* Applies this window's instructions provided source and target view buffers.
*
* <p>
* If this window has got any {@link SVNDiffInstruction#COPY_FROM_SOURCE} instructions, then
* appropriate bytes described by such an instruction are copied from the <code>sourceBuffer</code>
* to the <code>targetBuffer</code>.
*
* <p>
* {@link SVNDiffInstruction#COPY_FROM_NEW_DATA} instructions rule to copy bytes from
* the instructions & new data buffer provided to this window object via a call to the
* {@link #setData(ByteBuffer) setData()} method.
*
* <p>
* {@link SVNDiffInstruction#COPY_FROM_TARGET} instructions are relative to the bounds of
* the <code>targetBuffer</code> itself.
*
* @param sourceBuffer a buffer containing a source view
* @param targetBuffer a buffer to get a target view
* @return the size of the resultant target view
* @see #apply(SVNDiffWindowApplyBaton)
*/
public int apply(byte[] sourceBuffer, byte[] targetBuffer) {
int dataOffset = myInstructionsLength;
int tpos = 0;
for (Iterator instructions = instructions(true); instructions.hasNext();) {
SVNDiffInstruction instruction = (SVNDiffInstruction) instructions.next();
int iLength = instruction.length < getTargetViewLength() - tpos ? (int) instruction.length : getTargetViewLength() - tpos;
switch (instruction.type) {
case SVNDiffInstruction.COPY_FROM_NEW_DATA:
System.arraycopy(myData, myDataOffset + dataOffset, targetBuffer, tpos, iLength);
dataOffset += iLength;
break;
case SVNDiffInstruction.COPY_FROM_TARGET:
int start = instruction.offset;
int end = instruction.offset + iLength;
int tIndex = tpos;
for(int j = start; j < end; j++) {
targetBuffer[tIndex] = targetBuffer[j];
tIndex++;
}
break;
case SVNDiffInstruction.COPY_FROM_SOURCE:
System.arraycopy(sourceBuffer, instruction.offset, targetBuffer, tpos, iLength);
break;
default:
}
tpos += instruction.length;
if (tpos >= getTargetViewLength()) {
break;
}
}
return getTargetViewLength();
}
/**
* Sets a byte buffer containing instruction and new data bytes
* of this window.
*
* <p>
* Instructions will go before new data within the buffer and should start
* at <code>buffer.position() + buffer.arrayOffset()</code>.
*
* <p>
* Applying a diff window prior to setting instruction and new data bytes
* may cause a NPE.
*
* @param buffer an input data buffer
*/
public void setData(ByteBuffer buffer) {
myData = buffer.array();
myDataOffset = buffer.position() + buffer.arrayOffset();
}
/**
* Gives a string representation of this object.
*
* @return a string representation of this object
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getSourceViewOffset());
sb.append(":");
sb.append(getSourceViewLength());
sb.append(":");
sb.append(getTargetViewLength());
sb.append(":");
sb.append(getInstructionsLength());
sb.append(":");
sb.append(getNewDataLength());
sb.append(":");
sb.append(getDataLength());
sb.append(":");
sb.append(myDataOffset);
return sb.toString();
}
/**
* Tells if this window is not empty, i.e. has got any instructions.
*
* @return <span class="javakeyword">true</span> if has instructions,
* <span class="javakeyword">false</span> if has not
*/
public boolean hasInstructions() {
return myInstructionsLength > 0;
}
/**
* Writes this window object to the provided stream.
*
* <p>
* If <code>writeHeader</code> is <span class="javakeyword">true</span>
* then writes {@link #SVN_HEADER} bytes also.
*
* @param os an output stream to write to
* @param writeHeader controls whether the header should be written
* or not
* @throws IOException if an I/O error occurs
*/
public void writeTo(OutputStream os, boolean writeHeader) throws IOException {
writeTo(os, writeHeader, false);
}
/**
* Formats and writes this window bytes to the specified output stream.
*
* @param os an output stream to write the window to
* @param writeHeader if <span class="javakeyword">true</span> a window
* header will be also written
* @param compress if <span class="javakeyword">true</span> writes
* compressed window bytes using {@link #SVN1_HEADER}
* to indicate that (if <code>writeHeader</code> is
* <span class="javakeyword">true</span>), otherwise
* non-compressed window is written with {@link #SVN_HEADER}
* (again if <code>writeHeader</code> is <span class="javakeyword">true</span>)
* @throws IOException
* @since 1.1
*/
public void writeTo(OutputStream os, boolean writeHeader, boolean compress) throws IOException {
if (writeHeader) {
os.write(compress ? SVN1_HEADER : SVN_HEADER);
}
if (!hasInstructions()) {
return;
}
ByteBuffer offsets = ByteBuffer.allocate(100);
SVNDiffInstruction.writeLong(offsets, mySourceViewOffset);
SVNDiffInstruction.writeInt(offsets, mySourceViewLength);
SVNDiffInstruction.writeInt(offsets, myTargetViewLength);
ByteBuffer instructions = null;
ByteBuffer newData = null;
int instLength = 0;
int dataLength = 0;
if (compress) {
instructions = inflate(myData, myDataOffset, myInstructionsLength);
instLength = instructions.remaining();
newData = inflate(myData, myDataOffset + myInstructionsLength, myNewDataLength);
dataLength = newData.remaining();
SVNDiffInstruction.writeInt(offsets, instLength);
SVNDiffInstruction.writeInt(offsets, dataLength);
} else {
SVNDiffInstruction.writeInt(offsets, myInstructionsLength);
SVNDiffInstruction.writeInt(offsets, myNewDataLength);
}
os.write(offsets.array(), offsets.arrayOffset(), offsets.position());
if (compress) {
os.write(instructions.array(), instructions.arrayOffset(), instructions.remaining());
os.write(newData.array(), newData.arrayOffset(), newData.remaining());
} else {
os.write(myData, myDataOffset, myInstructionsLength);
if (myNewDataLength > 0) {
os.write(myData, myDataOffset + myInstructionsLength, myNewDataLength);
}
}
}
/**
* Returns the total amount of new data and instruction bytes.
*
* @return new data length + instructions length
*/
public int getDataLength() {
return myNewDataLength + myInstructionsLength;
}
/**
* Tells whether this window contains any copy-from-source
* instructions.
*
* @return <span class="javakeyword">true</span> if this window
* has got at least one {@link SVNDiffInstruction#COPY_FROM_SOURCE}
* instruction
*/
public boolean hasCopyFromSourceInstructions() {
for(Iterator instrs = instructions(true); instrs.hasNext();) {
SVNDiffInstruction instruction = (SVNDiffInstruction) instrs.next();
if (instruction.type == SVNDiffInstruction.COPY_FROM_SOURCE) {
return true;
}
}
return false;
}
/**
* Creates an exact copy of this window object.
*
* <p>
* <code>targetData</code> is written instruction & new data bytes and
* then is set to a new window object via a call to its {@link #setData(ByteBuffer) setData()}
* method.
*
* @param targetData a byte buffer to receive a copy of this wondow data
* @return a new window object that is an exact copy of this one
*/
public SVNDiffWindow clone(ByteBuffer targetData) {
int targetOffset = targetData.position() + targetData.arrayOffset();
int position = targetData.position();
targetData.put(myData, myDataOffset, myInstructionsLength + myNewDataLength);
targetData.position(position);
SVNDiffWindow clone = new SVNDiffWindow(getSourceViewOffset(), getSourceViewLength(), getTargetViewLength(),
getInstructionsLength(), getNewDataLength());
clone.setData(targetData);
clone.myDataOffset = targetOffset;
return clone;
}
private static ByteBuffer inflate(byte[] src, int offset, int length) throws IOException {
final ByteBuffer buffer = ByteBuffer.allocate(length*2 + 2);
SVNDiffInstruction.writeInt(buffer, length);
if (length < 512) {
buffer.put(src, offset, length);
} else {
DeflaterOutputStream out = new DeflaterOutputStream(new OutputStream() {
public void write(int b) throws IOException {
buffer.put((byte) (b & 0xFF));
}
public void write(byte[] b, int off, int len) throws IOException {
buffer.put(b, off, len);
}
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
});
out.write(src, offset, length);
out.finish();
if (buffer.position() >= length) {
buffer.clear();
SVNDiffInstruction.writeInt(buffer, length);
buffer.put(src, offset, length);
}
}
buffer.flip();
return buffer;
}
private class InstructionsIterator implements Iterator {
private SVNDiffInstruction myNextInsruction;
private int myOffset;
private int myNewDataOffset;
private boolean myIsTemplate;
public InstructionsIterator(boolean useTemplate) {
myIsTemplate = useTemplate;
myNextInsruction = readNextInstruction();
}
public boolean hasNext() {
return myNextInsruction != null;
}
public Object next() {
if (myNextInsruction == null) {
return null;
}
if (myIsTemplate) {
myTemplateNextInstruction.type = myNextInsruction.type;
myTemplateNextInstruction.length = myNextInsruction.length;
myTemplateNextInstruction.offset = myNextInsruction.offset;
myNextInsruction = readNextInstruction();
return myTemplateNextInstruction;
}
Object next = myNextInsruction;
myNextInsruction = readNextInstruction();
return next;
}
public void remove() {
}
private SVNDiffInstruction readNextInstruction() {
if (myData == null || myOffset >= myInstructionsLength) {
return null;
}
SVNDiffInstruction instruction = myIsTemplate ? myTemplateInstruction : new SVNDiffInstruction();
instruction.type = (myData[myDataOffset + myOffset] & 0xC0) >> 6;
instruction.length = myData[myDataOffset + myOffset] & 0x3f;
myOffset++;
if (instruction.length == 0) {
// read length from next byte
instruction.length = readInt();
}
if (instruction.type == 0 || instruction.type == 1) {
// read offset from next byte (no offset without length).
instruction.offset = readInt();
} else {
// set offset to offset in newdata.
instruction.offset = myNewDataOffset;
myNewDataOffset += instruction.length;
}
return instruction;
}
private int readInt() {
int result = 0;
while(true) {
byte b = myData[myDataOffset + myOffset];
result = result << 7;
result = result | (b & 0x7f);
if ((b & 0x80) != 0) {
myOffset++;
if (myOffset >= myInstructionsLength) {
return -1;
}
continue;
}
myOffset++;
return result;
}
}
}
/**
* Returns an array of instructions of this window.
*
* <p>
* If <code>target</code> is large enough to receive all instruction
* objects, then it's simply filled up to the end of instructions.
* However if it's not, it will be expanded to receive all instructions.
*
* @param target an instructions receiver
* @return an array containing all instructions
*/
public SVNDiffInstruction[] loadDiffInstructions(SVNDiffInstruction[] target) {
int index = 0;
for (Iterator instructions = instructions(); instructions.hasNext();) {
if (index >= target.length) {
SVNDiffInstruction[] newTarget = new SVNDiffInstruction[index*3/2];
System.arraycopy(target, 0, newTarget, 0, index);
target = newTarget;
}
target[index] = (SVNDiffInstruction) instructions.next();
index++;
}
myInstructionsCount = index;
return target;
}
/**
* Returns the amount of instructions of this window object.
*
* @return a total number of instructions
*/
public int getInstructionsCount() {
return myInstructionsCount;
}
/**
* Fills a target buffer with the specified number of new data bytes
* of this window object taken at the specified offset.
*
* @param target a buffer to copy to
* @param offset an offset relative to the position of the first
* new data byte of this window object
* @param length a number of new data bytes to copy
*/
public void writeNewData(ByteBuffer target, int offset, int length) {
offset += myDataOffset + myInstructionsLength;
target.put(myData, offset, length);
}
}