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