/* * ==================================================================== * 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.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.internal.delta.SVNDeltaAlgorithm; import org.tmatesoft.svn.core.internal.delta.SVNXDeltaAlgorithm; import org.tmatesoft.svn.core.internal.wc.IOExceptionWrapper; 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.util.SVNLogType; /** * The <b>SVNDeltaGenerator</b> is intended for generating diff windows of * fixed size having a target version of a file against a source one. * File contents are provided as two streams - source and target ones, or just * target if delta is generated against empty contents. * * <p> * The generator uses X-Delta algorithm for generating all kinds of deltas. * * @version 1.3 * @author TMate Software Ltd. * @since 1.2 */ public class SVNDeltaGenerator { private SVNDeltaAlgorithm myXDelta = new SVNXDeltaAlgorithm(); private byte[] mySourceBuffer; private byte[] myTargetBuffer; private int myMaximumBufferSize; /** * Creates a generator that will produce diff windows of * 100Kbytes contents length. That is, after applying of * such a window you get 100 Kbytes of file contents. * * @see #SVNDeltaGenerator(int) */ public SVNDeltaGenerator() { this(1024*100); } /** * Creates a generator that will produce diff windows of * a specified contents length. * * @param maximumDiffWindowSize a maximum size of a file contents * chunk that a single applied diff * window would produce */ public SVNDeltaGenerator(int maximumDiffWindowSize) { myMaximumBufferSize = maximumDiffWindowSize; int initialSize = Math.min(8192, myMaximumBufferSize); mySourceBuffer = new byte[initialSize]; myTargetBuffer = new byte[initialSize]; } /** * Generates a series of diff windows of fixed size comparing * target bytes (from <code>target</code> stream) against an * empty file and sends produced windows to the provided * consumer. <code>consumer</code>'s {@link org.tmatesoft.svn.core.io.ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow) textDeltaChunk()} * method is called to receive and process generated windows. * Now new data comes within a window, so the output stream is either * ignored (if it's <span class="javakeyword">null</span>) or immediately closed * (if it's not <span class="javakeyword">null</span>). * * <p> * If <code>computeChecksum</code> is <span class="javakeyword">true</span>, * the return value will be a strig containing a hex representation * of the MD5 digest computed for the target contents. * * @param path a file repository path * @param target an input stream to read target bytes * from * @param consumer a diff windows consumer * @param computeChecksum <span class="javakeyword">true</span> to * compute a checksum * @return if <code>computeChecksum</code> is <span class="javakeyword">true</span>, * a string representing a hex form of the * MD5 checksum computed for the target contents; otherwise <span class="javakeyword">null</span> * @throws SVNException */ public String sendDelta(String path, InputStream target, ISVNDeltaConsumer consumer, boolean computeChecksum) throws SVNException { return sendDelta(path, SVNFileUtil.DUMMY_IN, 0, target, consumer, computeChecksum); } /** * Generates a series of diff windows of fixed size comparing * target bytes (read from <code>target</code> stream) against source * bytes (read from <code>source</code> stream), and sends produced windows to the provided * consumer. <code>consumer</code>'s {@link org.tmatesoft.svn.core.io.ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow) textDeltaChunk()} * method is called to receive and process generated windows. * Now new data comes within a window, so the output stream is either * ignored (if it's <span class="javakeyword">null</span>) or immediately closed * (if it's not <span class="javakeyword">null</span>). * * <p> * If <code>computeChecksum</code> is <span class="javakeyword">true</span>, * the return value will be a strig containing a hex representation * of the MD5 digest computed for the target contents. * * @param path a file repository path * @param source an input stream to read source bytes * from * @param sourceOffset an offset of the source view in the given <code>source</code> stream * @param target an input stream to read target bytes * from * @param consumer a diff windows consumer * @param computeChecksum <span class="javakeyword">true</span> to * compute a checksum * @return if <code>computeChecksum</code> is <span class="javakeyword">true</span>, * a string representing a hex form of the * MD5 checksum computed for the target contents; otherwise <span class="javakeyword">null</span> * @throws SVNException */ public String sendDelta(String path, InputStream source, long sourceOffset, InputStream target, ISVNDeltaConsumer consumer, boolean computeChecksum) throws SVNException { MessageDigest digest = null; if (computeChecksum) { try { digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "MD5 implementation not found: {0}", e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.DEFAULT); return null; } } boolean windowSent = false; while(true) { int targetLength; int sourceLength; try { targetLength = readToBuffer(target, myTargetBuffer); } catch (IOExceptionWrapper ioew) { throw ioew.getOriginalException(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.DEFAULT); return null; } if (targetLength <= 0) { // send empty window, needed to create empty file. // only when no windows was sent at all. if (!windowSent && consumer != null) { consumer.textDeltaChunk(path, SVNDiffWindow.EMPTY); } break; } try { sourceLength = readToBuffer(source, mySourceBuffer); } catch (IOExceptionWrapper ioew) { throw ioew.getOriginalException(); } catch (IOException e) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, e.getLocalizedMessage()); SVNErrorManager.error(err, e, SVNLogType.DEFAULT); return null; } if (sourceLength < 0) { sourceLength = 0; } // update digest, if (digest != null) { digest.update(myTargetBuffer, 0, targetLength); } // generate and send window sendDelta(path, sourceOffset, mySourceBuffer, sourceLength, myTargetBuffer, targetLength, consumer); windowSent = true; sourceOffset += sourceLength; } if (consumer != null) { consumer.textDeltaEnd(path); } return SVNFileUtil.toHexDigest(digest); } /** * Generates a series of diff windows of fixed size comparing * target bytes (read from <code>target</code> stream) against an empty file, and sends produced windows to * the provided consumer. * * <p/> * This is identical to <code>sendDelta(path, null, 0, 0, target, targetLength, consumer)</code>. * * @param path a file repository path * @param target an input byte array to read target bytes from * @param targetLength * @param consumer a diff windows consumer * @throws SVNException */ public void sendDelta(String path, byte[] target, int targetLength, ISVNDeltaConsumer consumer) throws SVNException { sendDelta(path, null, 0, 0, target, targetLength, consumer); } /** * Generates a series of diff windows of fixed size comparing * <code>targetLength</code> of target bytes (read from <code>target</code> stream) against * <code>sourceLength</code> of source bytes (read from <code>source</code> stream at offset * <code>sourceOffset</code>), and sends produced windows to the provided <code>consumer</code>. * * <p/> * Size of the produced windows is set in a constructor of this delta generator. * * <p/> * <code>consumer</code>'s {@link org.tmatesoft.svn.core.io.ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow) textDeltaChunk()} * method is called to receive and process generated windows. * Now new data comes within a window, so the output stream is either * ignored (if it's <span class="javakeyword">null</span>) or immediately closed * (if it's not <span class="javakeyword">null</span>). * * @param path a file repository path * @param source an input stream to read source bytes from * @param sourceLength the size of the source view * @param sourceOffset an offset of the source view in the given <code>source</code> stream * @param target an input stream to read target bytes from * @param targetLength the size of the target view * @param consumer a diff windows consumer * @throws SVNException */ public void sendDelta(String path, byte[] source, int sourceLength, long sourceOffset, byte[] target, int targetLength, ISVNDeltaConsumer consumer) throws SVNException { if (targetLength == 0 || target == null) { // send empty window, needed to create empty file. // only when no windows was sent at all. if (consumer != null) { consumer.textDeltaChunk(path, SVNDiffWindow.EMPTY); } return; } if (source == null) { source = new byte[0]; sourceLength = 0; } else if (sourceLength < 0) { sourceLength = 0; } // generate and send window sendDelta(path, sourceOffset, source == null ? new byte[0] : source, sourceLength, target, targetLength, consumer); } private void sendDelta(String path, long sourceOffset, byte[] source, int sourceLength, byte[] target, int targetLength, ISVNDeltaConsumer consumer) throws SVNException { // always use x algorithm, v is deprecated now. SVNDeltaAlgorithm algorithm = myXDelta; algorithm.computeDelta(source, sourceLength, target, targetLength); // send single diff window to the editor. if (consumer == null) { algorithm.reset(); return; } int instructionsLength = algorithm.getInstructionsLength(); int newDataLength = algorithm.getNewDataLength(); SVNDiffWindow window = new SVNDiffWindow(sourceOffset, sourceLength, targetLength, instructionsLength, newDataLength); window.setData(algorithm.getData()); OutputStream os = consumer.textDeltaChunk(path, window); SVNFileUtil.closeFile(os); algorithm.reset(); } private int readToBuffer(InputStream is, byte[] buffer) throws IOException { int read = SVNFileUtil.readIntoBuffer(is, buffer, 0, buffer.length); if (read <= 0) { return read; } if (read == buffer.length && read < myMaximumBufferSize) { byte[] expanded = new byte[myMaximumBufferSize]; System.arraycopy(buffer, 0, expanded, 0, read); if (buffer == myTargetBuffer) { myTargetBuffer = expanded; } else { mySourceBuffer = expanded; } buffer = expanded; int anotherRead = SVNFileUtil.readIntoBuffer(is, buffer, read, buffer.length - read); if (anotherRead <= 0) { return read; } read += anotherRead; } return read; } }