changeset 312:f9f3e9b67ccc

Facilitate cancellation and progress reporting in changelog and manifest iterations
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 27 Sep 2011 05:29:12 +0200
parents b9592e21176a
children c1e3c18fd2f2
files src/org/tmatesoft/hg/core/HgChangesetHandler.java src/org/tmatesoft/hg/internal/IterateControlMediator.java src/org/tmatesoft/hg/repo/HgChangelog.java src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/util/CancelSupport.java test/org/tmatesoft/hg/test/TestAuxUtilities.java
diffstat 6 files changed, 206 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Mon Sep 26 04:06:04 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgChangesetHandler.java	Tue Sep 27 05:29:12 2011 +0200
@@ -16,6 +16,8 @@
  */
 package org.tmatesoft.hg.core;
 
+import org.tmatesoft.hg.util.CancelledException;
+
 /**
  * Callback to process {@link HgChangeset changesets}.
  * 
@@ -25,8 +27,9 @@
 public interface HgChangesetHandler/*XXX perhaps, shall parameterize with exception clients can throw, like: <E extends Exception>*/ {
 	/**
 	 * @param changeset not necessarily a distinct instance each time, {@link HgChangeset#clone() clone()} if need a copy.
+	 * @throws CancelledException if handler is not interested in more changesets and iteration shall stop
 	 * @throws RuntimeException or any subclass thereof to indicate error. General contract is that RuntimeExceptions 
 	 * will be re-thrown wrapped into {@link HgCallbackTargetException}.  
 	 */
-	void next(HgChangeset changeset)/* throws E*/;
+	void next(HgChangeset changeset) throws CancelledException;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/IterateControlMediator.java	Tue Sep 27 05:29:12 2011 +0200
@@ -0,0 +1,55 @@
+/*
+ * 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 org.tmatesoft.hg.internal.Lifecycle.Callback;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
+
+/**
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class IterateControlMediator {
+
+	private final CancelSupport src;
+	private Callback receiver;
+
+	public IterateControlMediator(CancelSupport source, Lifecycle.Callback target) {
+		assert target != null;
+		src = source;
+		receiver = target;
+	}
+
+	public boolean checkCancelled() {
+		if (src == null) {
+			return false;
+		}
+		try {
+			src.checkCancelled();
+			return false;
+		} catch (CancelledException ex) {
+			receiver.stop();
+			return true;
+		}
+	}
+	
+	public void stop() {
+		receiver.stop();
+	}
+}
--- a/src/org/tmatesoft/hg/repo/HgChangelog.java	Mon Sep 26 04:06:04 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgChangelog.java	Tue Sep 27 05:29:12 2011 +0200
@@ -33,8 +33,12 @@
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.IterateControlMediator;
+import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Pool;
 import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 /**
  * Representation of the Mercurial changelog file (list of ChangeSets)
@@ -338,16 +342,19 @@
 		}
 	}
 
-	private static class RawCsetParser implements RevlogStream.Inspector {
+	private static class RawCsetParser implements RevlogStream.Inspector, Lifecycle {
 		
 		private final Inspector inspector;
 		private final Pool<String> usersPool;
 		private final RawChangeset cset = new RawChangeset();
+		private final ProgressSupport progressHelper;
+		private IterateControlMediator iterateControl;
 
 		public RawCsetParser(HgChangelog.Inspector delegate) {
 			assert delegate != null;
 			inspector = delegate;
 			usersPool = new Pool<String>();
+			progressHelper = ProgressSupport.Factory.get(delegate);
 		}
 
 		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
@@ -356,9 +363,23 @@
 				cset.init(data, 0, data.length, usersPool);
 				// XXX there's no guarantee for Changeset.Callback that distinct instance comes each time, consider instance reuse
 				inspector.next(revisionNumber, Nodeid.fromBinary(nodeid, 0), cset);
+				progressHelper.worked(1);
 			} catch (Exception ex) {
 				throw new HgBadStateException(ex); // FIXME exception handling
 			}
+			if (iterateControl != null) {
+				iterateControl.checkCancelled();
+			}
+		}
+
+		public void start(int count, Callback callback, Object token) {
+			CancelSupport cs = CancelSupport.Factory.get(inspector, null);
+			iterateControl = cs == null ? null : new IterateControlMediator(cs, callback);
+			progressHelper.start(count);
+		}
+
+		public void finish(Object token) {
+			progressHelper.done();
 		}
 	}
 }
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Mon Sep 26 04:06:04 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Tue Sep 27 05:29:12 2011 +0200
@@ -28,10 +28,13 @@
 import org.tmatesoft.hg.internal.DataAccess;
 import org.tmatesoft.hg.internal.DigestHelper;
 import org.tmatesoft.hg.internal.Experimental;
+import org.tmatesoft.hg.internal.IterateControlMediator;
 import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Pool2;
 import org.tmatesoft.hg.internal.RevlogStream;
+import org.tmatesoft.hg.util.CancelSupport;
 import org.tmatesoft.hg.util.Path;
+import org.tmatesoft.hg.util.ProgressSupport;
 
 
 /**
@@ -277,13 +280,14 @@
 		}
 	}
 
-	private static class ManifestParser implements RevlogStream.Inspector {
-		private boolean gtg = true; // good to go
+	private static class ManifestParser implements RevlogStream.Inspector, Lifecycle {
 		private final Inspector inspector;
 		private final Inspector2 inspector2;
 		private Pool2<Nodeid> nodeidPool, thisRevPool;
 		private final Pool2<PathProxy> fnamePool;
 		private byte[] nodeidLookupBuffer = new byte[20]; // get reassigned each time new Nodeid is added to pool
+		private final ProgressSupport progressHelper;
+		private IterateControlMediator iterateControl;
 		
 		public ManifestParser(Inspector delegate) {
 			assert delegate != null;
@@ -292,20 +296,21 @@
 			nodeidPool = new Pool2<Nodeid>();
 			fnamePool = new Pool2<PathProxy>();
 			thisRevPool = new Pool2<Nodeid>();
+			progressHelper = ProgressSupport.Factory.get(delegate);
 		}
 		
 		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess da) {
-			if (!gtg) {
-				return;
-			}
 			try {
-				gtg = gtg && inspector.begin(revisionNumber, new Nodeid(nodeid, true), linkRevision);
+				if (!inspector.begin(revisionNumber, new Nodeid(nodeid, true), linkRevision)) {
+					iterateControl.stop();
+					return;
+				}
 				Path fname = null;
 				Flags flags = null;
 				Nodeid nid = null;
 				int i;
 				byte[] data = da.byteArray();
-				for (i = 0; gtg && i < actualLen; i++) {
+				for (i = 0; i < actualLen; i++) {
 					int x = i;
 					for( ; data[i] != '\n' && i < actualLen; i++) {
 						if (fname == null && data[i] == 0) {
@@ -337,18 +342,26 @@
 						} else {
 							flags = null;
 						}
+						boolean good2go;
 						if (inspector2 == null) {
 							String flagString = flags == null ? null : flags.nativeString();
-							gtg = inspector.next(nid, fname.toString(), flagString);
+							good2go = inspector.next(nid, fname.toString(), flagString);
 						} else {
-							gtg = inspector2.next(nid, fname, flags);
+							good2go = inspector2.next(nid, fname, flags);
+						}
+						if (!good2go) {
+							iterateControl.stop();
+							return;
 						}
 					}
 					nid = null;
 					fname = null;
 					flags = null;
 				}
-				gtg = gtg && inspector.end(revisionNumber);
+				if (!inspector.end(revisionNumber)) {
+					iterateControl.stop();
+					return;
+				}
 				//
 				// keep only actual file revisions, found at this version 
 				// (next manifest is likely to refer to most of them, although in specific cases 
@@ -357,10 +370,21 @@
 				Pool2<Nodeid> t = nodeidPool;
 				nodeidPool = thisRevPool;
 				thisRevPool = t;
+				progressHelper.worked(1);
 			} catch (IOException ex) {
 				throw new HgBadStateException(ex);
 			}
 		}
+
+		public void start(int count, Callback callback, Object token) {
+			CancelSupport cs = CancelSupport.Factory.get(inspector, null);
+			iterateControl = new IterateControlMediator(cs, callback);
+			progressHelper.start(count);
+		}
+
+		public void finish(Object token) {
+			progressHelper.done();
+		}
 	}
 	
 	private static class RevisionMapper implements RevlogStream.Inspector, Lifecycle {
--- a/src/org/tmatesoft/hg/util/CancelSupport.java	Mon Sep 26 04:06:04 2011 +0200
+++ b/src/org/tmatesoft/hg/util/CancelSupport.java	Tue Sep 27 05:29:12 2011 +0200
@@ -43,6 +43,17 @@
 		 * @return target if it's capable checking cancellation status or no-op implementation that never cancels.
 				 */
 		public static CancelSupport get(Object target) {
+			CancelSupport cs = get(target, null);
+			if (cs != null) {
+				return cs;
+			}
+			return new CancelSupport() {
+				public void checkCancelled() {
+				}
+			};
+		}
+		
+		public static CancelSupport get(Object target, CancelSupport defaultValue) {
 			if (target instanceof  CancelSupport) {
 				return (CancelSupport) target;
 			}
@@ -52,10 +63,7 @@
 					return cs;
 				}
 			}
