001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.io.diff;
013:
014: import java.io.IOException;
015: import java.io.InputStream;
016: import java.io.OutputStream;
017: import java.security.MessageDigest;
018: import java.security.NoSuchAlgorithmException;
019:
020: import org.tmatesoft.svn.core.SVNErrorCode;
021: import org.tmatesoft.svn.core.SVNErrorMessage;
022: import org.tmatesoft.svn.core.SVNException;
023: import org.tmatesoft.svn.core.internal.delta.SVNDeltaAlgorithm;
024: import org.tmatesoft.svn.core.internal.delta.SVNVDeltaAlgorithm;
025: import org.tmatesoft.svn.core.internal.delta.SVNXDeltaAlgorithm;
026: import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
027: import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
028: import org.tmatesoft.svn.core.io.ISVNDeltaConsumer;
029:
030: /**
031: * The <b>SVNDeltaGenerator</b> is intended for generating diff windows of
032: * fixed size having a target version of a file against a source one.
033: * File contents are provided as two streams - source and target ones, or just
034: * target if delta is generated against empty contents.
035: *
036: * <p>
037: * The generator uses the V-Delta algorithm for generating full contents delta (vs. empty)
038: * and the X-Delta algorithm for generating delta as a difference between target and
039: * non-empty source streams.
040: *
041: * @version 1.1.1
042: * @author TMate Software Ltd.
043: */
044: public class SVNDeltaGenerator {
045:
046: private SVNDeltaAlgorithm myXDelta = new SVNXDeltaAlgorithm();
047: private SVNDeltaAlgorithm myVDelta = new SVNVDeltaAlgorithm();
048:
049: private byte[] mySourceBuffer;
050: private byte[] myTargetBuffer;
051:
052: /**
053: * Creates a generator that will produce diff windows of
054: * 100Kbytes contents length. That is, after applying of
055: * such a window you get 100Kbytes of file contents.
056: *
057: * @see #SVNDeltaGenerator(int)
058: */
059: public SVNDeltaGenerator() {
060: this (1024 * 100);
061: }
062:
063: /**
064: * Creates a generator that will produce diff windows of
065: * a specified contents length.
066: *
067: * @param maximumDiffWindowSize a maximum size of a file contents
068: * chunk that a single applied diff
069: * window would produce
070: */
071: public SVNDeltaGenerator(int maximumDiffWindowSize) {
072: mySourceBuffer = new byte[maximumDiffWindowSize];
073: myTargetBuffer = new byte[maximumDiffWindowSize];
074: }
075:
076: /**
077: * Generates a series of diff windows of fixed size comparing
078: * target bytes (from <code>target</code> stream) against an
079: * empty file and sends produced windows to the provided
080: * consumer. <code>consumer</code>'s {@link org.tmatesoft.svn.core.io.ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow) textDeltaChunk()}
081: * method is called to receive and process generated windows.
082: * Now new data comes within a window, so the output stream is either
083: * ignored (if it's <span class="javakeyword">null</span>) or immediately closed
084: * (if it's not <span class="javakeyword">null</span>).
085: *
086: * <p>
087: * If <code>computeChecksum</code> is <span class="javakeyword">true</span>,
088: * the return value will be a strig containing a hex representation
089: * of the MD5 digest computed for the target contents.
090: *
091: * @param path a file repository path
092: * @param target an input stream to read target bytes
093: * from
094: * @param consumer a diff windows consumer
095: * @param computeChecksum <span class="javakeyword">true</span> to
096: * compute a checksum
097: * @return if <code>computeChecksum</code> is <span class="javakeyword">true</span>,
098: * a string representing a hex form of the
099: * MD5 checksum computed for the target contents; otherwise <span class="javakeyword">null</span>
100: * @throws SVNException
101: */
102: public String sendDelta(String path, InputStream target,
103: ISVNDeltaConsumer consumer, boolean computeChecksum)
104: throws SVNException {
105: return sendDelta(path, SVNFileUtil.DUMMY_IN, 0, target,
106: consumer, computeChecksum);
107: }
108:
109: /**
110: * Generates a series of diff windows of fixed size comparing
111: * target bytes (read from <code>target</code> stream) against source
112: * bytes (read from <code>source</code> stream), and sends produced windows to the provided
113: * consumer. <code>consumer</code>'s {@link org.tmatesoft.svn.core.io.ISVNDeltaConsumer#textDeltaChunk(String, SVNDiffWindow) textDeltaChunk()}
114: * method is called to receive and process generated windows.
115: * Now new data comes within a window, so the output stream is either
116: * ignored (if it's <span class="javakeyword">null</span>) or immediately closed
117: * (if it's not <span class="javakeyword">null</span>).
118: *
119: * <p>
120: * If <code>computeChecksum</code> is <span class="javakeyword">true</span>,
121: * the return value will be a strig containing a hex representation
122: * of the MD5 digest computed for the target contents.
123: *
124: * @param path a file repository path
125: * @param source an input stream to read source bytes
126: * from
127: * @param sourceOffset an offset of the source view in the given <code>source</code> stream
128: * @param target an input stream to read target bytes
129: * from
130: * @param consumer a diff windows consumer
131: * @param computeChecksum <span class="javakeyword">true</span> to
132: * compute a checksum
133: * @return if <code>computeChecksum</code> is <span class="javakeyword">true</span>,
134: * a string representing a hex form of the
135: * MD5 checksum computed for the target contents; otherwise <span class="javakeyword">null</span>
136: * @throws SVNException
137: */
138: public String sendDelta(String path, InputStream source,
139: long sourceOffset, InputStream target,
140: ISVNDeltaConsumer consumer, boolean computeChecksum)
141: throws SVNException {
142: MessageDigest digest = null;
143: if (computeChecksum) {
144: try {
145: digest = MessageDigest.getInstance("MD5");
146: } catch (NoSuchAlgorithmException e) {
147: SVNErrorMessage err = SVNErrorMessage.create(
148: SVNErrorCode.IO_ERROR,
149: "MD5 implementation not found: {0}", e
150: .getLocalizedMessage());
151: SVNErrorManager.error(err, e);
152: return null;
153: }
154: }
155: boolean windowSent = false;
156: while (true) {
157: int targetLength;
158: int sourceLength;
159: try {
160: targetLength = target.read(myTargetBuffer, 0,
161: myTargetBuffer.length);
162: } catch (IOException e) {
163: SVNErrorMessage err = SVNErrorMessage.create(
164: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
165: SVNErrorManager.error(err, e);
166: return null;
167: }
168: if (targetLength <= 0) {
169: // send empty window, needed to create empty file.
170: // only when no windows was sent at all.
171: if (!windowSent && consumer != null) {
172: consumer.textDeltaChunk(path, SVNDiffWindow.EMPTY);
173: }
174: break;
175: }
176: try {
177: sourceLength = source.read(mySourceBuffer, 0,
178: mySourceBuffer.length);
179: } catch (IOException e) {
180: SVNErrorMessage err = SVNErrorMessage.create(
181: SVNErrorCode.IO_ERROR, e.getLocalizedMessage());
182: SVNErrorManager.error(err, e);
183: return null;
184: }
185: if (sourceLength < 0) {
186: sourceLength = 0;
187: }
188: // update digest,
189: if (digest != null) {
190: digest.update(myTargetBuffer, 0, targetLength);
191: }
192: // generate and send window
193: sendDelta(path, sourceOffset, mySourceBuffer, sourceLength,
194: myTargetBuffer, targetLength, consumer);
195: windowSent = true;
196: sourceOffset += sourceLength;
197: }
198: if (consumer != null) {
199: consumer.textDeltaEnd(path);
200: }
201: return SVNFileUtil.toHexDigest(digest);
202: }
203:
204: public void sendDelta(String path, byte[] target, int targetLength,
205: ISVNDeltaConsumer consumer) throws SVNException {
206: sendDelta(path, null, 0, 0, target, targetLength, consumer);
207: }
208:
209: public void sendDelta(String path, byte[] source, int sourceLength,
210: long sourceOffset, byte[] target, int targetLength,
211: ISVNDeltaConsumer consumer) throws SVNException {
212: if (targetLength == 0 || target == null) {
213: // send empty window, needed to create empty file.
214: // only when no windows was sent at all.
215: if (consumer != null) {
216: consumer.textDeltaChunk(path, SVNDiffWindow.EMPTY);
217: }
218: return;
219: }
220: if (source == null) {
221: source = new byte[0];
222: sourceLength = 0;
223: } else if (sourceLength < 0) {
224: sourceLength = 0;
225: }
226: // generate and send window
227: sendDelta(path, sourceOffset, source == null ? new byte[0]
228: : source, sourceLength, target, targetLength, consumer);
229: }
230:
231: private void sendDelta(String path, long sourceOffset,
232: byte[] source, int sourceLength, byte[] target,
233: int targetLength, ISVNDeltaConsumer consumer)
234: throws SVNException {
235: // use x or v algorithm depending on sourceLength
236: SVNDeltaAlgorithm algorithm = sourceLength == 0 ? myVDelta
237: : myXDelta;
238: algorithm.computeDelta(source, sourceLength, target,
239: targetLength);
240: // send single diff window to the editor.
241: if (consumer == null) {
242: algorithm.reset();
243: return;
244: }
245: int instructionsLength = algorithm.getInstructionsLength();
246: int newDataLength = algorithm.getNewDataLength();
247: SVNDiffWindow window = new SVNDiffWindow(sourceOffset,
248: sourceLength, targetLength, instructionsLength,
249: newDataLength);
250: window.setData(algorithm.getData());
251: OutputStream os = consumer.textDeltaChunk(path, window);
252: SVNFileUtil.closeFile(os);
253: algorithm.reset();
254: }
255: }
|