changeset 115:c0cc2535462c

Introduced channels to pipeline (and easily filter) data streams
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 03 Feb 2011 23:32:08 +0100
parents 46291ec605a0
children 5d13dcaaff39
files src/org/tmatesoft/hg/core/CatCommand.java src/org/tmatesoft/hg/core/RepositoryFacade.java src/org/tmatesoft/hg/internal/ByteArrayChannel.java src/org/tmatesoft/hg/internal/Filter.java src/org/tmatesoft/hg/internal/FilterByteChannel.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgRepository.java src/org/tmatesoft/hg/repo/Revlog.java src/org/tmatesoft/hg/util/ByteChannel.java test/org/tmatesoft/hg/test/TestByteChannel.java
diffstat 10 files changed, 289 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/CatCommand.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/core/CatCommand.java	Thu Feb 03 23:32:08 2011 +0100
@@ -20,11 +20,10 @@
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
 import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
 
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.ByteChannel;
 
 /**
  * Command to obtain content of a 
@@ -61,26 +60,26 @@
 		return this;
 	}
 
-	public void execute(OutputStream os) throws IOException /*TODO own exception type*/ {
+	public void execute(ByteChannel sink) throws Exception /*TODO own exception type*/ {
 		if (localRevision == BAD_REVISION && revision == null) {
 			throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified");
 		}
 		if (file == null) {
 			throw new IllegalArgumentException("Name of the file is missing");
 		}
-		if (os == null) {
+		if (sink == null) {
 			throw new IllegalArgumentException();
 		}
 		HgDataFile dataFile = repo.getFileNode(file);
 		if (!dataFile.exists()) {
 			throw new FileNotFoundException();
 		}
-		byte[] content;
+		int revToExtract;
 		if (revision != null) {
-			content = dataFile.content(revision);
+			revToExtract = dataFile.getLocalRevision(revision);
 		} else {
-			content = dataFile.content(localRevision);
+			revToExtract = localRevision;
 		}
-		os.write(content);
+		dataFile.content(revToExtract, sink);
 	}
 }
--- a/src/org/tmatesoft/hg/core/RepositoryFacade.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/core/RepositoryFacade.java	Thu Feb 03 23:32:08 2011 +0100
@@ -41,6 +41,13 @@
 		repo = new HgLookup().detect(repoLocation.getCanonicalPath());
 		return repo != null && !repo.isInvalid();
 	}
