diff src/org/tmatesoft/hg/core/HgStatusCommand.java @ 423:9c9c442b5f2e

Major refactoring of exception handling. Low-level API uses RuntimeExceptions, while checked are left for higher level
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 23 Mar 2012 22:51:18 +0100
parents 7f136a3fa671
children 6437d261048a
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgStatusCommand.java	Fri Mar 23 21:26:01 2012 +0100
+++ b/src/org/tmatesoft/hg/core/HgStatusCommand.java	Fri Mar 23 22:51:18 2012 +0100
@@ -22,13 +22,14 @@
 
 import java.io.IOException;
 import java.util.ConcurrentModificationException;
-import java.util.concurrent.CancellationException;
 
 import org.tmatesoft.hg.internal.ChangelogHelper;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgStatusCollector;
 import org.tmatesoft.hg.repo.HgStatusInspector;
 import org.tmatesoft.hg.repo.HgWorkingCopyStatusCollector;
+import org.tmatesoft.hg.util.CancelSupport;
+import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.Status;
 
@@ -161,11 +162,13 @@
 	 * Perform status operation according to parameters set.
 	 *  
 	 * @param statusHandler callback to get status information
-	 * @throws IOException if there are (further unspecified) errors while walking working copy
+	 * @throws HgCallbackTargetException wrapper for any exception user callback code may produce
+	 * @throws CancelledException if execution of the command was cancelled
+	 * @throws IOException FIXME EXCEPTIONS WTF it's doing here if there are (further unspecified) errors while walking working copy
 	 * @throws IllegalArgumentException if handler is <code>null</code>
 	 * @throws ConcurrentModificationException if this command already runs (i.e. being used from another thread)
 	 */
-	public void execute(HgStatusHandler statusHandler) throws CancellationException, HgException, IOException {
+	public void execute(HgStatusHandler statusHandler) throws HgCallbackTargetException, CancelledException, HgException, IOException {
 		if (statusHandler == null) {
 			throw new IllegalArgumentException();
 		}
@@ -177,7 +180,7 @@
 		try {
 			// XXX if I need a rough estimation (for ProgressMonitor) of number of work units,
 			// I may use number of files in either rev1 or rev2 manifest edition
-			mediator.start(statusHandler, new ChangelogHelper(repo, startRevision));
+			mediator.start(statusHandler, getCancelSupport(statusHandler, true), new ChangelogHelper(repo, startRevision));
 			if (endRevision == WORKING_COPY) {
 				HgWorkingCopyStatusCollector wcsc = scope != null ? HgWorkingCopyStatusCollector.create(repo, scope) : new HgWorkingCopyStatusCollector(repo);
 				wcsc.setBaseRevisionCollector(sc);
@@ -190,11 +193,10 @@
 					sc.walk(startRevision, endRevision, mediator);
 				}
 			}
-		} catch (HgCallbackTargetException.Wrap ex) { 
-			// seems too general to catch RuntimeException, i.e.
-			// unless catch is for very narrow piece of code, it's better not to catch any RTE (which may happen elsewhere, not only in handler)
-			// XXX Perhaps, need more detailed explanation in handlers that are expected to throw Wrap/RTE (i.e. HgChangesetHandler)
-			throw new HgCallbackTargetException(ex).setRevisionIndex(endRevision);
+		} catch (CancelledException ex) {
+			// this is our exception, thrown from Mediator.
+			// next check shall throw original cause of the stop - either HgCallbackTargetException or original CancelledException
+			mediator.checkFailure();
 		} finally {
 			mediator.done();
 		}
@@ -208,7 +210,7 @@
 		void handleStatus(HgStatus s);
 	}
 
