1 /* 2 * Copyright (c) 2005, 2014, 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://docs.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 * 66 * <p> 67 * These constraints mean that a GUI application with time intensive 68 * computing needs at least two threads: 1) a thread to perform the lengthy 69 * task and 2) the <i>Event Dispatch Thread</i> (EDT) for all GUI-related 70 * activities. This involves inter-thread communication which can be 71 * tricky to implement. 72 * 73 * <p> 74 * {@code SwingWorker} is designed for situations where you need to have a long 75 * running task run in a background thread and provide updates to the UI 76 * either when done, or while processing. 77 * Subclasses of {@code SwingWorker} must implement 78 * the {@link #doInBackground} method to perform the background computation. 79 * 80 * 81 * <p> 82 * <b>Workflow</b> 83 * <p> 84 * There are three threads involved in the life cycle of a 85 * {@code SwingWorker} : 86 * <ul> 87 * <li> 88 * <p> 89 * <i>Current</i> thread: The {@link #execute} method is 90 * called on this thread. It schedules {@code SwingWorker} for the execution on a 91 * <i>worker</i> 92 * thread and returns immediately. One can wait for the {@code SwingWorker} to 93 * complete using the {@link #get get} methods. 94 * <li> 95 * <p> 96 * <i>Worker</i> thread: The {@link #doInBackground} 97 * method is called on this thread. 98 * This is where all background activities should happen. To notify 99 * {@code PropertyChangeListeners} about bound properties changes use the 100 * {@link #firePropertyChange firePropertyChange} and 101 * {@link #getPropertyChangeSupport} methods. By default there are two bound 102 * properties available: {@code state} and {@code progress}. 103 * <li> 104 * <p> 105 * <i>Event Dispatch Thread</i>: All Swing related activities occur 106 * on this thread. {@code SwingWorker} invokes the 107 * {@link #process process} and {@link #done} methods and notifies 108 * any {@code PropertyChangeListeners} on this thread. 109 * </ul> 110 * 111 * <p> 112 * Often, the <i>Current</i> thread is the <i>Event Dispatch 113 * Thread</i>. 114 * 115 * 116 * <p> 117 * Before the {@code doInBackground} method is invoked on a <i>worker</i> thread, 118 * {@code SwingWorker} notifies any {@code PropertyChangeListeners} about the 119 * {@code state} property change to {@code StateValue.STARTED}. After the 120 * {@code doInBackground} method is finished the {@code done} method is 121 * executed. Then {@code SwingWorker} notifies any {@code PropertyChangeListeners} 122 * about the {@code state} property change to {@code StateValue.DONE}. 123 * 124 * <p> 125 * {@code SwingWorker} is only designed to be executed once. Executing a 126 * {@code SwingWorker} more than once will not result in invoking the 127 * {@code doInBackground} method twice. 128 * 129 * <p> 130 * <b>Sample Usage</b> 131 * <p> 132 * The following example illustrates the simplest use case. Some 133 * processing is done in the background and when done you update a Swing 134 * component. 135 * 136 * <p> 137 * Say we want to find the "Meaning of Life" and display the result in 138 * a {@code JLabel}. 139 * 140 * <pre> 141 * final JLabel label; 142 * class MeaningOfLifeFinder extends SwingWorker<String, Object> { 143 * {@code @Override} 144 * public String doInBackground() { 145 * return findTheMeaningOfLife(); 146 * } 147 * 148 * {@code @Override} 149 * protected void done() { 150 * try { 151 * label.setText(get()); 152 * } catch (Exception ignore) { 153 * } 154 * } 155 * } 156 * 157 * (new MeaningOfLifeFinder()).execute(); 158 * </pre> 159 * 160 * <p> 161 * The next example is useful in situations where you wish to process data 162 * as it is ready on the <i>Event Dispatch Thread</i>. 163 * 164 * <p> 165 * Now we want to find the first N prime numbers and display the results in a 166 * {@code JTextArea}. While this is computing, we want to update our 167 * progress in a {@code JProgressBar}. Finally, we also want to print 168 * the prime numbers to {@code System.out}. 169 * <pre> 170 * class PrimeNumbersTask extends 171 * SwingWorker<List<Integer>, Integer> { 172 * PrimeNumbersTask(JTextArea textArea, int numbersToFind) { 173 * //initialize 174 * } 175 * 176 * {@code @Override} 177 * public List<Integer> doInBackground() { 178 * while (! enough && ! isCancelled()) { 179 * number = nextPrimeNumber(); 180 * publish(number); 181 * setProgress(100 * numbers.size() / numbersToFind); 182 * } 183 * } 184 * return numbers; 185 * } 186 * 187 * {@code @Override} 188 * protected void process(List<Integer> chunks) { 189 * for (int number : chunks) { 190 * textArea.append(number + "\n"); 191 * } 192 * } 193 * } 194 * 195 * JTextArea textArea = new JTextArea(); 196 * final JProgressBar progressBar = new JProgressBar(0, 100); 197 * PrimeNumbersTask task = new PrimeNumbersTask(textArea, N); 198 * task.addPropertyChangeListener( 199 * new PropertyChangeListener() { 200 * public void propertyChange(PropertyChangeEvent evt) { 201 * if ("progress".equals(evt.getPropertyName())) { 202 * progressBar.setValue((Integer)evt.getNewValue()); 203 * } 204 * } 205 * }); 206 * 207 * task.execute(); 208 * System.out.println(task.get()); //prints all prime numbers we have got 209 * </pre> 210 * 211 * <p> 212 * Because {@code SwingWorker} implements {@code Runnable}, a 213 * {@code SwingWorker} can be submitted to an 214 * {@link java.util.concurrent.Executor} for execution. 215 * 216 * @author Igor Kushnirskiy 217 * 218 * @param <T> the result type returned by this {@code SwingWorker's} 219 * {@code doInBackground} and {@code get} methods 220 * @param <V> the type used for carrying out intermediate results by this 221 * {@code SwingWorker's} {@code publish} and {@code process} methods 222 * 223 * @since 1.6 224 */ 225 public abstract class SwingWorker<T, V> implements RunnableFuture<T> { 226 /** 227 * number of worker threads. 228 */ 229 private static final int MAX_WORKER_THREADS = 10; 230 231 /** 232 * current progress. 233 */ 234 private volatile int progress; 235 236 /** 237 * current state. 238 */ 239 private volatile StateValue state; 240 241 /** 242 * everything is run inside this FutureTask. Also it is used as 243 * a delegatee for the Future API. 244 */ 245 private final FutureTask<T> future; 246 247 /** 248 * all propertyChangeSupport goes through this. 249 */ 250 private final PropertyChangeSupport propertyChangeSupport; 251 252 /** 253 * handler for {@code process} mehtod. 254 */ 255 private AccumulativeRunnable<V> doProcess; 256 257 /** 258 * handler for progress property change notifications. 259 */ 260 private AccumulativeRunnable<Integer> doNotifyProgressChange; 261 262 private final AccumulativeRunnable<Runnable> doSubmit = getDoSubmit(); 263 264 /** 265 * Values for the {@code state} bound property. 266 * @since 1.6 267 */ 268 public enum StateValue { 269 /** 270 * Initial {@code SwingWorker} state. 271 */ 272 PENDING, 273 /** 274 * {@code SwingWorker} is {@code STARTED} 275 * before invoking {@code doInBackground}. 276 */ 277 STARTED, 278 279 /** 280 * {@code SwingWorker} is {@code DONE} 281 * after {@code doInBackground} method 282 * is finished. 283 */ 284 DONE 285 } 286 287 /** 288 * Constructs this {@code SwingWorker}. 289 */ 290 public SwingWorker() { 291 Callable<T> callable = 292 new Callable<T>() { 293 public T call() throws Exception { 294 setState(StateValue.STARTED); 295 return doInBackground(); 296 } 297 }; 298 299 future = new FutureTask<T>(callable) { 300 @Override 301 protected void done() { 302 doneEDT(); 303 setState(StateValue.DONE); 304 } 305 }; 306 307 state = StateValue.PENDING; 308 propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this); 309 doProcess = null; 310 doNotifyProgressChange = null; 311 } 312 313 /** 314 * Computes a result, or throws an exception if unable to do so. 315 * 316 * <p> 317 * Note that this method is executed only once. 318 * 319 * <p> 320 * Note: this method is executed in a background thread. 321 * 322 * 323 * @return the computed result 324 * @throws Exception if unable to compute a result 325 * 326 */ 327 protected abstract T doInBackground() throws Exception ; 328 329 /** 330 * Sets this {@code Future} to the result of computation unless 331 * it has been cancelled. 332 */ 333 public final void run() { 334 future.run(); 335 } 336 337 /** 338 * Sends data chunks to the {@link #process} method. This method is to be 339 * used from inside the {@code doInBackground} method to deliver 340 * intermediate results 341 * for processing on the <i>Event Dispatch Thread</i> inside the 342 * {@code process} method. 343 * 344 * <p> 345 * Because the {@code process} method is invoked asynchronously on 346 * the <i>Event Dispatch Thread</i> 347 * multiple invocations to the {@code publish} method 348 * might occur before the {@code process} method is executed. For 349 * performance purposes all these invocations are coalesced into one 350 * invocation with concatenated arguments. 351 * 352 * <p> 353 * For example: 354 * 355 * <pre> 356 * publish("1"); 357 * publish("2", "3"); 358 * publish("4", "5", "6"); 359 * </pre> 360 * 361 * might result in: 362 * 363 * <pre> 364 * process("1", "2", "3", "4", "5", "6") 365 * </pre> 366 * 367 * <p> 368 * <b>Sample Usage</b>. This code snippet loads some tabular data and 369 * updates {@code DefaultTableModel} with it. Note that it safe to mutate 370 * the tableModel from inside the {@code process} method because it is 371 * invoked on the <i>Event Dispatch Thread</i>. 372 * 373 * <pre> 374 * class TableSwingWorker extends 375 * SwingWorker<DefaultTableModel, Object[]> { 376 * private final DefaultTableModel tableModel; 377 * 378 * public TableSwingWorker(DefaultTableModel tableModel) { 379 * this.tableModel = tableModel; 380 * } 381 * 382 * {@code @Override} 383 * protected DefaultTableModel doInBackground() throws Exception { 384 * for (Object[] row = loadData(); 385 * ! isCancelled() && row != null; 386 * row = loadData()) { 387 * publish((Object[]) row); 388 * } 389 * return tableModel; 390 * } 391 * 392 * {@code @Override} 393 * protected void process(List<Object[]> chunks) { 394 * for (Object[] row : chunks) { 395 * tableModel.addRow(row); 396 * } 397 * } 398 * } 399 * </pre> 400 * 401 * @param chunks intermediate results to process 402 * 403 * @see #process 404 * 405 */ 406 @SafeVarargs 407 @SuppressWarnings("varargs") // Passing chunks to add is safe 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 @SuppressWarnings("serial") // JDK-implementation class 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 }