diff src/org/tmatesoft/hg/internal/Patch.java @ 329:694ebabb5cb3

Refactor revlog patch mechanism, towards patch merging
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 13 Oct 2011 03:30:50 +0200
parents
children 9747a786a34d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/Patch.java	Thu Oct 13 03:30:50 2011 +0200
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program 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; version 2 of the License.
+ *
+ * This program 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.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.internal;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * @see http://mercurial.selenic.com/wiki/BundleFormat, in Changelog group description
+ * 
+ * range [start..end] in original source gets replaced with data of length (do not keep, use data.length instead)
+ * range [end(i)..start(i+1)] is copied from the source
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class Patch {
+	private final IntVector starts, ends;
+	private final ArrayList<byte[]> data;
+	
+	public Patch() {
+		starts = new IntVector();
+		ends = new IntVector();
+		data = new ArrayList<byte[]>();
+	}
+	
+	public int count() {
+		return data.size();
+	}
+
+	// number of bytes this patch will add (or remove, if negative) from the base revision
+	private int patchSizeDelta() {
+		int rv = 0;
+		int prevEnd = 0;
+		for (int i = 0, x = data.size(); i < x; i++) {
+			final int start = starts.get(i);
+			final int len = data.get(i).length;
+			rv += start - prevEnd; // would copy from original
+			rv += len; // and add new
+			prevEnd = ends.get(i);
+		}
+		rv -= prevEnd;
+		return rv;
+	}
+	
+	public byte[] apply(DataAccess baseRevisionContent, int outcomeLen) throws IOException {
+		if (outcomeLen == -1) {
+			outcomeLen = baseRevisionContent.length() + patchSizeDelta();
+		}
+		int prevEnd = 0, destIndex = 0;
+		byte[] rv = new byte[outcomeLen];
+		for (int i = 0, x = data.size(); i < x; i++) {
+			final int start = starts.get(i);
+			baseRevisionContent.seek(prevEnd);
+			// copy source bytes that were not modified (up to start of the record)
+			baseRevisionContent.readBytes(rv, destIndex, start - prevEnd);
+			destIndex += start - prevEnd;
+			// insert new data from the patch, if any
+			byte[] d = data.get(i);
+			System.arraycopy(d, 0, rv, destIndex, d.length);
+			destIndex += d.length;
+			prevEnd = ends.get(i);
+		}
+		baseRevisionContent.seek(prevEnd);
+		// copy everything in the source past last record's end
+		baseRevisionContent.readBytes(rv, destIndex, (int) (baseRevisionContent.length() - prevEnd));
+		return rv;
+	}
+	
+	public void clear() {
+		starts.clear();
+		ends.clear();
+		data.clear();
+	}
+	
+	/**
+	 * Initialize instance from stream. Any previous patch information (i.e. if instance if reused) is cleared first.
+	 * Read up to the end of DataAccess and interpret data as patch records.
+	 */
+	public void read(DataAccess da) throws IOException {
+		clear();
+		while (!da.isEmpty()) {
+			readOne(da);
+		}
+	}
+
+	/**
+	 * Caller is responsible to ensure stream got some data to read
+	 */
+	public void readOne(DataAccess da) throws IOException {
+		int s = da.readInt();
+		int e = da.readInt();
+		int len = da.readInt();
+		byte[] src = new byte[len];
+		da.readBytes(src, 0, len);
+		starts.add(s);
+		ends.add(e);
+		data.add(src);
+	}
+
+/*
+	private void add(Patch another, int index) {
+		starts.add(another.starts.get(index));
+		ends.add(another.ends.get(index));
+		data.add(another.data.get(index));
+	}
+
+	/**
+	 * Modify this patch with subsequent patch 
+	 * /
+	public void apply(Patch another) {
+		Patch r = new Patch();
+		int p1AppliedPos = 0;
+		int p1PrevEnd = 0;
+		for (int i = 0, j = 0, iMax = another.count(), jMax = this.count(); i < iMax; i++) {
+			int newerPatchEntryStart = another.starts.get(i);
+			int olderPatchEntryEnd;
+			
+			while (j < jMax) {
+				if (starts.get(j) < newerPatchEntryStart) {
+					if (starts.get(j)+data.get(j).length <= newerPatchEntryStart) {
+						r.add(this, j);
+					} else {
+						int newLen = newerPatchEntryStart - starts.get(j);
+						int newEnd = ends.get(j) <= newerPatchEntryStart ? ends.get(j) : newerPatchEntryStart; 
+						r.add(starts.get(j), newEnd, data.get(j), newLen);
+						break;
+					}
+				}
+				p1AppliedPos += starts.get(j) - p1PrevEnd;
+				p1AppliedPos += data.get(j).length;
+				p1PrevEnd = ends.get(j);
+				j++;
+			}
+			r.add(newerPatchEntryStart, another.ends.get(i), another.data.get(i));
+			p1AppliedPos += newerPatchEntryStart + p1PrevEnd - another.data.get(i).length;
+			// either j == jMax and another(i, i+1, ..., iMax) need to be just copied
+			// or new patch entry starts before end of one of original patch entries
+			if (olderPatchEntryEnd > (destPosition + newerPatchEntryStart)) {
+				destPosition += starts.get(j) - prevEnd; // count those in the original stream up to old patch start
+				int newLen = newerPatchEntryStart - destPosition;
+			}
+		}
+	}
+*/
+}
\ No newline at end of file