Source code: nextapp/echoservlet/OutgoingUpdateQueue.java
1 /*
2 * This file is part of the Echo Web Application Framework (hereinafter "Echo").
3 * Copyright (C) 2002-2004 NextApp, Inc.
4 *
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 *
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
11 *
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
15 * License.
16 *
17 * Alternatively, the contents of this file may be used under the terms of
18 * either the GNU General Public License Version 2 or later (the "GPL"), or
19 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
20 * in which case the provisions of the GPL or the LGPL are applicable instead
21 * of those above. If you wish to allow use of your version of this file only
22 * under the terms of either the GPL or the LGPL, and not to allow others to
23 * use your version of this file under the terms of the MPL, indicate your
24 * decision by deleting the provisions above and replace them with the notice
25 * and other provisions required by the GPL or the LGPL. If you do not delete
26 * the provisions above, a recipient may use your version of this file under
27 * the terms of any one of the MPL, the GPL or the LGPL.
28 */
29
30 package nextapp.echoservlet;
31
32 import java.io.Serializable;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.Set;
36
37 import nextapp.echo.Component;
38
39 /**
40 * Manages outgoing updates to the client.
41 *
42 * Managed updates include:
43 * <ul>
44 * <li>Panes requiring refresh</li>
45 * <li>Windows requiring refresh</li>
46 * <li>Windows requiring title update</li>
47 * <li>Windows requiring z-index adjustment (raising or lowering)</li>
48 * <li>Windows to be opened or closed</li>
49 * </ul>
50 */
51 final class OutgoingUpdateQueue
52 implements Serializable {
53
54 /**
55 * A set containing WindowUIs that need to be closed on the client.
56 */
57 private Set closedWindows = new HashSet();
58
59 /**
60 * A set containing WindowUIs that need to be raised on the client.
61 */
62 private Set raisedWindows = new HashSet();
63
64 /**
65 * A set containing WindowUIs that need to be lowered on the client.
66 */
67 private Set loweredWindows = new HashSet();
68
69 /**
70 * The InstancePeer with which this OutgoingUpdateQueue is associated.
71 */
72 private InstancePeer instancePeer;
73
74 /**
75 * A set containing WindowUI's that need to be opened on the client.
76 */
77 private Set openedWindows = new HashSet();
78
79 /**
80 * A set containing all services that need to be refreshed on the client.
81 */
82 private Set paneUpdates = new HashSet();
83
84 /**
85 * A set containing all WindowUI's whose titles need to be updated on the
86 * client.
87 */
88 private Set windowTitleUpdates = new HashSet();
89
90 /**
91 * A set containing all WindowUI's whose content needs to be updated on
92 * the client.
93 */
94 private Set windowUpdates = new HashSet();
95
96 /**
97 * Creates a new OutgoingUpdateQueue for the specified InstancePeer.
98 * There should be one OutgoingUpdateQueue per InstancePeer.
99 *
100 * @param instancePeer The InstancePeer that this OutgoingUpdateQueue will handle
101 * updates for.
102 */
103 OutgoingUpdateQueue(InstancePeer instancePeer) {
104 super();
105
106 this.instancePeer = instancePeer;
107 }
108
109 /**
110 * Removes all previously scheduled service updates for services that
111 * are descendants of the specified <code>ComponentPeer</code>. It is
112 * necessary to remove descendant services if the componentPeer has been
113 * removed or has become invisible.
114 *
115 * @param componentPeer The component peer whose descendant services
116 * are to be removed.
117 */
118 void deleteComponentUpdate(ComponentPeer componentPeer) {
119 Component component = componentPeer.getComponent();
120 Component testComponent;
121 ComponentPeer testComponentPeer;
122
123 for (Iterator it = paneUpdates.iterator(); it.hasNext();) {
124 testComponentPeer = (ComponentPeer) it.next();
125 testComponent = testComponentPeer.getComponent();
126 if (component.isAncestorOf(testComponent)) {
127 it.remove();
128 }
129 }
130 }
131
132 /**
133 * Returns all services (panes and windows) that are present on the client
134 * and need to be refreshed such that they are in sync with their
135 * respective server-side representations.
136 *
137 * @return An iterator of services needing to be refreshed.
138 */
139 Iterator dequeuePaneUpdate() {
140 // Handle forwarding container panes: Any forwarding container pane found will have its first parent component that is not
141 // a forwarding container pnae redrawn instead.
142 Set forwardedContainerUpdates = null;
143 Iterator it = paneUpdates.iterator();
144 while (it.hasNext()) {
145 PaneUI paneUI = (PaneUI) it.next();
146 if (paneUI instanceof ContainerPaneUI &&
147 !(((ContainerPaneUI) paneUI).getForwardingPane() instanceof ContainerPaneUI)) {
148 // Pane update is a forwarding container pane, that is forwarding to a non-container pane:
149 // Update its parent instead. This is done as otherwise the scrollbar policy desired by the pane being forwarded
150 // to will not be observed.
151 ComponentPeer componentPeer = (ComponentPeer) paneUI;
152
153 // Find first parent component that is not a forwarding container pane.
154 while (componentPeer instanceof ContainerPaneUI
155 && ((ContainerPaneUI) componentPeer).getForwardingPane() != componentPeer) {
156 componentPeer = componentPeer.getParent();
157 }
158
159 // Add peer to update list.
160 if (forwardedContainerUpdates == null) {
161 // lazy-create forwarded container update set
162 forwardedContainerUpdates = new HashSet();
163 }
164 forwardedContainerUpdates.add(componentPeer);
165 }
166 }
167 if (forwardedContainerUpdates != null) {
168 it = forwardedContainerUpdates.iterator();
169 while (it.hasNext()) {
170 enqueueComponentUpdate((ComponentPeer) it.next());
171 }
172 }
173
174 return paneUpdates.iterator();
175 }
176
177 /**
178 * Returns an iterator over all windows that are presently open on the
179 * client and need to be closed. The controller will use the
180 * Iterator.remove() method to remove each window from this queue after
181 * it has generated script to close the window.
182 *
183 * @return An iterator of windows needing to be closed.
184 */
185 Iterator dequeueWindowClose() {
186 return closedWindows.iterator();
187 }
188
189 /**
190 * Returns an iterator over all windows that need to be lowered
191 * on the client. The controller will use the
192 * Iterator.remove() method to remove each window from this queue after
193 * it has generated script to lower the window.
194 *
195 * @return An iterator of windows needing to be lowered.
196 */
197 Iterator dequeueWindowLower() {
198 return loweredWindows.iterator();
199 }
200
201 /**
202 * Returns an iterator over all windows that are presently closed on the
203 * client and need to be opened. The controller will use the
204 * Iterator.remove() method to remove each window from this queue after
205 * it has generated script to open the window.
206 *
207 * @return An iterator of windows needing to be opened.
208 */
209 Iterator dequeueWindowOpen() {
210 return openedWindows.iterator();
211 }
212
213 /**
214 * Returns an iterator over all windows that need to be raised
215 * on the client. The controller will use the
216 * Iterator.remove() method to remove each window from this queue after
217 * it has generated script to raise the window.
218 *
219 * @return An iterator of windows needing to be raised.
220 */
221 Iterator dequeueWindowRaise() {
222 return raisedWindows.iterator();
223 }
224
225 /**
226 * Returns an iterator over all open windows that need to have their titles
227 * updated on the client.
228 *
229 * @return An iterator of windows needing their titles updated.
230 */
231 Iterator dequeueWindowTitleUpdate() {
232 return windowTitleUpdates.iterator();
233 }
234
235 /**
236 * Returns an iterator over all open windows that need to have their
237 * content updated on the client.
238 *
239 * @return An iterator of windows needing their content updated.
240 */
241 Iterator dequeueWindowUpdate() {
242 return windowUpdates.iterator();
243 }
244
245 /**
246 * Causes the service containing the given component to be re-rendered.
247 *
248 * @param componentPeer A <code>ComponentPeer</code> whose represented
249 * <code>Component</code> has changed and therefore needs to be
250 * updated on the client browser.
251 */
252 void enqueueComponentUpdate(ComponentPeer componentPeer) {
253 if (componentPeer instanceof WindowUI) {
254 enqueueWindowUpdate((WindowUI) componentPeer);
255 } else {
256 enqueuePaneUpdate(componentPeer);
257 }
258 }
259
260 /**
261 * Adds the <code>Service</code> that is responsible for rendering the
262 * specified <code>ComponentPeer</code> to the list of services needing
263 * to be refreshed.
264 *
265 * @param componentPeer The component that needs to be re-rendered.
266 */
267 private void enqueuePaneUpdate(ComponentPeer componentPeer) {
268 if (componentPeer instanceof WindowUI) {
269 throw new IllegalArgumentException("Cannot redraw window.");
270 }
271
272 ComponentPeer parent = componentPeer.getParent();
273 if (parent instanceof WindowUI) {
274 // If the componentPeer is the root pane of the window, then forward the redraw request to
275 // redrawWindow().
276 enqueueWindowUpdate((WindowUI) parent);
277 } else {
278 PaneUI containingPane = instancePeer.getContainingPane(componentPeer);
279
280 // Quick check to ensure that the service containing this component is not already queued for update.
281 // This check is done first because often many components under the same service will be updated at once.
282 if (paneUpdates.contains(containingPane)) {
283 return;
284 }
285
286 Iterator it;
287 Component testComponent;
288 Component component = componentPeer.getComponent();
289
290 // Determine if any services that are ancestors of the component are already scheduled for updating.
291 // At the same time, remove any descendant services of the component from being scheduled for updates.
292 it = paneUpdates.iterator();
293 while (it.hasNext()) {
294 testComponent = ((ComponentPeer) it.next()).getComponent();
295
296 // Check for ancestors of the test component.
297 if (testComponent.isAncestorOf(component)) {
298 return;
299 }
300
301 if (component.isAncestorOf(testComponent)) {
302 // A child of the component was found in the queue, therefore, the component should be added. There is
303 // no possiblility that the components parent could be in the queue. Therefore, the iterator is now
304 // used only to look for more child components.
305 it.remove();
306 while (it.hasNext()) {
307 testComponent = ((ComponentPeer) it.next()).getComponent();
308 if (component.isAncestorOf(testComponent)) {
309 it.remove();
310 }
311 }
312 paneUpdates.add(containingPane);
313 return;
314 }
315 }
316
317 if (openedWindows.size() > 0) {
318 // Determine if the component is a descendant of a window that is scheduled to be opened.
319 // If this is the case, the component's containing service will not be scheduled for update.
320 it = openedWindows.iterator();
321 while (it.hasNext()) {
322 testComponent = ((ComponentPeer) it.next()).getComponent();
323 if (testComponent.isAncestorOf(component)) {
324 // The window that contains the component is scheduled to be opened. There is no reason
325 // to schedule the component's containing service for an update.
326 return;
327 }
328 }
329 }
330
331 if (windowUpdates.size() > 0) {
332 // Determine if the component is a descendant of a window that is scheduled to be refreshed.
333 // If this is the case, the component's containing service will not be scheduled for update.
334 it = windowUpdates.iterator();
335 while (it.hasNext()) {
336 testComponent = ((ComponentPeer) it.next()).getComponent();
337 if (testComponent.isAncestorOf(component)) {
338 // The window that contains the component is scheduled to be opened. There is no reason
339 // to schedule the component's containing service for an update.
340 return;
341 }
342 }
343 }
344
345 if (closedWindows.size() > 0) {
346 // Determine if the component is a descendant of a window that is scheduled to be closed.
347 // If this is the case, the component's containg service will not be scheduled for update.
348 it = closedWindows.iterator();
349 while (it.hasNext()) {
350 testComponent = ((ComponentPeer) it.next()).getComponent();
351 if (testComponent.isAncestorOf(component)) {
352 // The window that contains the component is scheduled to be closed. There is no reason
353 // to schedule the component's containing service for an update.
354 return;
355 }
356 }
357 }
358
359 // The method has not prematurely returned, therefore no services that contain the component were found.
360 paneUpdates.add(containingPane);
361 }
362 }
363
364 /**
365 * Removes a window from the application. The window will be closed
366 * on the client browser if necessary (the only case where this is not
367 * necessary is if the window is not yet open but was scheduled to be
368 * opened by a call to <code>addWindow()</code>).
369 *
370 * @param windowUI The window to be removed.
371 */
372 void enqueueWindowClose(WindowUI windowUI) {
373 if (openedWindows.contains(windowUI)) {
374 openedWindows.remove(windowUI);
375 } else {
376 closedWindows.add(windowUI);
377 windowUpdates.remove(windowUI);
378 windowTitleUpdates.remove(windowUI);
379 deleteComponentUpdate(windowUI);
380 loweredWindows.remove(windowUI);
381 raisedWindows.remove(windowUI);
382 }
383 }
384
385 /**
386 * Schedules a window to be lowered to the background of the client
387 * application.
388 *
389 * @param windowUI The window to lower.
390 */
391 void enqueueWindowLower(WindowUI windowUI) {
392 if (!openedWindows.contains(windowUI)) {
393 if (loweredWindows.contains(windowUI)) {
394 loweredWindows.remove(windowUI);
395 }
396 if (raisedWindows.contains(windowUI)) {
397 raisedWindows.remove(windowUI);
398 }
399 loweredWindows.add(windowUI);
400 }
401 }
402
403 /**
404 * Adds a window to the application. The window will be opened on the
405 * client if necessary (the only case where this is not necessary is if
406 * the window is already opened and was scheduled to be closed by a call
407 * to <code>removeWindow()</code>).
408 *
409 * @param windowUI The window to be added.
410 */
411 void enqueueWindowOpen(WindowUI windowUI) {
412 if (closedWindows.contains(windowUI)) {
413 // The window was closed, and is being opened again, in the same step. Therefore, the window on the user's screen,
414 // The window is thus removed from the closedWindows collections.
415 closedWindows.remove(windowUI);
416
417 // The window may also need to be refreshed, and because its state was not being monitored between the time it was
418 // closed and reopened, there is no way to tell if an update is necessary. Thus the window's content is marked to be
419 // updated.
420 windowUpdates.add(windowUI);
421 windowTitleUpdates.add(windowUI);
422 } else {
423 // The window needs to be opened on the client.
424 openedWindows.add(windowUI);
425
426 // Remove any service updates that are scheduled underneath the window.
427 deleteComponentUpdate(windowUI);
428
429 // Remove the window from being updated as a service.
430 windowUpdates.remove(windowUI);
431
432 // Remove any window title updates.
433 windowTitleUpdates.remove(windowUI);
434
435 // Remove any window raising/lowering tasks
436 loweredWindows.remove(windowUI);
437 raisedWindows.remove(windowUI);
438 }
439 }
440
441 /**
442 * Adds the specified <code>WindowUI</code> to the list of windows that
443 * need to have be raised on the client.
444 *
445 * @param windowUI the peer of a <code>Window</code> which should be
446 * raised on the client.
447 */
448 void enqueueWindowRaise(WindowUI windowUI) {
449 if (!openedWindows.contains(windowUI)) {
450 if (loweredWindows.contains(windowUI)) {
451 loweredWindows.remove(windowUI);
452 }
453 if (raisedWindows.contains(windowUI)) {
454 raisedWindows.remove(windowUI);
455 }
456 raisedWindows.add(windowUI);
457 }
458 }
459
460 /**
461 * Adds the specified <code>WindowUI</code> to the list of windows that
462 * need to have their titles updated.
463 *
464 * @param windowUI The peer of a <code>Window</code> whose title has been
465 * changed.
466 */
467 void enqueueWindowTitleUpdate(WindowUI windowUI) {
468 if (!openedWindows.contains(windowUI) && !closedWindows.contains(windowUI)) {
469 windowTitleUpdates.add(windowUI);
470 }
471 }
472
473 /**
474 * Adds the specified <code>WindowUI</code> to the list of windows that
475 * need to have their content updated.
476 *
477 * @param windowUI The peer of a <code>Window</code> whose content has been
478 * changed.
479 */
480 private void enqueueWindowUpdate(WindowUI windowUI) {
481 // If the window is scheduled to be opened (and is therefore not yet open), return immediately.
482 if (openedWindows.contains(windowUI)) {
483 return;
484 }
485
486 windowUpdates.add(windowUI);
487 }
488
489 /**
490 * Returns true if there are services that need to be refreshed on the
491 * client.
492 *
493 * @return True if there are services that need to be refreshed on the
494 * client.
495 */
496 boolean isPaneUpdateRequired() {
497 return paneUpdates.size() > 0;
498 }
499
500 /**
501 * Returns true if there are windows on the client that need to be closed.
502 *
503 * @return True if there are windows on the client that need to be closed.
504 */
505 boolean isWindowCloseRequired() {
506 return closedWindows.size() > 0;
507 }
508
509 /**
510 * Returns true if there are windows that need to be lowered to the
511 * background of the screen.
512 *
513 * @return True if there are windows that need to be lowered to the
514 * background of the screen.
515 */
516 boolean isWindowLowerRequired() {
517 return loweredWindows.size() > 0;
518 }
519
520 /**
521 * Returns true if there are windows that need to be opened on the client.
522 *
523 * @return True if there are windows that need to be opened on the client.
524 */
525 boolean isWindowOpenRequired() {
526 return openedWindows.size() > 0;
527 }
528
529 /**
530 * Returns true if there are windows that need to be raised to the
531 * foreground of the screen.
532 *
533 * @return True if there are windows that need to be raised to the
534 * foreground of the screen.
535 */
536 boolean isWindowRaiseRequired() {
537 return raisedWindows.size() > 0;
538 }
539
540 /**
541 * Returns true if there are windows that need their titles updated on the
542 * client.
543 *
544 * @return True if there are windows that need their titles updated on the
545 * client.
546 */
547 boolean isWindowTitleUpdateRequired() {
548 return windowTitleUpdates.size() > 0;
549 }
550
551 /**
552 * Returns true if there are windows that need their content updated on
553 * the client.
554 *
555 * @return True if there are windows that need their content updated on
556 * the client.
557 */
558 boolean isWindowUpdateRequired() {
559 return windowUpdates.size() > 0;
560 }
561
562 /**
563 * Purges all updates.
564 */
565 void purgeAllUpdates() {
566 windowUpdates.clear();
567 windowTitleUpdates.clear();
568 paneUpdates.clear();
569 openedWindows.clear();
570 closedWindows.clear();
571 raisedWindows.clear();
572 loweredWindows.clear();
573 }
574 }