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 @SafeVarargs 408 protected final void publish(V... chunks) { 409 synchronized (this) { 410 if (doProcess == null) { 411 doProcess = new AccumulativeRunnable<V>() { 412 @Override 413 public void run(List<V> args) { 414 process(args); 415 } 416 @Override 417 protected void submit() { 418 doSubmit.add(this); 419 } 420 }; 421 } 422 } 423 doProcess.add(chunks); 424 } 425 426 /** 427 * Receives data chunks from the {@code publish} method asynchronously on the 428 * <i>Event Dispatch Thread</i>. 429 * 430 * <p> 431 * Please refer to the {@link #publish} method for more details. 432 * 433 * @param chunks intermediate results to process 434 * 435 * @see #publish 436 * 437 */ 438 protected void process(List<V> chunks) { 439 } 440 441 /** 442 * Executed on the <i>Event Dispatch Thread</i> after the {@code doInBackground} 443 * method is finished. The default 444 * implementation does nothing. Subclasses may override this method to 445 * perform completion actions on the <i>Event Dispatch Thread</i>. Note 446 * that you can query status inside the implementation of this method to 447 * determine the result of this task or whether this task has been cancelled. 448 * 449 * @see #doInBackground 450 * @see #isCancelled() 451 * @see #get 452 */ 453 protected void done() { 454 } 455 456 /** 457 * Sets the {@code progress} bound property. 458 * The value should be from 0 to 100. 459 * 460 * <p> 461 * Because {@code PropertyChangeListener}s are notified asynchronously on 462 * the <i>Event Dispatch Thread</i> multiple invocations to the 463 * {@code setProgress} method might occur before any 464 * {@code PropertyChangeListeners} are invoked. For performance purposes 465 * all these invocations are coalesced into one invocation with the last 466 * invocation argument only. 467 * 468 * <p> 469 * For example, the following invokations: 470 * 471 * <pre> 472 * setProgress(1); 473 * setProgress(2); 474 * setProgress(3); 475 * </pre> 476 * 477 * might result in a single {@code PropertyChangeListener} notification with 478 * the value {@code 3}. 479 * 480 * @param progress the progress value to set 481 * @throws IllegalArgumentException is value not from 0 to 100 482 */ 483 protected final void setProgress(int progress) { 484 if (progress < 0 || progress > 100) { 485 throw new IllegalArgumentException("the value should be from 0 to 100"); 486 } 487 if (this.progress == progress) { 488 return; 489 } 490 int oldProgress = this.progress; 491 this.progress = progress; 492 if (! getPropertyChangeSupport().hasListeners("progress")) { 493 return; 494 } 495 synchronized (this) { 496 if (doNotifyProgressChange == null) { 497 doNotifyProgressChange = 498 new AccumulativeRunnable<Integer>() { 499 @Override 500 public void run(List<Integer> args) { 501 firePropertyChange("progress", 502 args.get(0), 503 args.get(args.size() - 1)); 504 } 505 @Override 506 protected void submit() { 507 doSubmit.add(this); 508 } 509 }; 510 } 511 } 512 doNotifyProgressChange.add(oldProgress, progress); 513 } 514 515 /** 516 * Returns the {@code progress} bound property. 517 * 518 * @return the progress bound property. 519 */ 520 public final int getProgress() { 521 return progress; 522 } 523 524 /** 525 * Schedules this {@code SwingWorker} for execution on a <i>worker</i> 526 * thread. There are a number of <i>worker</i> threads available. In the 527 * event all <i>worker</i> threads are busy handling other 528 * {@code SwingWorkers} this {@code SwingWorker} is placed in a waiting 529 * queue. 530 * 531 * <p> 532 * Note: 533 * {@code SwingWorker} is only designed to be executed once. Executing a 534 * {@code SwingWorker} more than once will not result in invoking the 535 * {@code doInBackground} method twice. 536 */ 537 public final void execute() { 538 getWorkersExecutorService().execute(this); 539 } 540 541 // Future methods START 542 /** 543 * {@inheritDoc} 544 */ 545 public final boolean cancel(boolean mayInterruptIfRunning) { 546 return future.cancel(mayInterruptIfRunning); 547 } 548 549 /** 550 * {@inheritDoc} 551 */ 552 public final boolean isCancelled() { 553 return future.isCancelled(); 554 } 555 556 /** 557 * {@inheritDoc} 558 */ 559 public final boolean isDone() { 560 return future.isDone(); 561 } 562 563 /** 564 * {@inheritDoc} 565 * <p> 566 * Note: calling {@code get} on the <i>Event Dispatch Thread</i> blocks 567 * <i>all</i> events, including repaints, from being processed until this 568 * {@code SwingWorker} is complete. 569 * 570 * <p> 571 * When you want the {@code SwingWorker} to block on the <i>Event 572 * Dispatch Thread</i> we recommend that you use a <i>modal dialog</i>. 573 * 574 * <p> 575 * For example: 576 * 577 * <pre> 578 * class SwingWorkerCompletionWaiter extends PropertyChangeListener { 579 * private JDialog dialog; 580 * 581 * public SwingWorkerCompletionWaiter(JDialog dialog) { 582 * this.dialog = dialog; 583 * } 584 * 585 * public void propertyChange(PropertyChangeEvent event) { 586 * if ("state".equals(event.getPropertyName()) 587 * && SwingWorker.StateValue.DONE == event.getNewValue()) { 588 * dialog.setVisible(false); 589 * dialog.dispose(); 590 * } 591 * } 592 * } 593 * JDialog dialog = new JDialog(owner, true); 594 * swingWorker.addPropertyChangeListener( 595 * new SwingWorkerCompletionWaiter(dialog)); 596 * swingWorker.execute(); 597 * //the dialog will be visible until the SwingWorker is done 598 * dialog.setVisible(true); 599 * </pre> 600 */ 601 public final T get() throws InterruptedException, ExecutionException { 602 return future.get(); 603 } 604 605 /** 606 * {@inheritDoc} 607 * <p> 608 * Please refer to {@link #get} for more details. 609 */ 610 public final T get(long timeout, TimeUnit unit) throws InterruptedException, 611 ExecutionException, TimeoutException { 612 return future.get(timeout, unit); 613 } 614 615 // Future methods END 616 617 // PropertyChangeSupports methods START 618 /** 619 * Adds a {@code PropertyChangeListener} to the listener list. The listener 620 * is registered for all properties. The same listener object may be added 621 * more than once, and will be called as many times as it is added. If 622 * {@code listener} is {@code null}, no exception is thrown and no action is taken. 623 * 624 * <p> 625 * Note: This is merely a convenience wrapper. All work is delegated to 626 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 627 * 628 * @param listener the {@code PropertyChangeListener} to be added 629 */ 630 public final void addPropertyChangeListener(PropertyChangeListener listener) { 631 getPropertyChangeSupport().addPropertyChangeListener(listener); 632 } 633 634 /** 635 * Removes a {@code PropertyChangeListener} from the listener list. This 636 * removes a {@code PropertyChangeListener} that was registered for all 637 * properties. If {@code listener} was added more than once to the same 638 * event source, it will be notified one less time after being removed. If 639 * {@code listener} is {@code null}, or was never added, no exception is 640 * thrown and no action is taken. 641 * 642 * <p> 643 * Note: This is merely a convenience wrapper. All work is delegated to 644 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 645 * 646 * @param listener the {@code PropertyChangeListener} to be removed 647 */ 648 public final void removePropertyChangeListener(PropertyChangeListener listener) { 649 getPropertyChangeSupport().removePropertyChangeListener(listener); 650 } 651 652 /** 653 * Reports a bound property update to any registered listeners. No event is 654 * fired if {@code old} and {@code new} are equal and non-null. 655 * 656 * <p> 657 * This {@code SwingWorker} will be the source for 658 * any generated events. 659 * 660 * <p> 661 * When called off the <i>Event Dispatch Thread</i> 662 * {@code PropertyChangeListeners} are notified asynchronously on 663 * the <i>Event Dispatch Thread</i>. 664 * <p> 665 * Note: This is merely a convenience wrapper. All work is delegated to 666 * {@code PropertyChangeSupport} from {@link #getPropertyChangeSupport}. 667 * 668 * 669 * @param propertyName the programmatic name of the property that was 670 * changed 671 * @param oldValue the old value of the property 672 * @param newValue the new value of the property 673 */ 674 public final void firePropertyChange(String propertyName, Object oldValue, 675 Object newValue) { 676 getPropertyChangeSupport().firePropertyChange(propertyName, 677 oldValue, newValue); 678 } 679 680 /** 681 * Returns the {@code PropertyChangeSupport} for this {@code SwingWorker}. 682 * This method is used when flexible access to bound properties support is 683 * needed. 684 * <p> 685 * This {@code SwingWorker} will be the source for 686 * any generated events. 687 * 688 * <p> 689 * Note: The returned {@code PropertyChangeSupport} notifies any 690 * {@code PropertyChangeListener}s asynchronously on the <i>Event Dispatch 691 * Thread</i> in the event that {@code firePropertyChange} or 692 * {@code fireIndexedPropertyChange} are called off the <i>Event Dispatch 693 * Thread</i>. 694 * 695 * @return {@code PropertyChangeSupport} for this {@code SwingWorker} 696 */ 697 public final PropertyChangeSupport getPropertyChangeSupport() { 698 return propertyChangeSupport; 699 } 700 701 // PropertyChangeSupports methods END 702 703 /** 704 * Returns the {@code SwingWorker} state bound property. 705 * 706 * @return the current state 707 */ 708 public final StateValue getState() { 709 /* 710 * DONE is a speacial case 711 * to keep getState and isDone is sync 712 */ 713 if (isDone()) { 714 return StateValue.DONE; 715 } else { 716 return state; 717 } 718 } 719 720 /** 721 * Sets this {@code SwingWorker} state bound property. 722 * @param state the state to set 723 */ 724 private void setState(StateValue state) { 725 StateValue old = this.state; 726 this.state = state; 727 firePropertyChange("state", old, state); 728 } 729 730 /** 731 * Invokes {@code done} on the EDT. 732 */ 733 private void doneEDT() { 734 Runnable doDone = 735 new Runnable() { 736 public void run() { 737 done(); 738 } 739 }; 740 if (SwingUtilities.isEventDispatchThread()) { 741 doDone.run(); 742 } else { 743 doSubmit.add(doDone); 744 } 745 } 746 747 748 /** 749 * returns workersExecutorService. 750 * 751 * returns the service stored in the appContext or creates it if 752 * necessary. 753 * 754 * @return ExecutorService for the {@code SwingWorkers} 755 */ 756 private static synchronized ExecutorService getWorkersExecutorService() { 757 final AppContext appContext = AppContext.getAppContext(); 758 ExecutorService executorService = 759 (ExecutorService) appContext.get(SwingWorker.class); 760 if (executorService == null) { 761 //this creates daemon threads. 762 ThreadFactory threadFactory = 763 new ThreadFactory() { 764 final ThreadFactory defaultFactory = 765 Executors.defaultThreadFactory(); 766 public Thread newThread(final Runnable r) { 767 Thread thread = 768 defaultFactory.newThread(r); 769 thread.setName("SwingWorker-" 770 + thread.getName()); 771 thread.setDaemon(true); 772 return thread; 773 } 774 }; 775 776 executorService = 777 new ThreadPoolExecutor(MAX_WORKER_THREADS, MAX_WORKER_THREADS, 778 10L, TimeUnit.MINUTES, 779 new LinkedBlockingQueue<Runnable>(), 780 threadFactory); 781 appContext.put(SwingWorker.class, executorService); 782 783 // Don't use ShutdownHook here as it's not enough. We should track 784 // AppContext disposal instead of JVM shutdown, see 6799345 for details 785 final ExecutorService es = executorService; 786 appContext.addPropertyChangeListener(AppContext.DISPOSED_PROPERTY_NAME, 787 new PropertyChangeListener() { 788 @Override 789 public void propertyChange(PropertyChangeEvent pce) { 790 boolean disposed = (Boolean)pce.getNewValue(); 791 if (disposed) { 792 final WeakReference<ExecutorService> executorServiceRef = 793 new WeakReference<ExecutorService>(es); 794 final ExecutorService executorService = 795 executorServiceRef.get(); 796 if (executorService != null) { 797 AccessController.doPrivileged( 798 new PrivilegedAction<Void>() { 799 public Void run() { 800 executorService.shutdown(); 801 return null; 802 } 803 } 804 ); 805 } 806 } 807 } 808 } 809 ); 810 } 811 return executorService; 812 } 813 814 private static final Object DO_SUBMIT_KEY = new StringBuilder("doSubmit"); 815 private static AccumulativeRunnable<Runnable> getDoSubmit() { 816 synchronized (DO_SUBMIT_KEY) { 817 final AppContext appContext = AppContext.getAppContext(); 818 Object doSubmit = appContext.get(DO_SUBMIT_KEY); 819 if (doSubmit == null) { 820 doSubmit = new DoSubmitAccumulativeRunnable(); 821 appContext.put(DO_SUBMIT_KEY, doSubmit); 822 } 823 return (AccumulativeRunnable<Runnable>) doSubmit; 824 } 825 } 826 private static class DoSubmitAccumulativeRunnable 827 extends AccumulativeRunnable<Runnable> implements ActionListener { 828 private final static int DELAY = 1000 / 30; 829 @Override 830 protected void run(List<Runnable> args) { 831 for (Runnable runnable : args) { 832 runnable.run(); 833 } 834 } 835 @Override 836 protected void submit() { 837 Timer timer = new Timer(DELAY, this); 838 timer.setRepeats(false); 839 timer.start(); 840 } 841 public void actionPerformed(ActionEvent event) { 842 run(); 843 } 844 } 845 846 private class SwingWorkerPropertyChangeSupport 847 extends PropertyChangeSupport { 848 SwingWorkerPropertyChangeSupport(Object source) { 849 super(source); 850 } 851 @Override 852 public void firePropertyChange(final PropertyChangeEvent evt) { 853 if (SwingUtilities.isEventDispatchThread()) { 854 super.firePropertyChange(evt); 855 } else { 856 doSubmit.add( 857 new Runnable() { 858 public void run() { 859 SwingWorkerPropertyChangeSupport.this 860 .firePropertyChange(evt); 861 } 862 }); 863 } 864 } 865 } 866 }