+	
+	public HgRepository getRepository() {
+		if (repo == null) {
+			throw new IllegalStateException("Call any of #init*() methods first first");
+		}
+		return repo;
+	}
 
 	public LogCommand createLogCommand() {
 		return new LogCommand(repo/*, getCommandContext()*/);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/ByteArrayChannel.java	Thu Feb 03 23:32:08 2011 +0100
@@ -0,0 +1,91 @@
+/*
+ * 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@svnkit.com
+ */
+package org.tmatesoft.hg.internal;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.tmatesoft.hg.util.ByteChannel;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class ByteArrayChannel implements ByteChannel {
+	private final List<ByteBuffer> buffers;
+	private ByteBuffer target;
+	private byte[] result;
+	
+	public ByteArrayChannel() {
+		this(-1);
+	}
+	
+	public ByteArrayChannel(int size) {
+		if (size == -1) {
+			buffers = new LinkedList<ByteBuffer>();
+		} else {
+			if (size < 0) {
+				throw new IllegalArgumentException(String.valueOf(size));
+			}
+			buffers = null;
+			target = ByteBuffer.allocate(size);
+		}
+	}
+
+	// TODO document what happens on write after toArray() in each case
+	public int write(ByteBuffer buffer) throws Exception {
+		int rv = buffer.remaining();
+		if (buffers == null) {
+			target.put(buffer);
+		} else {
+			ByteBuffer copy = ByteBuffer.allocate(rv);
+			copy.put(buffer);
+			buffers.add(copy);
+		}
+		return rv;
+	}
+
+	public byte[] toArray() {
+		if (result != null) {
+			return result;
+		}
+		if (buffers == null) {
+			assert target.hasArray();
+			// int total = target.position();
+			// System.arraycopy(target.array(), new byte[total]);
+			// I don't want to duplicate byte[] for now
+			// although correct way of doing things is to make a copy and discard target
+			return target.array();
+		} else {
+			int total = 0;
+			for (ByteBuffer bb : buffers) {
+				bb.flip();
+				total += bb.limit();
+			}
+			result = new byte[total];
+			int off = 0;
+			for (ByteBuffer bb : buffers) {
+				bb.get(result, off, bb.limit());
+				off += bb.limit();
+			}
+			buffers.clear();
+			return result;
+		}
+	}
+}
--- a/src/org/tmatesoft/hg/internal/Filter.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/internal/Filter.java	Thu Feb 03 23:32:08 2011 +0100
@@ -28,6 +28,8 @@
  */
 public interface Filter {
 
+	// returns a buffer ready to be read. may return original buffer.
+	// original buffer may not be fully consumed, #compact() might be operation to perform 
 	ByteBuffer filter(ByteBuffer src);
 
 	interface Factory {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/FilterByteChannel.java	Thu Feb 03 23:32:08 2011 +0100
@@ -0,0 +1,53 @@
+/*
+ * 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@svnkit.com
+ */
+package org.tmatesoft.hg.internal;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+
+import org.tmatesoft.hg.util.ByteChannel;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class FilterByteChannel implements ByteChannel {
+	private final Filter[] filters;
+	private final ByteChannel delegate;
+	
+	public FilterByteChannel(ByteChannel delegateChannel, Collection<Filter> filtersToApply) {
+		if (delegateChannel == null || filtersToApply == null) {
+			throw new IllegalArgumentException();
+		}
+		delegate = delegateChannel;
+		filters = filtersToApply.toArray(new Filter[filtersToApply.size()]);
+	}
+
+	public int write(ByteBuffer buffer) throws Exception {
+		final int srcPos = buffer.position();
+		ByteBuffer processed = buffer;
+		for (Filter f : filters) {
+			// each next filter consumes not more than previous
+			// hence total consumed equals position shift in the original buffer
+			processed = f.filter(processed);
+		}
+		delegate.write(processed);
+		return buffer.position() - srcPos; // consumed as much from original buffer
+	}
+
+}
--- a/src/org/tmatesoft/hg/repo/HgDataFile.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgDataFile.java	Thu Feb 03 23:32:08 2011 +0100
@@ -18,6 +18,7 @@
 
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.TreeMap;
@@ -25,6 +26,7 @@
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.core.Path;
 import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.ByteChannel;
 
 
 
@@ -42,11 +44,18 @@
 	private final Path path;
 	private Metadata metadata;
 	
-	/*package-local*/HgDataFile(HgRepository hgRepo, Path path, RevlogStream content) {
+	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath, RevlogStream content) {
 		super(hgRepo, content);
-		this.path = path;
+		path = filePath;
 	}
-	
+
+	/*package-local*/HgDataFile(HgRepository hgRepo, Path filePath) {
+		super(hgRepo);
+		path = filePath;
+	}
+
+	// exists is not the best name possible. now it means no file with such name was ever known to the repo.
+	// it might be confused with files existed before but lately removed. 
 	public boolean exists() {
 		return content != null; // XXX need better impl
 	}
@@ -63,6 +72,21 @@
 	public byte[] content() {
 		return content(TIP);
 	}
+	
+	public void content(int revision, ByteChannel sink) throws /*TODO typed*/Exception {
+		byte[] content = content(revision);
+		ByteBuffer buf = ByteBuffer.allocate(512);
+		int left = content.length;
+		int offset = 0;
+		do {
+			buf.put(content, offset, Math.min(left, buf.remaining()));
+			buf.flip();
+			int consumed = sink.write(buf);
+			buf.compact();
+			offset += consumed;
+			left -= consumed;
+		} while (left > 0);
+	}
 
 	// for data files need to check heading of the file content for possible metadata
 	// @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8-
--- a/src/org/tmatesoft/hg/repo/HgRepository.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/HgRepository.java	Thu Feb 03 23:32:08 2011 +0100
@@ -139,13 +139,20 @@
 		String nPath = normalizePath.rewrite(path);
 		String storagePath = dataPathHelper.rewrite(nPath);
 		RevlogStream content = resolve(Path.create(storagePath));
-		return new HgDataFile(this, Path.create(nPath), content);
+		Path p = Path.create(nPath);
+		if (content == null) {
+			return new HgDataFile(this, p);
+		}
+		return new HgDataFile(this, p, content);
 	}
 
 	public HgDataFile getFileNode(Path path) {
 		String storagePath = dataPathHelper.rewrite(path.toString());
 		RevlogStream content = resolve(Path.create(storagePath));
-		// XXX no content when no file? or HgDataFile.exists() to detect that? How about files that were removed in previous releases?
+		// XXX no content when no file? or HgDataFile.exists() to detect that?
+		if (content == null) {
+			return new HgDataFile(this, path);
+		}
 		return new HgDataFile(this, path, content);
 	}
 
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Thu Feb 03 22:13:55 2011 +0100
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Thu Feb 03 23:32:08 2011 +0100
@@ -38,22 +38,28 @@
  */
 abstract class Revlog {
 
-	private final HgRepository hgRepo;
+	private final HgRepository repo;
 	protected final RevlogStream content;
 
-	protected Revlog(HgRepository hgRepo, RevlogStream content) {
+	protected Revlog(HgRepository hgRepo, RevlogStream contentStream) {
 		if (hgRepo == null) {
 			throw new IllegalArgumentException();
 		}
-		if (content == null) {
+		if (contentStream == null) {
 			throw new IllegalArgumentException();
 		}
-		this.hgRepo = hgRepo;
-		this.content = content;
+		repo = hgRepo;
+		content = contentStream;
+	}
+	
+	// invalid Revlog
+	protected Revlog(HgRepository hgRepo) {
+		repo = hgRepo;
+		content = null;
 	}
 
 	public final HgRepository getRepo() {
-		return hgRepo;
+		return repo;
 	}
 
 	public int getRevisionCount() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/util/ByteChannel.java	Thu Feb 03 23:32:08 2011 +0100
@@ -0,0 +1,34 @@
+/*
+ * 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@svnkit.com
+ */
+package org.tmatesoft.hg.util;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Much like {@link java.nio.channels.WritableByteChannel} except for thrown exception 
+ * 
+ * XXX Perhaps, we'll add CharChannel in the future to deal with character conversions/encodings 
+ * 
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public interface ByteChannel {
+	// XXX does int return value makes any sense given buffer keeps its read state
+	// not clear what retvalue should be in case some filtering happened inside write - i.e. return
+	// number of bytes consumed in 
+	int write(ByteBuffer buffer) throws Exception /*FIXME Exception type*/;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/org/tmatesoft/hg/test/TestByteChannel.java	Thu Feb 03 23:32:08 2011 +0100
@@ -0,0 +1,47 @@
+/*
+ * 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@svnkit.com
+ */
+package org.tmatesoft.hg.test;
+
+import java.util.Arrays;
+
+import org.junit.Assert;
+import org.tmatesoft.hg.core.CatCommand;
+import org.tmatesoft.hg.core.RepositoryFacade;
+import org.tmatesoft.hg.internal.ByteArrayChannel;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class TestByteChannel {
+
+	public static void main(String[] args) throws Exception {
+		RepositoryFacade rf = new RepositoryFacade();
+		rf.init();
+		HgDataFile file = rf.getRepository().getFileNode("COPYING");
+		int rev = HgRepository.TIP;
+		byte[] oldAccess = file.content(rev);
+		ByteArrayChannel ch = new ByteArrayChannel();
+		file.content(rev, ch);
+		byte[] newAccess = ch.toArray();
+		Assert.assertArrayEquals(oldAccess, newAccess);
+		//CatCommand cmd = rf.createCatCommand();
+	}
+}