1 /* 2 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.lang.ref.WeakReference; 28 import java.security.AccessController; 29 import java.security.PrivilegedAction; 30 import java.beans.PropertyChangeListener; 31 import java.beans.PropertyChangeSupport; 32 import java.beans.PropertyChangeEvent; 33 import java.util.List; 34 import java.util.concurrent.*; 35 import java.util.concurrent.locks.*; 36 37 import java.awt.event.*; 38 39 import javax.swing.SwingUtilities; 40 41 import sun.awt.AppContext; 42 import sun.swing.AccumulativeRunnable; 43 44 /** 45 * An abstract class to perform lengthy GUI-interaction tasks in a 46 * background thread. Several background threads can be used to execute such 47 * tasks. However, the exact strategy of choosing a thread for any particular 48 * {@code SwingWorker} is unspecified and should not be relied on. 49 * <p> 50 * When writing a multi-threaded application using Swing, there are 51 * two constraints to keep in mind: 52 * (refer to 53 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html"> 54 * How to Use Threads 55 * </a> for more details): 56 * <ul> 57 * <li> Time-consuming tasks should not be run on the <i>Event 58 * Dispatch Thread</i>. Otherwise the application becomes unresponsive. 59 * </li> 60 * <li> Swing components should be accessed on the <i>Event 61 * Dispatch Thread</i> only. 62 * </li> 63 * </ul> 64 * 65 * <p> 66 * 67 * <p> 68 * These constraints mean that a GUI application with time intensive 69 * computing needs at least two threads: 1) a thread to perform the lengthy 70 * task and 2) the <i>Event Dispatch Thread</i> (EDT) for all GUI-related 71 * activities. This involves inter-thread communication which can be 72 * tricky to implement. 73 * 74 * <p> 75 * {@code SwingWorker} is designed for situations where you need to have a long 76 * running task run in a background thread and provide updates to the UI 77 * either when done, or while processing. 78 * Subclasses of {@code SwingWorker} must implement 79 * the {@link #doInBackground} method to perform the background computation. 80 * 81 * 82 * <p> 83 * <b>Workflow</b> 84 * <p> 85 * There are three threads involved in the life cycle of a 86 * {@code SwingWorker} : 87 * <ul> 88 * <li> 89 * <p> 90 * <i>Current</i> thread: The {@link #execute} method is 91 * called on this thread. It schedules {@code SwingWorker} for the execution on a 92 * <i>worker</i> 93 * thread and returns immediately. One can wait for the {@code SwingWorker} to 94 * complete using the {@link #get get} methods. 95 * <li> 96 * <p> 97 * <i>Worker</i> thread: The {@link #doInBackground} 98 * method is called on this thread. 99 * This is where all background activities should happen. To notify 100 * {@code PropertyChangeListeners} about bound properties changes use the 101 * {@link #firePropertyChange firePropertyChange} and 102 * {@link #getPropertyChangeSupport} methods. By default there are two bound 103 * properties available: {@code state} and {@code progress}. 104 * <li> 105 * <p> 106 * <i>Event Dispatch Thread</i>: All Swing related activities occur 107 * on this thread. {@code SwingWorker} invokes the 108 * {@link #process process} and {@link #done} methods and notifies 109 * any {@code PropertyChangeListeners} on this thread. 110 * </ul> 111 * 112 * <p> 113 * Often, the <i>Current</i> thread is the <i>Event Dispatch 114 * Thread</i>. 115 * 116 * 117 * <p> 118 * Before the {@code doInBackground} method is invoked on a <i>worker</i> thread, 119 * {@code SwingWorker} notifies any {@code PropertyChangeListeners} about the 120 * {@code state} property change to {@code StateValue.STARTED}. After the 121 * {@code doInBackground} method is finished the {@code done} method is 122 * executed. Then {@code SwingWorker} notifies any {@code PropertyChangeListeners} 123 * about the {@code state} property change to {@code StateValue.DONE}. 124 * 125 * <p> 126 * {@code SwingWorker} is only designed to be executed once. Executing a 127 * {@code SwingWorker} more than once will not result in invoking the 128 * {@code doInBackground} method twice. 129 * 130 * <p> 131 * <b>Sample Usage</b> 132 * <p> 133 * The following example illustrates the simplest use case. Some 134 * processing is done in the background and when done you update a Swing 135 * component. 136 * 137 * <p> 138 * Say we want to find the "Meaning of Life" and display the result in 139 * a {@code JLabel}. 140 * 141 * <pre> 142 * final JLabel label; 143 * class MeaningOfLifeFinder extends SwingWorker<String, Object> { 144 * {@code @Override} 145 * public String doInBackground() { 146 * return findTheMeaningOfLife(); 147 * } 148 * 149 * {@code @Override} 150 * protected void done() { 151 * try { 152 * label.setText(get()); 153 * } catch (Exception ignore) { 154 * } 155 * } 156 * } 157 * 158 * (new MeaningOfLifeFinder()).execute(); 159 * </pre> 160 * 161 * <p> 162 * The next example is useful in situations where you wish to process data 163 * as it is ready on the <i>Event Dispatch Thread</i>. 164 * 165 * <p> 166 * Now we want to find the first N prime numbers and display the results in a 167 * {@code JTextArea}. While this is computing, we want to update our 168 * progress in a {@code JProgressBar}. Finally, we also want to print 169 * the prime numbers to {@code System.out}. 170 * <pre> 171 * class PrimeNumbersTask extends 172 * SwingWorker<List<Integer>, Integer> { 173 * PrimeNumbersTask(JTextArea textArea, int numbersToFind) { 174 * //initialize 175 * } 176 * 177 * {@code @Override} 178 * public List<Integer> doInBackground() { 179 * while (! enough && ! isCancelled()) { 180 * number = nextPrimeNumber(); 181 * publish(number); 182 * setProgress(100 * numbers.size() / numbersToFind); 183 * } 184 * } 185 * return numbers; 186 * } 187 * 188 * {@code @Override} 189 * protected void process(List<Integer> chunks) { 190 * for (int number : chunks) { 191 * textArea.append(number + "\n"); 192 * } 193 * } 194 * } 195 * 196 * JTextArea textArea = new JTextArea(); 197 * final JProgressBar progressBar = new JProgressBar(0, 100); 198 * PrimeNumbersTask task = new PrimeNumbersTask(textArea, N); 199 * task.addPropertyChangeListener( 200 * new PropertyChangeListener() { 201 * public void propertyChange(PropertyChangeEvent evt) { 202 * if ("progress".equals(evt.getPropertyName())) { 203 * progressBar.setValue((Integer)evt.getNewValue()); 204 * } 205 * } 206 * }); 207 * 208 * task.execute(); 209 * System.out.println(task.get()); //prints all prime numbers we have got 210 * </pre> 211 * 212 * <p> 213 * Because {@code SwingWorker} implements {@code Runnable}, a 214 * {@code SwingWorker} can be submitted to an 215 * {@link java.util.concurrent.Executor} for execution. 216 * 217 * @author Igor Kushnirskiy 218 * 219 * @param <T> the result type returned by this {@code SwingWorker's} 220 * {@code doInBackground} and {@code get} methods 221 * @param <V> the type used for carrying out intermediate results by this 222 * {@code SwingWorker's} {@code publish} and {@code process} methods 223 * 224 * @since 1.6 225 */ 226 public abstract class SwingWorker<T, V> implements RunnableFuture<T> { 227 /** 228 * number of worker threads. 229 */ 230 private static final int MAX_WORKER_THREADS = 10; 231 232 /** 233 * current progress. 234 */ 235 private volatile int progress; 236 237 /** 238 * current state. 239 */ 240 private volatile StateValue state; 241 242 /** 243 * everything is run inside this FutureTask. Also it is used as 244 * a delegatee for the Future API. 245 */ 246 private final FutureTask<T> future; 247 248 /** 249 * all propertyChangeSupport goes through this. 250 */ 251 private final PropertyChangeSupport propertyChangeSupport; 252 253 /** 254 * handler for {@code process} mehtod. 255 */ 256 private AccumulativeRunnable<V> doProcess; 257 258 /** 259 * handler for progress property change notifications. 260 */ 261 private AccumulativeRunnable<Integer> doNotifyProgressChange; 262 263 private final AccumulativeRunnable<Runnable> doSubmit = getDoSubmit(); 264 265 /** 266 * Values for the {@code state} bound property. 267 * @since 1.6 268 */ 269 public enum StateValue { 270 /** 271 * Initial {@code SwingWorker} state. 272 */ 273 PENDING, 274 /** 275 * {@code SwingWorker} is {@code STARTED} 276 * before invoking {@code doInBackground}. 277 */ 278 STARTED, 279 280 /** 281 * {@code SwingWorker} is {@code DONE} 282 * after {@code doInBackground} method 283 * is finished. 284 */ 285 DONE 286 } 287 288 /** 289 * Constructs this {@code SwingWorker}. 290 */ 291 public SwingWorker() { 292 Callable<T> callable = 293 new Callable<T>() { 294 public T call() throws Exception { 295 setState(StateValue.STARTED); 296 return doInBackground(); 297 } 298 }; 299 300 future = new FutureTask<T>(callable) { 301 @Override 302 protected void done() { 303 doneEDT(); 304 setState(StateValue.DONE); 305 } 306 }; 307 308 state = StateValue.PENDING; 309 propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this); 310 doProcess = null; 311 doNotifyProgressChange = null; 312 } 313 314 /** 315 * Computes a result, or throws an exception if unable to do so. 316 * 317 * <p> 318 * Note that this method is executed only once. 319 * 320 * <p> 321 * Note: this method is executed in a background thread. 322 * 323 * 324 * @return the computed result 325 * @throws Exception if unable to compute a result 326 * 327 */ 328 protected abstract T doInBackground() throws Exception ; 329 330 /** 331 * Sets this {@code Future} to the result of computation unless 332 * it has been cancelled. 333 */ 334 public final void run() { 335 future.run(); 336 } 337 338 /** 339 * Sends data chunks to the {@link #process} method. This method is to be 340 * used from inside the {@code doInBackground} method to deliver 341 * intermediate results 342 * for processing on the <i>Event Dispatch Thread</i> inside the 343 * {@code process} method. 344 * 345 * <p> 346 * Because the {@code process} method is invoked asynchronously on 347 * the <i>Event Dispatch Thread</i> 348 * multiple invocations to the {@code publish} method 349 * might occur before the {@code process} method is executed. For 350 * performance purposes all these invocations are coalesced into one 351 * invocation with concatenated arguments. 352 * 353 * <p> 354 * For example: 355 * 356 * <pre> 357 * publish("1"); 358 * publish("2", "3"); 359 * publish("4", "5", "6"); 360 * </pre> 361 * 362 * might result in: 363 * 364 * <pre> 365 * process("1", "2", "3", "4", "5", "6") 366 * </pre> 367 * 368 * <p> 369 * <b>Sample Usage</b>. This code snippet loads some tabular data and 370 * updates {@code DefaultTableModel} with it. Note that it safe to mutate 371 * the tableModel from inside the {@code process} method because it is 372 * invoked on the <i>Event Dispatch Thread</i>. 373 * 374 * <pre> 375 * class TableSwingWorker extends 376 * SwingWorker<DefaultTableModel, Object[]> { 377 * private final DefaultTableModel tableModel; 378 * 379 * public TableSwingWorker(DefaultTableModel tableModel) { 380 * this.tableModel = tableModel; 381 * } 382 * 383 * {@code @Override} 384 * protected DefaultTableModel doInBackground() throws Exception { 385 * for (Object[] row = loadData(); 386 * ! isCancelled() && row != null; 387 * row = loadData()) { 388 * publish((Object[]) row); 389 * } 390 * return tableModel; 391 * } 392 * 393 * {@code @Override} 394 * protected void process(List<Object[]> chunks) { 395 * for (Object[] row : chunks) { 396 * tableModel.addRow(row); 397 * } 398 * } 399 * } 400 * </pre> 401 * 402 * @param chunks intermediate results to process 403 * 404 * @see #process 405 * 406 */ 407 protected final void publish(V... chunks) { 408 synchronized (this) { 409 if (doProcess == null) { 410 doProcess = new AccumulativeRunnable<V>() { 411 @Override 412 public void run(List<V> args) { 413 process(args); 414 } 415 @Override 416 protected void submit() { 417 doSubmit.add(this); 418 } 419 }; 420 } 421 } 422 doProcess.add(chunks); 423 } 424 425 /** 426 * Receives data chunks from the {@code publish} method asynchronously on the 427 * <i>Event Dispatch Thread</i>. 428 * 429 * <p> 430 * Please refer to the {@link #publish} method for more details. 431 * 432 * @param chunks intermediate results to process 433 * 434 * @see #publish 435 * 436 */ 437 protected void process(List<V> chunks) { 438 } 439 440 /** 441 * Executed on the <i>Event Dispatch Thread</i> after the {@code doInBackground} 442 * method is finished. The default 443 * implementation does nothing. Subclasses may override this method to 444 * perform completion actions on the <i>Event Dispatch Thread</i>. Note 445 * that you can query status inside the implementation of this method to 446 * determine the result of this task or whether this task has been cancelled. 447 * 448 * @see #doInBackground 449 * @see #isCancelled() 450 * @see #get 451 */ 452 protected void done() { 453 } 454 455 /** 456 * Sets the {@code progress} bound property. 457 * The value should be from 0 to 100. 458 * 459 * <p> 460 * Because {@code PropertyChangeListener}s are notified asynchronously on 461 * the <i>Event Dispatch Thread</i> multiple invocations to the 462 * {@code setProgress} method might occur before any 463 * {@code PropertyChangeListeners} are invoked. For performance purposes 464 * all these invocations are coalesced into one invocation with the last 465 * invocation argument only. 466 * 467 * <p> 468 * For example, the following invokations: 469 * 470 * <pre> 471 * setProgress(1); 472 * setProgress(2); 473 * setProgress(3); 474 * </pre> 475 * 476 * might result in a single {@code PropertyChangeListener} notification with 477 * the value {@code 3}. 478 * 479 * @param progress the progress value to set 480 * @throws IllegalArgumentException is value not from 0 to 100 481 */ 482 protected final void setProgress(int progress) { 483 if (progress < 0 || progress > 100) { 484 throw new IllegalArgumentException("the value should be from 0 to 100"); 485 } 486 if (this.progress == progress) { 487 return; 488 } 489 int oldProgress = this.progress; 490 this.progress = progress; 491 if (! getPropertyChangeSupport().hasListeners("progress")) { 492 return; 493 } 494 synchronized (this) { 495 if (doNotifyProgressChange == null) { 496 doNotifyProgressChange = 497 new AccumulativeRunnable<Integer>() { 498 @Override 499 public void run(List<Integer> args) { 500 firePropertyChange("progress", 501 args.get(0), 502 args.get(args.size() - 1)); 503 } 504 @Override 505 protected void submit() { 506 doSubmit.add(this); 507 } 508 }; 509 } 510 } 511 doNotifyProgressChange.add(oldProgress, progress); 512 } 513 514 /** 515 * Returns the {@code progress} bound property. 516 * 517 * @return the progress bound property. 518 */ 519 public final int getProgress() { 520 return progress; 521 } 522 523 /** 524 * Schedules this {@code SwingWorker} for execution on a <i>worker</i> 525 * thread. There are a number of <i>worker</i> threads available. In the 526 * event all <i>worker</i> threads are busy handling other 527 * {@code SwingWorkers} this {@code SwingWorker} is placed in a waiting 528 * queue. 529 * 530 * <p> 531 * Note: 532 * {@code SwingWorker} is only designed to be executed once. Executing a 533 * {@code SwingWorker} more than once will not result in invoking the 534 * {@code doInBackground} method twice. 535 */ 536 public final void execute() { 537 getWorkersExecutorService().execute(this); 538 } 539 540 // Future methods START 541 /** 542 * {@inheritDoc} 543 */ 544 public final boolean cancel(boolean mayInterruptIfRunning) { 545 return future.cancel(mayInterruptIfRunning); 546 } 547 548 /** 549 * {@inheritDoc} 550 */ 551 public final boolean isCancelled() { 552 return future.isCancelled(); 553 } 554 555 /** 556 * {@inheritDoc} 557 */ 558 public final boolean isDone() { 559 return future.isDone(); 560 } 561 562 /** 563 * {@inheritDoc} 564 * <p> 565 * Note: calling {@code get} on the <i>Event Dispatch Thread</i> blocks 566 * <i>all</i> events, including repaints, from being processed until this 567 * {@code SwingWorker} is complete. 568 * 569 * <p> 570 * When you want the {@code SwingWorker} to block on the <i>Event 571 * Dispatch Thread</i> we recommend that you use a <i>modal dialog</i>. 572 * 573 * <p> 574 * For example: 575 * 576 * <pre> 577 * class SwingWorkerCompletionWaiter extends PropertyChangeListener { 578 * private JDialog dialog; 579 * 580 * public SwingWorkerCompletionWaiter(JDialog dialog) { 581 * this.dialog = dialog; 582 * } 583 * 584 * public void propertyChange(PropertyChangeEvent event) { 585 * if ("state".equals(event.getPropertyName()) 586 * && SwingWorker.StateValue.DONE == event.getNewValue()) { 587 * dialog.setVisible(false); 588 * dialog.dispose(); 589 * } 590 * } 591 * } 592 * JDialog dialog = new JDialog(owner, true); 593 * swingWorker.addPropertyChangeListener( 594 * new SwingWorkerCompletionWaiter(dialog)); 595 * swingWorker.execute(); 596 * //the dialog will be visible until the SwingWorker is done 597 * dialog.setVisible(true); 598 * </pre> 599 */ 600 public final T get() throws InterruptedException, ExecutionException { 601 return future.get(); 602 } 603 604 /** 605 * {@inheritDoc} 606 * <p> 607 * Please refer to {@link #get} for more details. 608 */ 609 public final T get(long timeout, TimeUnit unit) throws InterruptedException, 610 ExecutionException, TimeoutException { 611 return future.get(timeout, unit); 612 } 613 614 // Future methods END 615 616 // PropertyChangeSupports methods START 617 /** 618 * Adds a {@code PropertyChangeListener} to the listener list. The listener 619 * is registered for all properties. The same listener object may be added 620 * more than once, and will be called as many times as it is added. If 621 * {@code listener} is {@code null}, no exception is thrown and no action is taken. 622 * 623 * <p> 624 * Note: This is merely a convenience wrapper. All work is delegated to 625 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 626 * 627 * @param listener the {@code PropertyChangeListener} to be added 628 */ 629 public final void addPropertyChangeListener(PropertyChangeListener listener) { 630 getPropertyChangeSupport().addPropertyChangeListener(listener); 631 } 632 633 /** 634 * Removes a {@code PropertyChangeListener} from the listener list. This 635 * removes a {@code PropertyChangeListener} that was registered for all 636 * properties. If {@code listener} was added more than once to the same 637 * event source, it will be notified one less time after being removed. If 638 * {@code listener} is {@code null}, or was never added, no exception is 639 * thrown and no action is taken. 640 * 641 * <p> 642 * Note: This is merely a convenience wrapper. All work is delegated to 643 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 644 * 645 * @param listener the {@code PropertyChangeListener} to be removed 646 */ 647 public final void removePropertyChangeListener(PropertyChangeListener listener) { 648 getPropertyChangeSupport().removePropertyChangeListener(listener); 649 } 650 651 /** 652 * Reports a bound property update to any registered listeners. No event is 653 * fired if {@code old} and {@code new} are equal and non-null. 654 * 655 * <p> 656 * This {@code SwingWorker} will be the source for 657 * any generated events. 658 * 659 * <p> 660 * When called off the <i>Event Dispatch Thread</i> 661 * {@code PropertyChangeListeners} are notified asynchronously on 662 * the <i>Event Dispatch Thread</i>. 663 * <p> 664 * Note: This is merely a convenience wrapper. All work is delegated to 665 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 666 * 667 * 668 * @param propertyName the programmatic name of the property that was 669 * changed 670 * @param oldValue the old value of the property 671 * @param newValue the new value of the property 672 */ 673 public final void firePropertyChange(String propertyName, Object oldValue, 674 Object newValue) { 675 getPropertyChangeSupport().firePropertyChange(propertyName, 676 oldValue, newValue); 677 } 678 679 /** 680 * Returns the {@code PropertyChangeSupport} for this {@code SwingWorker}. 681 * This method is used when flexible access to bound properties support is 682 * needed. 683 * <p> 684 * This {@code SwingWorker} will be the source for 685 * any generated events. 686 * 687 * <p> 688 * Note: The returned {@code PropertyChangeSupport} notifies any 689 * {@code PropertyChangeListener}s asynchronously on the <i>Event Dispatch 690 * Thread</i> in the event that {@code firePropertyChange} or 691 * {@code fireIndexedPropertyChange} are called off the <i>Event Dispatch 692 * Thread</i>. 693 * 694 * @return {@code PropertyChangeSupport} for this {@code SwingWorker} 695 */ 696 public final PropertyChangeSupport getPropertyChangeSupport() { 697 return propertyChangeSupport; 698 } 699 700 // PropertyChangeSupports methods END 701 702 /** 703 * Returns the {@code SwingWorker} state bound property. 704 * 705 * @return the current state 706 */ 707 public final StateValue getState() { 708 /* 709 * DONE is a speacial case 710 * to keep getState and isDone is sync 711 */ 712 if (isDone()) { 713 return StateValue.DONE; 714 } else { 715 return state; 716 } 717 } 718 719 /** 720 * Sets this {@code SwingWorker} state bound property. 721 * @param state the state to set 722 */ 723 private void setState(StateValue state) { 724 StateValue old = this.state; 725 this.state = state; 726 firePropertyChange("state", old, state); 727 } 728 729 /** 730 * Invokes {@code done} on the EDT. 731 */ 732 private void doneEDT() { 733 Runnable doDone = 734 new Runnable() { 735 public void run() { 736 done(); 737 } 738 }; 739 if (SwingUtilities.isEventDispatchThread()) { 740 doDone.run(); 741 } else { 742 doSubmit.add(doDone); 743 } 744 } 745 746 747 /** 748 * returns workersExecutorService. 749 * 750 * returns the service stored in the appContext or creates it if 751 * necessary. 752 * 753 * @return ExecutorService for the {@code SwingWorkers} 754 */ 755 private static synchronized ExecutorService getWorkersExecutorService() { 756 final AppContext appContext = AppContext.getAppContext(); 757 ExecutorService executorService = 758 (ExecutorService) appContext.get(SwingWorker.class); 759 if (executorService == null) { 760 //this creates daemon threads. 761 ThreadFactory threadFactory = 762 new ThreadFactory() { 763 final ThreadFactory defaultFactory = 764 Executors.defaultThreadFactory(); 765 public Thread newThread(final Runnable r) { 766 Thread thread = 767 defaultFactory.newThread(r); 768 thread.setName("SwingWorker-" 769 + thread.getName()); 770 thread.setDaemon(true); 771 return thread; 772 } 773 }; 774 775 executorService = 776 new ThreadPoolExecutor(MAX_WORKER_THREADS, MAX_WORKER_THREADS, 777 10L, TimeUnit.MINUTES, 778 new LinkedBlockingQueue<Runnable>(), 779 threadFactory); 780 appContext.put(SwingWorker.class, executorService); 781 782 // Don't use ShutdownHook here as it's not enough. We should track 783 // AppContext disposal instead of JVM shutdown, see 6799345 for details 784 final ExecutorService es = executorService; 785 appContext.addPropertyChangeListener(AppContext.DISPOSED_PROPERTY_NAME, 786 new PropertyChangeListener() { 787 @Override 788 public void propertyChange(PropertyChangeEvent pce) { 789 boolean disposed = (Boolean)pce.getNewValue(); 790 if (disposed) { 791 final WeakReference<ExecutorService> executorServiceRef = 792 new WeakReference<ExecutorService>(es); 793 final ExecutorService executorService = 794 executorServiceRef.get(); 795 if (executorService != null) { 796 AccessController.doPrivileged( 797 new PrivilegedAction<Void>() { 798 public Void run() { 799 executorService.shutdown(); 800 return null; 801 } 802 } 803 ); 804 } 805 } 806 } 807 } 808 ); 809 } 810 return executorService; 811 } 812 813 private static final Object DO_SUBMIT_KEY = new StringBuilder("doSubmit"); 814 private static AccumulativeRunnable<Runnable> getDoSubmit() { 815 synchronized (DO_SUBMIT_KEY) { 816 final AppContext appContext = AppContext.getAppContext(); 817 Object doSubmit = appContext.get(DO_SUBMIT_KEY); 818 if (doSubmit == null) { 819 doSubmit = new DoSubmitAccumulativeRunnable(); 820 appContext.put(DO_SUBMIT_KEY, doSubmit); 821 } 822 return (AccumulativeRunnable<Runnable>) doSubmit; 823 } 824 } 825 private static class DoSubmitAccumulativeRunnable 826 extends AccumulativeRunnable<Runnable> implements ActionListener { 827 private final static int DELAY = 1000 / 30; 828 @Override 829 protected void run(List<Runnable> args) { 830 for (Runnable runnable : args) { 831 runnable.run(); 832 } 833 } 834 @Override 835 protected void submit() { 836 Timer timer = new Timer(DELAY, this); 837 timer.setRepeats(false); 838 timer.start(); 839 } 840 public void actionPerformed(ActionEvent event) { 841 run(); 842 } 843 } 844 845 private class SwingWorkerPropertyChangeSupport 846 extends PropertyChangeSupport { 847 SwingWorkerPropertyChangeSupport(Object source) { 848 super(source); 849 } 850 @Override 851 public void firePropertyChange(final PropertyChangeEvent evt) { 852 if (SwingUtilities.isEventDispatchThread()) { 853 super.firePropertyChange(evt); 854 } else { 855 doSubmit.add( 856 new Runnable() { 857 public void run() { 858 SwingWorkerPropertyChangeSupport.this 859 .firePropertyChange(evt); 860 } 861 }); 862 } 863 } 864 } 865 }