-	private class Mediator implements HgStatusInspector {
+	private class Mediator implements HgStatusInspector, CancelSupport {
 		boolean needModified;
 		boolean needAdded;
 		boolean needRemoved;
@@ -219,68 +221,117 @@
 		boolean needCopies;
 		HgStatusHandler handler;
 		private ChangelogHelper logHelper;
+		private CancelSupport handlerCancelSupport;
+		private HgCallbackTargetException failure;
+		private CancelledException cancellation;
 
 		Mediator() {
 		}
 		
-		public void start(HgStatusHandler h, ChangelogHelper changelogHelper) {
+		public void start(HgStatusHandler h, CancelSupport hcs, ChangelogHelper changelogHelper) {
 			handler = h;
+			handlerCancelSupport = hcs;
 			logHelper = changelogHelper;
 		}
 
 		public void done() {
 			handler = null;
+			handlerCancelSupport = null;
 			logHelper = null;
+			failure = null;
+			cancellation = null;
 		}
 		
 		public boolean busy() {
 			return handler != null;
 		}
 
+		// XXX copy from ChangesetTransformer. Perhaps, can share the code?
+		public void checkFailure() throws HgCallbackTargetException, CancelledException {
+			// do not forget to clear exceptions for reuse of this instance 
+			if (failure != null) {
+				HgCallbackTargetException toThrow = failure;
+				failure = null; 
+				throw toThrow;
+			}
+			if (cancellation != null) {
+				CancelledException toThrow = cancellation;
+				cancellation = null;
+				throw toThrow;
+			}
+		}
+
+		// XXX copy from ChangesetTransformer. code sharing note above applies
+		public void checkCancelled() throws CancelledException {
+			if (failure != null || cancellation != null) {
+				// stop status iteration. Our exception is for the purposes of cancellation only,
+				// the one we have stored (this.cancellation) is for user
+				throw new CancelledException(); 
+			}
+		}
+
+		private void dispatch(HgStatus s) {
+			try {
+				handler.handleStatus(s);
+				handlerCancelSupport.checkCancelled();
+			} catch (HgCallbackTargetException ex) {
+				failure = ex;
+			} catch (CancelledException ex) {
+				cancellation = ex;
+			}
+		}
+
 		public void modified(Path fname) {
 			if (needModified) {
-				handler.handleStatus(new HgStatus(Modified, fname, logHelper));
+				dispatch(new HgStatus(Modified, fname, logHelper));
 			}
 		}
 		public void added(Path fname) {
 			if (needAdded) {
-				handler.handleStatus(new HgStatus(Added, fname, logHelper));
+				dispatch(new HgStatus(Added, fname, logHelper));
 			}
 		}
 		public void removed(Path fname) {
 			if (needRemoved) {
-				handler.handleStatus(new HgStatus(Removed, fname, logHelper));
+				dispatch(new HgStatus(Removed, fname, logHelper));
 			}
 		}
 		public void copied(Path fnameOrigin, Path fnameAdded) {
 			if (needCopies) {
 				// TODO post-1.0 in fact, merged files may report 'copied from' as well, correct status kind thus may differ from Added
-				handler.handleStatus(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
+				dispatch(new HgStatus(Added, fnameAdded, fnameOrigin, logHelper));
 			}
 		}
 		public void missing(Path fname) {
 			if (needMissing) {
-				handler.handleStatus(new HgStatus(Missing, fname, logHelper));
+				dispatch(new HgStatus(Missing, fname, logHelper));
 			}
 		}
 		public void unknown(Path fname) {
 			if (needUnknown) {
-				handler.handleStatus(new HgStatus(Unknown, fname, logHelper));
+				dispatch(new HgStatus(Unknown, fname, logHelper));
 			}
 		}
 		public void clean(Path fname) {
 			if (needClean) {
-				handler.handleStatus(new HgStatus(Clean, fname, logHelper));
+				dispatch(new HgStatus(Clean, fname, logHelper));
 			}
 		}
 		public void ignored(Path fname) {
 			if (needIgnored) {
-				handler.handleStatus(new HgStatus(Ignored, fname, logHelper));
+				dispatch(new HgStatus(Ignored, fname, logHelper));
 			}
 		}
 		
-		public void invalid(Path fname, Exception ex) {
-			handler.handleError(fname, new Status(Status.Kind.ERROR, "Failed to get file status", ex));
+		public void invalid(Path fname, Exception err) {
+			try {
+				handler.handleError(fname, new Status(Status.Kind.ERROR, "Failed to get file status", err));
+				handlerCancelSupport.checkCancelled();
+			} catch (HgCallbackTargetException ex) {
+				failure = ex;
+			} catch (CancelledException ex) {
+				cancellation = ex;
+			}
 		}
 	}
 }