KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > client > CommandExecutor


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.user.client;
17
18 import com.google.gwt.core.client.GWT;
19 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
20
21 import java.util.ArrayList JavaDoc;
22 import java.util.Iterator JavaDoc;
23 import java.util.List JavaDoc;
24
25 /**
26  * Class which executes {@link Command}s and {@link IncrementalCommand}s after
27  * all currently pending event handlers have completed. This class attempts to
28  * protect against slow script warnings by running commands in small time
29  * increments.
30  *
31  * <p>
32  * It is still possible that a poorly written command could cause a slow script
33  * warning which a user may choose to cancel. In that event, a
34  * {@link CommandCanceledException} or an
35  * {@link IncrementalCommandCanceledException} is reported through the current
36  * {@link UncaughtExceptionHandler} depending on the type of command which
37  * caused the warning. All other commands will continue to be executed.
38  * </p>
39  *
40  * TODO(mmendez): Can an SSW be detected without using a timer? Currently, if a
41  * {@link Command} or an {@link IncrementalCommand} calls either
42  * {@link Window#alert(String)} or the JavaScript <code>alert(String)</code>
43  * methods directly or indirectly then the {@link #cancellationTimer} can fire,
44  * resulting in a false SSW cancellation detection.
45  */

46 class CommandExecutor {
47
48   /**
49    * A circular iterator used by this class. This iterator will wrap back to
50    * zero when it hits the end of the commands.
51    */

52   private class CircularIterator implements Iterator JavaDoc {
53     /**
54      * Index of the element where this iterator should wrap back to the
55      * beginning of the collection.
56      */

57     private int end;
58
59     /**
60      * Index of the last item returned by {@link #next()}.
61      */

62     private int last = -1;
63
64     /**
65      * Index of the next command to execute.
66      */

67     private int next = 0;
68
69     /**
70      * Returns <code>true</code> if there are more commands in the queue.
71      *
72      * @return <code>true</code> if there are more commands in the queue.
73      */

74     public boolean hasNext() {
75       return next < end;
76     }
77
78     /**
79      * Returns the next command from the queue. When the end of the dispatch
80      * region is reached it will wrap back to the start.
81      *
82      * @return next command from the queue.
83      */

84     public Object JavaDoc next() {
85       last = next;
86       Object JavaDoc command = commands.get(next++);
87       if (next >= end) {
88         next = 0;
89       }
90
91       return command;
92     }
93
94     /**
95      * Removes the command which was previously returned by {@link #next()}.
96      *
97      * @return the command which was previously returned by {@link #next()}.
98      */

99     public void remove() {
100       assert (last >= 0);
101
102       commands.remove(last);
103       --end;
104
105       if (last <= next) {
106         if (--next < 0) {
107           next = 0;
108         }
109       }
110
111       last = -1;
112     }
113
114     /**
115      * Returns the last element returned by {@link #next()}.
116      *
117      * @return last element returned by {@link #next()}
118      */

119     private Object JavaDoc getLast() {
120       assert (last >= 0);
121       return commands.get(last);
122     }
123
124     private void setEnd(int end) {
125       assert (end >= next);
126
127       this.end = end;
128     }
129
130     private void setLast(int last) {
131       this.last = last;
132     }
133     
134     private boolean wasRemoved() {
135       return last == -1;
136     }
137   }
138
139   /**
140    * Default amount of time to wait before assuming that a script cancellation
141    * has taken place. This should be a platform dependent value, ultimately we
142    * may need to acquire this value based on a rebind decision. For now, we
143    * chose the smallest value known to cause an SSW.
144    */

145   private static final long DEFAULT_CANCELLATION_TIMEOUT_MILLIS = 10000;
146
147   /**
148    * Default amount of time to spend dispatching commands before we yield to the
149    * system.
150    */

151   private static final long DEFAULT_TIME_SLICE_MILLIS = 100;
152
153   /**
154    * Returns true the end time has been reached or exceeded.
155    *
156    * @param currentTimeMillis current time in milliseconds
157    * @param startTimeMillis end time in milliseconds
158    * @return true if the end time has been reached
159    */

160   private static boolean hasTimeSliceExpired(long currentTimeMillis,
161       long startTimeMillis) {
162     return Math.abs(currentTimeMillis - startTimeMillis) >= DEFAULT_TIME_SLICE_MILLIS;
163   }
164
165   /**
166    * Timer used to recover from script cancellations arising from slow script
167    * warnings.
168    */

169   private final Timer cancellationTimer = new Timer() {
170     public void run() {
171       if (!isExecuting()) {
172         /*
173          * If we are not executing, then the cancellation timer expired right
174          * about the time that the command dispatcher finished -- we are okay so
175          * we just exit.
176          */

177         return;
178       }
179
180       doCommandCanceled();
181     }
182   };
183
184   /**
185    * Commands that need to be executed.
186    */

187   private final List JavaDoc commands = new ArrayList JavaDoc();
188
189   /**
190    * Set to <code>true</code> when we are actively dispatching commands.
191    */

192   private boolean executing = false;
193
194   /**
195    * Timer used to drive the dispatching of commands in the background.
196    */

197   private final Timer executionTimer = new Timer() {
198     public void run() {
199       assert (!isExecuting());
200
201       setExecutionTimerPending(false);
202
203       doExecuteCommands(System.currentTimeMillis());
204     }
205   };
206
207   /**
208    * Set to <code>true</code> when we are waiting for a dispatch timer event
209    * to fire.
210    */

211   private boolean executionTimerPending = false;
212
213   /**
214    * The single circular iterator instance that we use to iterate over the
215    * collection of commands.
216    */

217   private final CircularIterator iterator = new CircularIterator();
218
219   /**
220    * Submits a {@link Command} for execution.
221    *
222    * @param command command to submit
223    */

224   public void submit(Command command) {
225     commands.add(command);
226
227     maybeStartExecutionTimer();
228   }
229
230   /**
231    * Submits an {@link IncrementalCommand} for execution.
232    *
233    * @param command command to submit
234    */

235   public void submit(IncrementalCommand command) {
236     commands.add(command);
237
238     maybeStartExecutionTimer();
239   }
240
241   /**
242    * Reports either a {@link CommandCanceledException} or an
243    * {@link IncrementalCommandCanceledException} back through the
244    * {@link UncaughtExceptionHandler} if one is set.
245    */

246   protected void doCommandCanceled() {
247     Object JavaDoc cmd = iterator.getLast();
248     iterator.remove();
249     assert (cmd != null);
250
251     RuntimeException JavaDoc ex = null;
252     if (cmd instanceof Command) {
253       ex = new CommandCanceledException((Command) cmd);
254     } else if (cmd instanceof IncrementalCommand) {
255       ex = new IncrementalCommandCanceledException((IncrementalCommand) cmd);
256     }
257
258     if (ex != null) {
259       UncaughtExceptionHandler ueh = GWT.getUncaughtExceptionHandler();
260       if (ueh != null) {
261         ueh.onUncaughtException(ex);
262       }
263     }
264
265     setExecuting(false);
266
267     maybeStartExecutionTimer();
268   }
269
270   /**
271    * This method will dispatch commands from the command queue. It will dispatch
272    * commands until one of the following conditions is <code>true</code>:
273    * <ul>
274    * <li>It consumed its dispatching time slice {@value #DEFAULT_TIME_SLICE_MILLIS}</li>
275    * <li>It encounters a <code>null</code> in the command queue</li>
276    * <li>All commands which were present at the start of the dispatching have
277    * been removed from the command queue</li>
278    * <li>The command that it was processing was canceled due to a false
279    * cancellation -- in this case we exit without updating any state</li>
280    * </ul>
281    *
282    * @param startTimeMillis the time when this method started
283    */

284   protected void doExecuteCommands(long startTimeMillis) {
285     assert (!isExecutionTimerPending());
286
287     boolean wasCanceled = false;
288     try {
289       setExecuting(true);
290
291       iterator.setEnd(commands.size());
292
293       cancellationTimer.schedule((int) DEFAULT_CANCELLATION_TIMEOUT_MILLIS);
294
295       while (iterator.hasNext()) {
296         Object JavaDoc element = iterator.next();
297
298         boolean removeCommand = true;
299         try {
300           if (element == null) {
301             // null forces a yield or pause in execution
302
return;
303           }
304
305           if (element instanceof Command) {
306             Command command = (Command) element;
307             command.execute();
308           } else if (element instanceof IncrementalCommand) {
309             IncrementalCommand incrementalCommand = (IncrementalCommand) element;
310             removeCommand = !incrementalCommand.execute();
311           }
312
313         } finally {
314           wasCanceled = iterator.wasRemoved();
315           if (wasCanceled) {
316             /*
317              * The iterator may have already had its remove method called, if
318              * it has, then we need to exit without updating any state
319              */

320             return;
321           }
322           
323           if (removeCommand) {
324             iterator.remove();
325           }
326         }
327
328         if (hasTimeSliceExpired(System.currentTimeMillis(), startTimeMillis)) {
329           // the time slice has expired
330
return;
331         }
332       }
333     } finally {
334       if (!wasCanceled) {
335         cancellationTimer.cancel();
336   
337         setExecuting(false);
338   
339         maybeStartExecutionTimer();
340       }
341     }
342   }
343
344   /**
345    * Starts the dispatch timer if there are commands to dispatch and we are not
346    * waiting for a dispatch timer and we are not actively dispatching.
347    */

348   protected void maybeStartExecutionTimer() {
349     if (!commands.isEmpty() && !isExecutionTimerPending() && !isExecuting()) {
350       setExecutionTimerPending(true);
351       executionTimer.schedule(1);
352     }
353   }
354
355   /**
356    * This method is for testing only.
357    */

358   List JavaDoc getPendingCommands() {
359     return commands;
360   }
361
362   /**
363    * This method is for testing only.
364    */

365   void setExecuting(boolean executing) {
366     this.executing = executing;
367   }
368
369   /**
370    * This method is for testing only.
371    */

372   void setLast(int last) {
373     iterator.setLast(last);
374   }
375
376   /**
377    * Returns <code>true</code> if this instance is currently dispatching
378    * commands.
379    *
380    * @return <code>true</code> if this instance is currently dispatching
381    * commands
382    */

383   private boolean isExecuting() {
384     return executing;
385   }
386
387   /**
388    * Returns <code>true</code> if a the dispatch timer was scheduled but it
389    * still has not fired.
390    *
391    * @return <code>true</code> if a the dispatch timer was scheduled but it
392    * still has not fired
393    */

394   private boolean isExecutionTimerPending() {
395     return executionTimerPending;
396   }
397
398   private void setExecutionTimerPending(boolean pending) {
399     executionTimerPending = pending;
400   }
401 }
Popular Tags