-			return new CancelSupport() {
-				public void checkCancelled() {
-				}
-			};
+			return defaultValue;
 		}
 	}
 
--- a/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Mon Sep 26 04:06:04 2011 +0200
+++ b/test/org/tmatesoft/hg/test/TestAuxUtilities.java	Tue Sep 27 05:29:12 2011 +0200
@@ -18,7 +18,14 @@
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.ArrayHelper;
+import org.tmatesoft.hg.repo.HgChangelog;
+import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Adaptable;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
 
 /**
  *
@@ -52,4 +59,76 @@
 		}
 		return rebuilt;
 	}
+
+	@Test
+	public void testCancelSupport() throws Exception {
+		HgRepository repository = Configuration.get().find("branches-1"); // any repo with more revisions
+		class CancelImpl implements CancelSupport {
+			private boolean shallStop = false;
+			public void stop() {
+				shallStop = true;
+			}
+			public void checkCancelled() throws CancelledException {
+				if (shallStop) {
+					throw new CancelledException();
+				}
+			}
+		}
+		class InspectorImplementsCancel implements HgChangelog.Inspector, CancelSupport {
+			public final int when2stop;
+			public int lastVisitet = 0;
+			private final CancelImpl cancelImpl = new CancelImpl(); 
+
+			public InspectorImplementsCancel(int limit) {
+				when2stop = limit;
+			}
+			
+			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+				lastVisitet = revisionNumber;
+				if (revisionNumber == when2stop) {
+					cancelImpl.stop();
+				}
+			}
+
+			public void checkCancelled() throws CancelledException {
+				cancelImpl.checkCancelled();
+			}
+		};
+		class InspectorImplementsAdaptable implements HgChangelog.Inspector, Adaptable {
+			public final int when2stop;
+			public int lastVisitet = 0;
+			private final CancelImpl cancelImpl = new CancelImpl();
+			
+			public InspectorImplementsAdaptable(int limit) {
+				when2stop = limit;
+			}
+			
+			public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
+				lastVisitet = revisionNumber;
+				if (revisionNumber == when2stop) {
+					cancelImpl.stop();
+				}
+			}
+			@SuppressWarnings("unchecked")
+			public <T> T getAdapter(Class<T> adapterClass) {
+				if (CancelSupport.class == adapterClass) {
+					return (T) cancelImpl;
+				}
+				return null;
+			}
+			
+		}
+		//
+		InspectorImplementsCancel insp1;
+		repository.getChangelog().all(insp1= new InspectorImplementsCancel(2));
+		Assert.assertEquals(insp1.when2stop, insp1.lastVisitet);
+		repository.getChangelog().all(insp1 = new InspectorImplementsCancel(12));
+		Assert.assertEquals(insp1.when2stop, insp1.lastVisitet);
+		//
+		InspectorImplementsAdaptable insp2;
+		repository.getChangelog().all(insp2= new InspectorImplementsAdaptable(3));
+		Assert.assertEquals(insp2.when2stop, insp2.lastVisitet);
+		repository.getChangelog().all(insp2 = new InspectorImplementsAdaptable(10));
+		Assert.assertEquals(insp2.when2stop, insp2.lastVisitet);
+	}
 }