This patch adds the concept of an IncrementalCommand as a DeferredCommand, it updates the command dispatching logic to use a time bound, round robin dispatch, it tries to prevent slow script warnings and detect when a script is canceled as a result of a slow script warning.
IncrementalCommands continue to be called on the background thread until their execute command returns false. This can be used to build things like DeferredIterators, generators, etc.
Review by: scottb
Suggested by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@755 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/javadoc/com/google/gwt/examples/IncrementalCommandExample.java b/user/javadoc/com/google/gwt/examples/IncrementalCommandExample.java
new file mode 100644
index 0000000..98130c7
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/IncrementalCommandExample.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.examples;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.user.client.DeferredCommand;
+import com.google.gwt.user.client.IncrementalCommand;
+import com.google.gwt.user.client.ui.Label;
+
+public class IncrementalCommandExample implements EntryPoint {
+
+ public void onModuleLoad() {
+ final Label label = new Label();
+
+ DeferredCommand.addCommand(new IncrementalCommand() {
+ private int index = 0;
+
+ protected static final int COUNT = 10;
+
+ public boolean execute() {
+ label.setText("IncrementalCommand - index " + Integer.toString(index));
+
+ return ++index < COUNT;
+ }
+ });
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/CommandCanceledException.java b/user/src/com/google/gwt/user/client/CommandCanceledException.java
new file mode 100644
index 0000000..a5e8329
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/CommandCanceledException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+/**
+ * Exception reported to the current {@link UncaughtExceptionHandler} when a
+ * deferred {@link Command} is canceled as a result of a slow script warning.
+ */
+public class CommandCanceledException extends RuntimeException {
+ private Command command;
+
+ public CommandCanceledException(Command command) {
+ this.command = command;
+ }
+
+ /**
+ * Returns the {@link Command} which was canceled by the user as a result of a
+ * slow script warning.
+ *
+ * @return the {@link Command} which was canceled by the user as a result of a
+ * slow script warning
+ */
+ public Command getCommand() {
+ return command;
+ }
+}
diff --git a/user/src/com/google/gwt/user/client/CommandExecutor.java b/user/src/com/google/gwt/user/client/CommandExecutor.java
new file mode 100644
index 0000000..e9b693e
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/CommandExecutor.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Class which executes {@link Command}s and {@link IncrementalCommand}s after
+ * all currently pending event handlers have completed. This class attempts to
+ * protect against slow script warnings by running commands in small time
+ * increments.
+ *
+ * <p>
+ * It is still possible that a poorly written command could cause a slow script
+ * warning which a user may choose to cancel. In that event, a
+ * {@link CommandCanceledException} or an
+ * {@link IncrementalCommandCanceledException} is reported through the current
+ * {@link UncaughtExceptionHandler} depending on the type of command which
+ * caused the warning. All other commands will continue to be executed.
+ * </p>
+ *
+ * TODO(mmendez): Can an SSW be detected without using a timer? Currently, if a
+ * {@link Command} or an {@link IncrementalCommand} calls either
+ * {@link Window#alert(String)} or the JavaScript <code>alert(String)</code>
+ * methods directly or indirectly then the {@link #cancellationTimer} can fire,
+ * resulting in a false SSW cancellation detection.
+ */
+class CommandExecutor {
+
+ /**
+ * A circular iterator used by this class. This iterator will wrap back to
+ * zero when it hits the end of the commands.
+ */
+ private class CircularIterator implements Iterator {
+ /**
+ * Index of the element where this iterator should wrap back to the
+ * beginning of the collection.
+ */
+ private int end;
+
+ /**
+ * Index of the last item returned by {@link #next()}.
+ */
+ private int last = -1;
+
+ /**
+ * Index of the next command to execute.
+ */
+ private int next = 0;
+
+ /**
+ * Returns <code>true</code> if there are more commands in the queue.
+ *
+ * @return <code>true</code> if there are more commands in the queue.
+ */
+ public boolean hasNext() {
+ return next < end;
+ }
+
+ /**
+ * Returns the next command from the queue. When the end of the dispatch
+ * region is reached it will wrap back to the start.
+ *
+ * @return next command from the queue.
+ */
+ public Object next() {
+ last = next;
+ Object command = commands.get(next++);
+ if (next >= end) {
+ next = 0;
+ }
+
+ return command;
+ }
+
+ /**
+ * Removes the command which was previously returned by {@link #next()}.
+ *
+ * @return the command which was previously returned by {@link #next()}.
+ */
+ public void remove() {
+ assert (last >= 0);
+
+ commands.remove(last);
+ --end;
+
+ if (last <= next) {
+ if (--next < 0) {
+ next = 0;
+ }
+ }
+
+ last = -1;
+ }
+
+ /**
+ * Returns the last element returned by {@link #next()}.
+ *
+ * @return last element returned by {@link #next()}
+ */
+ private Object getLast() {
+ assert (last >= 0);
+ return commands.get(last);
+ }
+
+ private void setEnd(int end) {
+ assert (end >= next);
+
+ this.end = end;
+ }
+
+ private void setLast(int last) {
+ this.last = last;
+ }
+
+ private boolean wasRemoved() {
+ return last == -1;
+ }
+ }
+
+ /**
+ * Default amount of time to wait before assuming that a script cancellation
+ * has taken place. This should be a platform dependent value, ultimately we
+ * may need to acquire this value based on a rebind decision. For now, we
+ * chose the smallest value known to cause an SSW.
+ */
+ private static final long DEFAULT_CANCELLATION_TIMEOUT_MILLIS = 10000;
+
+ /**
+ * Default amount of time to spend dispatching commands before we yield to the
+ * system.
+ */
+ private static final long DEFAULT_TIME_SLICE_MILLIS = 100;
+
+ /**
+ * Returns true the end time has been reached or exceeded.
+ *
+ * @param currentTimeMillis current time in milliseconds
+ * @param startTimeMillis end time in milliseconds
+ * @return true if the end time has been reached
+ */
+ private static boolean hasTimeSliceExpired(long currentTimeMillis,
+ long startTimeMillis) {
+ return Math.abs(currentTimeMillis - startTimeMillis) >= DEFAULT_TIME_SLICE_MILLIS;
+ }
+
+ /**
+ * Timer used to recover from script cancellations arising from slow script
+ * warnings.
+ */
+ private final Timer cancellationTimer = new Timer() {
+ public void run() {
+ if (!isExecuting()) {
+ /*
+ * If we are not executing, then the cancellation timer expired right
+ * about the time that the command dispatcher finished -- we are okay so
+ * we just exit.
+ */
+ return;
+ }
+
+ doCommandCanceled();
+ }
+ };
+
+ /**
+ * Commands that need to be executed.
+ */
+ private final List commands = new ArrayList();
+
+ /**
+ * Set to <code>true</code> when we are actively dispatching commands.
+ */
+ private boolean executing = false;
+
+ /**
+ * Timer used to drive the dispatching of commands in the background.
+ */
+ private final Timer executionTimer = new Timer() {
+ public void run() {
+ assert (!isExecuting());
+
+ setExecutionTimerPending(false);
+
+ doExecuteCommands(System.currentTimeMillis());
+ }
+ };
+
+ /**
+ * Set to <code>true</code> when we are waiting for a dispatch timer event
+ * to fire.
+ */
+ private boolean executionTimerPending = false;
+
+ /**
+ * The single circular iterator instance that we use to iterate over the
+ * collection of commands.
+ */
+ private final CircularIterator iterator = new CircularIterator();
+
+ /**
+ * Submits a {@link Command} for execution.
+ *
+ * @param command command to submit
+ */
+ public void submit(Command command) {
+ commands.add(command);
+
+ maybeStartExecutionTimer();
+ }
+
+ /**
+ * Submits an {@link IncrementalCommand} for execution.
+ *
+ * @param command command to submit
+ */
+ public void submit(IncrementalCommand command) {
+ commands.add(command);
+
+ maybeStartExecutionTimer();
+ }
+
+ /**
+ * Reports either a {@link CommandCanceledException} or an
+ * {@link IncrementalCommandCanceledException} back through the
+ * {@link UncaughtExceptionHandler} if one is set.
+ */
+ protected void doCommandCanceled() {
+ Object cmd = iterator.getLast();
+ iterator.remove();
+ assert (cmd != null);
+
+ RuntimeException ex = null;
+ if (cmd instanceof Command) {
+ ex = new CommandCanceledException((Command) cmd);
+ } else if (cmd instanceof IncrementalCommand) {
+ ex = new IncrementalCommandCanceledException((IncrementalCommand) cmd);
+ }
+
+ if (ex != null) {
+ UncaughtExceptionHandler ueh = GWT.getUncaughtExceptionHandler();
+ if (ueh != null) {
+ ueh.onUncaughtException(ex);
+ }
+ }
+
+ setExecuting(false);
+
+ maybeStartExecutionTimer();
+ }
+
+ /**
+ * This method will dispatch commands from the command queue. It will dispatch
+ * commands until one of the following conditions is <code>true</code>:
+ * <ul>
+ * <li>It consumed its dispatching time slice {@value #DEFAULT_TIME_SLICE_MILLIS}</li>
+ * <li>It encounters a <code>null</code> in the command queue</li>
+ * <li>All commands which were present at the start of the dispatching have
+ * been removed from the command queue</li>
+ * <li>The command that it was processing was canceled due to a false
+ * cancellation -- in this case we exit without updating any state</li>
+ * </ul>
+ *
+ * @param startTimeMillis the time when this method started
+ */
+ protected void doExecuteCommands(long startTimeMillis) {
+ assert (!isExecutionTimerPending());
+
+ boolean wasCanceled = false;
+ try {
+ setExecuting(true);
+
+ iterator.setEnd(commands.size());
+
+ cancellationTimer.schedule((int) DEFAULT_CANCELLATION_TIMEOUT_MILLIS);
+
+ while (iterator.hasNext()) {
+ Object element = iterator.next();
+
+ boolean removeCommand = true;
+ try {
+ if (element == null) {
+ // null forces a yield or pause in execution
+ return;
+ }
+
+ if (element instanceof Command) {
+ Command command = (Command) element;
+ command.execute();
+ } else if (element instanceof IncrementalCommand) {
+ IncrementalCommand incrementalCommand = (IncrementalCommand) element;
+ removeCommand = !incrementalCommand.execute();
+ }
+
+ } finally {
+ wasCanceled = iterator.wasRemoved();
+ if (wasCanceled) {
+ /*
+ * The iterator may have already had its remove method called, if
+ * it has, then we need to exit without updating any state
+ */
+ return;
+ }
+
+ if (removeCommand) {
+ iterator.remove();
+ }
+ }
+
+ if (hasTimeSliceExpired(System.currentTimeMillis(), startTimeMillis)) {
+ // the time slice has expired
+ return;
+ }
+ }
+ } finally {
+ if (!wasCanceled) {
+ cancellationTimer.cancel();
+
+ setExecuting(false);
+
+ maybeStartExecutionTimer();
+ }
+ }
+ }
+
+ /**
+ * Starts the dispatch timer if there are commands to dispatch and we are not
+ * waiting for a dispatch timer and we are not actively dispatching.
+ */
+ protected void maybeStartExecutionTimer() {
+ if (!commands.isEmpty() && !isExecutionTimerPending() && !isExecuting()) {
+ setExecutionTimerPending(true);
+ executionTimer.schedule(1);
+ }
+ }
+
+ /**
+ * This method is for testing only.
+ */
+ List getPendingCommands() {
+ return commands;
+ }
+
+ /**
+ * This method is for testing only.
+ */
+ void setExecuting(boolean executing) {
+ this.executing = executing;
+ }
+
+ /**
+ * This method is for testing only.
+ */
+ void setLast(int last) {
+ iterator.setLast(last);
+ }
+
+ /**
+ * Returns <code>true</code> if this instance is currently dispatching
+ * commands.
+ *
+ * @return <code>true</code> if this instance is currently dispatching
+ * commands
+ */
+ private boolean isExecuting() {
+ return executing;
+ }
+
+ /**
+ * Returns <code>true</code> if a the dispatch timer was scheduled but it
+ * still has not fired.
+ *
+ * @return <code>true</code> if a the dispatch timer was scheduled but it
+ * still has not fired
+ */
+ private boolean isExecutionTimerPending() {
+ return executionTimerPending;
+ }
+
+ private void setExecutionTimerPending(boolean pending) {
+ executionTimerPending = pending;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/DeferredCommand.java b/user/src/com/google/gwt/user/client/DeferredCommand.java
index 19f0089..691db98 100644
--- a/user/src/com/google/gwt/user/client/DeferredCommand.java
+++ b/user/src/com/google/gwt/user/client/DeferredCommand.java
@@ -15,26 +15,14 @@
*/
package com.google.gwt.user.client;
-import java.util.Vector;
-
/**
* This class allows you to execute code after all currently pending event
- * handlers have completed, using the {@link #add(Command)} method. This is
- * useful when you need to execute code outside of the context of the current
- * stack.
+ * handlers have completed, using the {@link #addCommand(Command)} or
+ * {@link #addCommand(IncrementalCommand)} methods. This is useful when you need
+ * to execute code outside of the context of the current stack.
*/
public class DeferredCommand {
-
- /**
- * The list of commands to be processed the next time a timer event is
- * handled.
- */
- private static Vector deferredCommands = new Vector();
-
- /**
- * Records whether a timer is pending so we don't set multiple ones.
- */
- private static boolean timerIsActive = false;
+ private static final CommandExecutor commandExecutor = new CommandExecutor();
/**
* Enqueues a {@link Command} to be fired after all current events have been
@@ -44,60 +32,41 @@
* inserted into the queue. Any events added after the pause will
* wait for an additional cycle through the system event loop before
* executing. Pauses are cumulative.
+ *
+ * @deprecated As of release 1.4, replaced by {@link #addCommand(Command)}
*/
public static void add(Command cmd) {
- deferredCommands.add(cmd);
- maybeSetDeferredCommandTimer();
+ commandExecutor.submit(cmd);
}
/**
- * Executes the current set of deferred commands before returning.
+ * Enqueues a {@link Command} to be fired after all current events have been
+ * handled.
+ *
+ * Note that the {@link Command} should not perform any blocking operations.
+ *
+ * @param cmd the command to be fired. If cmd is null, a "pause" will be
+ * inserted into the queue. Any events added after the pause will
+ * wait for an additional cycle through the system event loop before
+ * executing. Pauses are cumulative.
*/
- private static void flushDeferredCommands() {
- /*
- * Only execute the commands present at the beginning, and always pull from
- * the beginning of the list. This ensures that if any commands are added
- * while executing the current ones, they will stay in the list for the next
- * pass (otherwise, they wouldn't appear deferred).
- *
- * If a deferred command throws an exception, that's okay, we'll just pick
- * up where we left off on the next tick.
- */
- for (int i = 0, max = deferredCommands.size(); i < max; ++i) {
- Command current = (Command) deferredCommands.remove(0);
- if (current == null) {
- /*
- * This is an indication that we should defer everything else in the
- * list until the next tick. Leave everything else in the queue.
- */
- return;
- } else {
- current.execute();
- }
- }
+ public static void addCommand(Command cmd) {
+ commandExecutor.submit(cmd);
}
- private static void maybeSetDeferredCommandTimer() {
- if (!timerIsActive && !deferredCommands.isEmpty()) {
- // There are some deferred commands in the queue.
- // Make sure a timer will fire for them.
- new Timer() {
- public void run() {
- try {
-
- // execute the pending commands
- flushDeferredCommands();
-
- } finally {
- // this timer has now fired and will not fire again
- timerIsActive = false;
-
- // always setup the next timer, even if we're throwing an exception
- maybeSetDeferredCommandTimer();
- }
- }
- }.schedule(1);
- timerIsActive = true;
- }
+ /**
+ * Enqueues an {@link IncrementalCommand} to be fired after all current events
+ * have been handled.
+ *
+ * Note that the {@link IncrementalCommand} should not perform any blocking
+ * operations.
+ *
+ * @param cmd the command to be fired. If cmd is null, a "pause" will be
+ * inserted into the queue. Any events added after the pause will
+ * wait for an additional cycle through the system event loop before
+ * executing. Pauses are cumulative.
+ */
+ public static void addCommand(IncrementalCommand cmd) {
+ commandExecutor.submit(cmd);
}
}
diff --git a/user/src/com/google/gwt/user/client/IncrementalCommand.java b/user/src/com/google/gwt/user/client/IncrementalCommand.java
new file mode 100644
index 0000000..d2f9739
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/IncrementalCommand.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+/**
+ * An <code>IncrementalCommand</code> is a command that is broken into one or
+ * more substeps, each substep brings the whole command nearer to completion.
+ * The command is complete when <code>execute()</code> returns
+ * <code>false</code>.
+ *
+ * {@example com.google.gwt.examples.IncrementalCommandExample}
+ */
+public interface IncrementalCommand {
+ /**
+ * Causes the <code>IncrementalCommand</code> to execute its encapsulated
+ * behavior.
+ *
+ * @return <code>true</code> if the command has more work to do,
+ * <code>false</code> otherwise
+ */
+ boolean execute();
+}
diff --git a/user/src/com/google/gwt/user/client/IncrementalCommandCanceledException.java b/user/src/com/google/gwt/user/client/IncrementalCommandCanceledException.java
new file mode 100644
index 0000000..b2cf291
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/IncrementalCommandCanceledException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+/**
+ * Exception reported to the current {@link UncaughtExceptionHandler} when a
+ * deferred {@link IncrementalCommand} is canceled as a result of a slow script
+ * warning.
+ */
+public class IncrementalCommandCanceledException extends RuntimeException {
+ private IncrementalCommand command;
+
+ public IncrementalCommandCanceledException(IncrementalCommand command) {
+ this.command = command;
+ }
+
+ /**
+ * Returns the {@link IncrementalCommand} which was canceled by the user as a
+ * result of a slow script warning.
+ *
+ * @return the {@link IncrementalCommand} which was canceled by the user as a
+ * result of a slow script warning
+ */
+ public IncrementalCommand getCommand() {
+ return command;
+ }
+}
diff --git a/user/test/com/google/gwt/user/client/CommandExecutorTest.java b/user/test/com/google/gwt/user/client/CommandExecutorTest.java
new file mode 100644
index 0000000..57e8b17
--- /dev/null
+++ b/user/test/com/google/gwt/user/client/CommandExecutorTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.client;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Test cases for {@link CommandExecutor}.
+ */
+public class CommandExecutorTest extends GWTTestCase {
+
+ private static class NonRestartingCommandExecutor extends CommandExecutor {
+ protected void maybeStartExecutionTimer() {
+ // keeps the executing timer for interfering with the test
+ }
+ }
+
+ private static class TestCommand implements Command {
+ private boolean executed;
+
+ public boolean didExecute() {
+ return executed;
+ }
+
+ public void execute() {
+ executed = true;
+ }
+ }
+
+ private static class TestIncrementalCommand implements IncrementalCommand {
+ private boolean done = false;
+ private int executeCount;
+
+ public boolean execute() {
+ ++executeCount;
+
+ return !isDone();
+ }
+
+ public int getExecuteCount() {
+ return executeCount;
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public void setDone(boolean done) {
+ this.done = done;
+ }
+ }
+
+ /**
+ * A sufficiently large delay to let the SSW triggers.
+ */
+ private static final int TEST_FINISH_DELAY_MILLIS = 40000;
+
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks that we can recover after a cancellation
+ */
+ public void testDoExecuteCommands_CancellationRecovery() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ final Command c1 = new Command() {
+ public void execute() {
+ }
+ };
+
+ ce.setExecuting(true);
+ ce.submit(c1);
+ ce.setLast(0);
+
+ final UncaughtExceptionHandler originalUEH = GWT.getUncaughtExceptionHandler();
+
+ UncaughtExceptionHandler ueh1 = new UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (!(e instanceof CommandCanceledException)
+ && !(e instanceof IncrementalCommandCanceledException)) {
+ originalUEH.onUncaughtException(e);
+ return;
+ }
+
+ CommandCanceledException cce = (CommandCanceledException) e;
+ if (cce.getCommand() != c1) {
+ fail("CommandCanceledException did not contain the correct failed command");
+ }
+
+ // Submit some more work and do another dispatch
+ ce.submit(new IncrementalCommand() {
+ public boolean execute() {
+ return false;
+ }
+ });
+
+ ce.submit(new Command() {
+ public void execute() {
+ finishTest();
+ }
+ });
+
+ delayTestFinish(TEST_FINISH_DELAY_MILLIS);
+ ce.doExecuteCommands(System.currentTimeMillis());
+ }
+ };
+
+ GWT.setUncaughtExceptionHandler(ueh1);
+ ce.doCommandCanceled();
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks Command cancellation detection
+ */
+ public void testDoExecuteCommands_CommandCancellation() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ final Command c1 = new Command() {
+ public void execute() {
+ }
+ };
+
+ // Setup the cancellation state
+ ce.setExecuting(true);
+ ce.submit(c1);
+ ce.setLast(0);
+
+ final UncaughtExceptionHandler originalUEH = GWT.getUncaughtExceptionHandler();
+
+ UncaughtExceptionHandler ueh1 = new UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (!(e instanceof CommandCanceledException)) {
+ originalUEH.onUncaughtException(e);
+ return;
+ }
+
+ CommandCanceledException cce = (CommandCanceledException) e;
+ if (cce.getCommand() != c1) {
+ fail("CommandCanceledException did not contain the correct failed command");
+ }
+ }
+ };
+
+ GWT.setUncaughtExceptionHandler(ueh1);
+ ce.doCommandCanceled();
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks that calling {@link CommandExecutor#doExecuteCommands(long)} with no
+ * items in the queue is safe
+ */
+ public void testDoExecuteCommands_emptyQueue() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ ce.doExecuteCommands(System.currentTimeMillis());
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks IncrementalCommand cancellation detection
+ */
+ public void testDoExecuteCommands_IncrementalCommandCancellation() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ final IncrementalCommand ic = new IncrementalCommand() {
+ public boolean execute() {
+ return false;
+ }
+ };
+
+ // setup the cancellation state
+ ce.setExecuting(true);
+ ce.submit(ic);
+ ce.setLast(0);
+
+ final UncaughtExceptionHandler originalUEH = GWT.getUncaughtExceptionHandler();
+
+ UncaughtExceptionHandler ueh1 = new UncaughtExceptionHandler() {
+ public void onUncaughtException(Throwable e) {
+ if (!(e instanceof CommandCanceledException)) {
+ originalUEH.onUncaughtException(e);
+ return;
+ }
+
+ IncrementalCommandCanceledException icce = (IncrementalCommandCanceledException) e;
+ if (icce.getCommand() != ic) {
+ fail("IncrementalCommandCanceledException did not contain the correct failed command");
+ }
+ }
+ };
+
+ GWT.setUncaughtExceptionHandler(ueh1);
+ ce.doCommandCanceled();
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks that an incremental command executes and is removed from the queue
+ * when it is done
+ */
+ public void testDoExecuteCommands_IncrementalCommands() {
+ TestIncrementalCommand tic = new TestIncrementalCommand();
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ tic.setDone(true);
+ ce.submit(tic);
+ ce.doExecuteCommands(System.currentTimeMillis());
+ assertTrue(tic.getExecuteCount() > 0);
+ assertTrue(ce.getPendingCommands().isEmpty());
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks that null does in fact cause a pause.
+ */
+ public void testDoExecuteCommands_pause() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ TestCommand tc1 = new TestCommand();
+ TestCommand tc2 = new TestCommand();
+
+ ce.submit(tc1);
+ ce.submit((Command) null);
+ ce.submit(tc2);
+
+ ce.doExecuteCommands(System.currentTimeMillis());
+
+ assertTrue(tc1.didExecute() && !tc2.didExecute());
+ assertEquals(1, ce.getPendingCommands().size());
+ assertTrue(ce.getPendingCommands().contains(tc2));
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#doExecuteCommands(int)}.
+ *
+ * Checks that after one pass dispatch pass, we still have the incremental
+ * command in the queue
+ */
+ public void testDoExecuteCommands_timeSliceUsage() {
+ final CommandExecutor ce = new NonRestartingCommandExecutor();
+
+ Command tc = new TestCommand();
+ ce.submit(tc);
+
+ TestIncrementalCommand tic = new TestIncrementalCommand();
+ ce.submit(tic);
+ ce.doExecuteCommands(System.currentTimeMillis());
+
+ assertEquals(1, ce.getPendingCommands().size());
+ assertTrue(ce.getPendingCommands().contains(tic));
+ assertTrue(tic.getExecuteCount() > 0);
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#submit(com.google.gwt.user.client.Command)}.
+ *
+ * <p/> Cases:
+ * <ul>
+ * <li>Submit <code>null</code></li>
+ * <li>Submit {@link Command} and make sure that it fires</li>
+ * </ul>
+ */
+ public void testSubmitCommand() {
+ CommandExecutor ce = new CommandExecutor();
+ ce.submit((Command) null);
+
+ delayTestFinish(TEST_FINISH_DELAY_MILLIS);
+
+ ce.submit(new Command() {
+ public void execute() {
+ finishTest();
+ }
+ });
+ }
+
+ /**
+ * Test method for
+ * {@link com.google.gwt.user.client.CommandExecutor#submit(com.google.gwt.user.client.IncrementalCommand)}.
+ *
+ * <p/> Cases:
+ * <ul>
+ * <li>Submit <code>null</code></li>
+ * <li>Submit {@link IncrementalCommand} and make sure that it fires as many
+ * times as we want it to</li>
+ * </ul>
+ */
+ public void testSubmitIncrementalCommand() {
+ CommandExecutor ce = new CommandExecutor();
+ ce.submit((Command) null);
+
+ delayTestFinish(TEST_FINISH_DELAY_MILLIS);
+
+ ce.submit(new IncrementalCommand() {
+ private int executionCount = 0;
+
+ public boolean execute() {
+ if (++executionCount >= 10) {
+ fail("IncrementalCommand was fired more than 10 times");
+ }
+
+ if (executionCount == 9) {
+ finishTest();
+ }
+
+ return executionCount < 10;
+ }
+ });
+ }
+}