1 /* 2 * Copyright (c) 2010, 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 26 package javafx.concurrent; 27 28 import javafx.application.Platform; 29 import javafx.beans.property.BooleanProperty; 30 import javafx.beans.property.DoubleProperty; 31 import javafx.beans.property.ObjectProperty; 32 import javafx.beans.property.ReadOnlyBooleanProperty; 33 import javafx.beans.property.ReadOnlyDoubleProperty; 34 import javafx.beans.property.ReadOnlyObjectProperty; 35 import javafx.beans.property.ReadOnlyStringProperty; 36 import javafx.beans.property.SimpleBooleanProperty; 37 import javafx.beans.property.SimpleDoubleProperty; 38 import javafx.beans.property.SimpleObjectProperty; 39 import javafx.beans.property.SimpleStringProperty; 40 import javafx.beans.property.StringProperty; 41 import javafx.beans.value.ChangeListener; 42 import javafx.beans.value.ObservableValue; 43 import javafx.event.Event; 44 import javafx.event.EventDispatchChain; 45 import javafx.event.EventHandler; 46 import javafx.event.EventTarget; 47 import javafx.event.EventType; 48 import java.security.AccessController; 49 import java.security.AccessControlContext; 50 import java.security.PrivilegedAction; 51 import java.util.concurrent.BlockingQueue; 52 import java.util.concurrent.Executor; 53 import java.util.concurrent.LinkedBlockingQueue; 54 import java.util.concurrent.ThreadFactory; 55 import java.util.concurrent.ThreadPoolExecutor; 56 import java.util.concurrent.TimeUnit; 57 import sun.util.logging.PlatformLogger; 58 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_CANCELLED; 59 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_FAILED; 60 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_READY; 61 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_RUNNING; 62 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SCHEDULED; 63 import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SUCCEEDED; 64 65 /** 66 * <p> 67 * A Service is a non-visual component encapsulating the information required 68 * to perform some work on one or more background threads. As part of the 69 * JavaFX UI library, the Service knows about the JavaFX Application thread 70 * and is designed to relieve the application developer from the burden 71 * of managing multithreaded code that interacts with the user interface. As 72 * such, all of the methods and state on the Service are intended to be 73 * invoked exclusively from the JavaFX Application thread. The only exception 74 * to this, is when initially configuring a Service, which may safely be done 75 * from any thread, and initially starting a Service, which may also safely 76 * be done from any thread. However, once the Service has been initialized and 77 * started, it may only thereafter be used from the FX thread. 78 * </p> 79 * <p> 80 * A Service creates and manages a {@link Task} that performs the work 81 * on the background thread. 82 * Service implements {@link Worker}. As such, you can observe the state of 83 * the background task and optionally cancel it. Service is a reusable 84 * Worker, meaning that it can be reset and restarted. Due to this, a Service 85 * can be constructed declaratively and restarted on demand. 86 * Once a Service is started, it will schedule its Task and listen for 87 * changes to the state of the Task. A Task does not hold a reference to the 88 * Service that started it, meaning that a running Task will not prevent 89 * the Service from being garbage collected. 90 * </p> 91 * <p> 92 * If an {@link java.util.concurrent.Executor} is specified on the Service, 93 * then it will be used to actually execute the service. Otherwise, 94 * a daemon thread will be created and executed. If you wish to create 95 * non-daemon threads, then specify a custom Executor (for example, 96 * you could use a {@link ThreadPoolExecutor} with a custom 97 * {@link java.util.concurrent.ThreadFactory}). 98 * </p> 99 * <p> 100 * Because a Service is intended to simplify declarative use cases, subclasses 101 * should expose as properties the input parameters to the work to be done. 102 * For example, suppose I wanted to write a Service which read the first line 103 * from any URL and returned it as a String. Such a Service might be defined, 104 * such that it had a single property, {@code url}. It might be implemented 105 * as: 106 * </p> 107 * <pre><code> 108 * public static class FirstLineService extends Service<String> { 109 * private StringProperty url = new SimpleStringProperty(this, "url"); 110 * public final void setUrl(String value) { url.set(value); } 111 * public final String getUrl() { return url.get(); } 112 * public final StringProperty urlProperty() { return url; } 113 * 114 * protected Task createTask() { 115 * final String _url = getUrl(); 116 * return new Task<String>() { 117 * protected String call() throws Exception { 118 * URL u = new URL(_url); 119 * BufferedReader in = new BufferedReader( 120 * new InputStreamReader(u.openStream())); 121 * String result = in.readLine(); 122 * in.close(); 123 * return result; 124 * } 125 * }; 126 * } 127 * } 128 * </code></pre> 129 * <p> 130 * The Service by default uses a thread pool Executor with some unspecified 131 * default or maximum thread pool size. This is done so that naive code 132 * will not completely swamp the system by creating thousands of Threads. 133 * </p> 134 * @param <V> the type of object returned by the Service. 135 * @since JavaFX 2.0 136 */ 137 public abstract class Service<V> implements Worker<V>, EventTarget { 138 /** 139 * Logger used in the case of some uncaught exceptions 140 */ 141 private static final PlatformLogger LOG = PlatformLogger.getLogger(Service.class.getName()); 142 143 /* 144 The follow chunk of static state is for defining the default Executor used 145 with the Service. This is based on pre-existing JavaFX Script code and 146 experience with JavaFX Script. It was necessary to have a thread pool by default 147 because we found naive code could totally overwhelm the system otherwise 148 by spawning thousands of threads for fetching resources, for example. 149 We also set the priority and daemon status of the thread in its thread 150 factory. 151 */ 152 private static final int THREAD_POOL_SIZE = 32; 153 private static final long THREAD_TIME_OUT = 1000; 154 155 /** 156 * Because the ThreadPoolExecutor works completely backwards from what we want (ie: 157 * it doesn't increase thread count beyond the core pool size unless the queue is full), 158 * our queue has to be smart in that it will REJECT an item in the queue unless the 159 * thread size in the EXECUTOR is > 32, in which case we will queue up. 160 */ 161 private static final BlockingQueue<Runnable> IO_QUEUE = new LinkedBlockingQueue<Runnable>() { 162 @Override public boolean offer(Runnable runnable) { 163 if (EXECUTOR.getPoolSize() < THREAD_POOL_SIZE) { 164 return false; 165 } 166 return super.offer(runnable); 167 } 168 }; 169 170 // Addition of doPrivileged added due to RT-19580 171 private static final ThreadGroup THREAD_GROUP = AccessController.doPrivileged((PrivilegedAction<ThreadGroup>) () -> new ThreadGroup("javafx concurrent thread pool")); 172 private static final Thread.UncaughtExceptionHandler UNCAUGHT_HANDLER = (thread, throwable) -> { 173 // Ignore IllegalMonitorStateException, these are thrown from the ThreadPoolExecutor 174 // when a browser navigates away from a page hosting an applet that uses 175 // asynchronous tasks. These exceptions generally do not cause loss of functionality. 176 if (!(throwable instanceof IllegalMonitorStateException)) { 177 LOG.warning("Uncaught throwable in " + THREAD_GROUP.getName(), throwable); 178 } 179 }; 180 181 // Addition of doPrivileged added due to RT-19580 182 private static final ThreadFactory THREAD_FACTORY = run -> AccessController.doPrivileged((PrivilegedAction<Thread>) () -> { 183 final Thread th = new Thread(THREAD_GROUP, run); 184 th.setUncaughtExceptionHandler(UNCAUGHT_HANDLER); 185 th.setPriority(Thread.MIN_PRIORITY); 186 th.setDaemon(true); 187 return th; 188 }); 189 190 private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor( 191 2, THREAD_POOL_SIZE, 192 THREAD_TIME_OUT, TimeUnit.MILLISECONDS, 193 IO_QUEUE, THREAD_FACTORY, new ThreadPoolExecutor.AbortPolicy()); 194 195 static { 196 EXECUTOR.allowCoreThreadTimeOut(true); 197 } 198 199 private final ObjectProperty<State> state = new SimpleObjectProperty<>(this, "state", State.READY); 200 @Override public final State getState() { checkThread(); return state.get(); } 201 @Override public final ReadOnlyObjectProperty<State> stateProperty() { checkThread(); return state; } 202 203 private final ObjectProperty<V> value = new SimpleObjectProperty<>(this, "value"); 204 @Override public final V getValue() { checkThread(); return value.get(); } 205 @Override public final ReadOnlyObjectProperty<V> valueProperty() { checkThread(); return value; } 206 207 private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<>(this, "exception"); 208 @Override public final Throwable getException() { checkThread(); return exception.get(); } 209 @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception; } 210 211 private final DoubleProperty workDone = new SimpleDoubleProperty(this, "workDone", -1); 212 @Override public final double getWorkDone() { checkThread(); return workDone.get(); } 213 @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone; } 214 215 private final DoubleProperty totalWorkToBeDone = new SimpleDoubleProperty(this, "totalWork", -1); 216 @Override public final double getTotalWork() { checkThread(); return totalWorkToBeDone.get(); } 217 @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWorkToBeDone; } 218 219 private final DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1); 220 @Override public final double getProgress() { checkThread(); return progress.get(); } 221 @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress; } 222 223 private final BooleanProperty running = new SimpleBooleanProperty(this, "running", false); 224 @Override public final boolean isRunning() { checkThread(); return running.get(); } 225 @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; } 226 227 private final StringProperty message = new SimpleStringProperty(this, "message", ""); 228 @Override public final String getMessage() { checkThread(); return message.get(); } 229 @Override public final ReadOnlyStringProperty messageProperty() { checkThread(); return message; } 230 231 private final StringProperty title = new SimpleStringProperty(this, "title", ""); 232 @Override public final String getTitle() { checkThread(); return title.get(); } 233 @Override public final ReadOnlyStringProperty titleProperty() { checkThread(); return title; } 234 235 /** 236 * The executor to use for running this Service. If no executor is specified, then 237 * a new daemon thread will be created and used for running the Service using some 238 * default executor. 239 */ 240 private final ObjectProperty<Executor> executor = new SimpleObjectProperty<>(this, "executor"); 241 public final void setExecutor(Executor value) { checkThread(); executor.set(value); } 242 public final Executor getExecutor() { checkThread(); return executor.get(); } 243 public final ObjectProperty<Executor> executorProperty() { checkThread(); return executor; } 244 245 /** 246 * The onReady event handler is called whenever the Task state transitions 247 * to the READY state. 248 * 249 * @return the onReady event handler property 250 * @since JavaFX 2.1 251 */ 252 public final ObjectProperty<EventHandler<WorkerStateEvent>> onReadyProperty() { 253 checkThread(); 254 return getEventHelper().onReadyProperty(); 255 } 256 257 /** 258 * The onReady event handler is called whenever the Task state transitions 259 * to the READY state. 260 * 261 * @return the onReady event handler, if any 262 * @since JavaFX 2.1 263 */ 264 public final EventHandler<WorkerStateEvent> getOnReady() { 265 checkThread(); 266 return eventHelper == null ? null : eventHelper.getOnReady(); 267 } 268 269 /** 270 * The onReady event handler is called whenever the Task state transitions 271 * to the READY state. 272 * 273 * @param value the event handler, can be null to clear it 274 * @since JavaFX 2.1 275 */ 276 public final void setOnReady(EventHandler<WorkerStateEvent> value) { 277 checkThread(); 278 getEventHelper().setOnReady(value); 279 } 280 281 /** 282 * A protected convenience method for subclasses, called whenever the 283 * state of the Service has transitioned to the READY state. 284 * This method is invoked after the Service has been fully transitioned to the new state. 285 * @since JavaFX 2.1 286 */ 287 protected void ready() { } 288 289 /** 290 * The onSchedule event handler is called whenever the Task state 291 * transitions to the SCHEDULED state. 292 * 293 * @return the onScheduled event handler property 294 * @since JavaFX 2.1 295 */ 296 public final ObjectProperty<EventHandler<WorkerStateEvent>> onScheduledProperty() { 297 checkThread(); 298 return getEventHelper().onScheduledProperty(); 299 } 300 301 /** 302 * The onSchedule event handler is called whenever the Task state 303 * transitions to the SCHEDULED state. 304 * 305 * @return the onScheduled event handler, if any 306 * @since JavaFX 2.1 307 */ 308 public final EventHandler<WorkerStateEvent> getOnScheduled() { 309 checkThread(); 310 return eventHelper == null ? null : eventHelper.getOnScheduled(); 311 } 312 313 /** 314 * The onSchedule event handler is called whenever the Task state 315 * transitions to the SCHEDULED state. 316 * 317 * @param value the event handler, can be null to clear it 318 * @since JavaFX 2.1 319 */ 320 public final void setOnScheduled(EventHandler<WorkerStateEvent> value) { 321 checkThread(); 322 getEventHelper().setOnScheduled(value); 323 } 324 325 /** 326 * A protected convenience method for subclasses, called whenever the 327 * state of the Service has transitioned to the SCHEDULED state. 328 * This method is invoked after the Service has been fully transitioned to the new state. 329 * @since JavaFX 2.1 330 */ 331 protected void scheduled() { } 332 333 /** 334 * The onRunning event handler is called whenever the Task state 335 * transitions to the RUNNING state. 336 * 337 * @return the onRunning event handler property 338 * @since JavaFX 2.1 339 */ 340 public final ObjectProperty<EventHandler<WorkerStateEvent>> onRunningProperty() { 341 checkThread(); 342 return getEventHelper().onRunningProperty(); 343 } 344 345 /** 346 * The onRunning event handler is called whenever the Task state 347 * transitions to the RUNNING state. 348 * 349 * @return the onRunning event handler, if any 350 * @since JavaFX 2.1 351 */ 352 public final EventHandler<WorkerStateEvent> getOnRunning() { 353 checkThread(); 354 return eventHelper == null ? null : eventHelper.getOnRunning(); 355 } 356 357 /** 358 * The onRunning event handler is called whenever the Task state 359 * transitions to the RUNNING state. 360 * 361 * @param value the event handler, can be null to clear it 362 * @since JavaFX 2.1 363 */ 364 public final void setOnRunning(EventHandler<WorkerStateEvent> value) { 365 checkThread(); 366 getEventHelper().setOnRunning(value); 367 } 368 369 /** 370 * A protected convenience method for subclasses, called whenever the 371 * state of the Service has transitioned to the RUNNING state. 372 * This method is invoked after the Service has been fully transitioned to the new state. 373 * @since JavaFX 2.1 374 */ 375 protected void running() { } 376 377 /** 378 * The onSucceeded event handler is called whenever the Task state 379 * transitions to the SUCCEEDED state. 380 * 381 * @return the onSucceeded event handler property 382 * @since JavaFX 2.1 383 */ 384 public final ObjectProperty<EventHandler<WorkerStateEvent>> onSucceededProperty() { 385 checkThread(); 386 return getEventHelper().onSucceededProperty(); 387 } 388 389 /** 390 * The onSucceeded event handler is called whenever the Task state 391 * transitions to the SUCCEEDED state. 392 * 393 * @return the onSucceeded event handler, if any 394 * @since JavaFX 2.1 395 */ 396 public final EventHandler<WorkerStateEvent> getOnSucceeded() { 397 checkThread(); 398 return eventHelper == null ? null : eventHelper.getOnSucceeded(); 399 } 400 401 /** 402 * The onSucceeded event handler is called whenever the Task state 403 * transitions to the SUCCEEDED state. 404 * 405 * @param value the event handler, can be null to clear it 406 * @since JavaFX 2.1 407 */ 408 public final void setOnSucceeded(EventHandler<WorkerStateEvent> value) { 409 checkThread(); 410 getEventHelper().setOnSucceeded(value); 411 } 412 413 /** 414 * A protected convenience method for subclasses, called whenever the 415 * state of the Service has transitioned to the SUCCEEDED state. 416 * This method is invoked after the Service has been fully transitioned to the new state. 417 * @since JavaFX 2.1 418 */ 419 protected void succeeded() { } 420 421 /** 422 * The onCancelled event handler is called whenever the Task state 423 * transitions to the CANCELLED state. 424 * 425 * @return the onCancelled event handler property 426 * @since JavaFX 2.1 427 */ 428 public final ObjectProperty<EventHandler<WorkerStateEvent>> onCancelledProperty() { 429 checkThread(); 430 return getEventHelper().onCancelledProperty(); 431 } 432 433 /** 434 * The onCancelled event handler is called whenever the Task state 435 * transitions to the CANCELLED state. 436 * 437 * @return the onCancelled event handler, if any 438 * @since JavaFX 2.1 439 */ 440 public final EventHandler<WorkerStateEvent> getOnCancelled() { 441 checkThread(); 442 return eventHelper == null ? null : eventHelper.getOnCancelled(); 443 } 444 445 /** 446 * The onCancelled event handler is called whenever the Task state 447 * transitions to the CANCELLED state. 448 * 449 * @param value the event handler, can be null to clear it 450 * @since JavaFX 2.1 451 */ 452 public final void setOnCancelled(EventHandler<WorkerStateEvent> value) { 453 checkThread(); 454 getEventHelper().setOnCancelled(value); 455 } 456 457 /** 458 * A protected convenience method for subclasses, called whenever the 459 * state of the Service has transitioned to the CANCELLED state. 460 * This method is invoked after the Service has been fully transitioned to the new state. 461 * @since JavaFX 2.1 462 */ 463 protected void cancelled() { } 464 465 /** 466 * The onFailed event handler is called whenever the Task state 467 * transitions to the FAILED state. 468 * 469 * @return the onFailed event handler property 470 * @since JavaFX 2.1 471 */ 472 public final ObjectProperty<EventHandler<WorkerStateEvent>> onFailedProperty() { 473 checkThread(); 474 return getEventHelper().onFailedProperty(); 475 } 476 477 /** 478 * The onFailed event handler is called whenever the Task state 479 * transitions to the FAILED state. 480 * 481 * @return the onFailed event handler, if any 482 * @since JavaFX 2.1 483 */ 484 public final EventHandler<WorkerStateEvent> getOnFailed() { 485 checkThread(); 486 return eventHelper == null ? null : eventHelper.getOnFailed(); 487 } 488 489 /** 490 * The onFailed event handler is called whenever the Task state 491 * transitions to the FAILED state. 492 * 493 * @param value the event handler, can be null to clear it 494 * @since JavaFX 2.1 495 */ 496 public final void setOnFailed(EventHandler<WorkerStateEvent> value) { 497 checkThread(); 498 getEventHelper().setOnFailed(value); 499 } 500 501 /** 502 * A protected convenience method for subclasses, called whenever the 503 * state of the Service has transitioned to the FAILED state. 504 * This method is invoked after the Service has been fully transitioned to the new state. 505 * @since JavaFX 2.1 506 */ 507 protected void failed() { } 508 509 /** 510 * A reference to the last task that was executed. I need this reference so that in the 511 * restart method I can cancel the currently running task, and so the cancel method 512 * can cancel the currently running task. 513 */ 514 private Task<V> task; 515 516 /** 517 * This boolean is set to true once the Service has been initially started. You can initialize 518 * the Service from any thread, and you can initially start it from any thread. But any 519 * subsequent usage of the service's methods must occur on the FX application thread. 520 */ 521 private volatile boolean startedOnce = false; 522 523 /** 524 * Create a new Service. 525 */ 526 protected Service() { 527 // Add a listener to the state, such that we can fire the correct event 528 // notifications whenever the state of the Service has changed. 529 state.addListener((observableValue, old, value1) -> { 530 531 // Invoke the appropriate event handler 532 switch (value1) { 533 case CANCELLED: 534 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_CANCELLED)); 535 cancelled(); 536 break; 537 case FAILED: 538 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_FAILED)); 539 failed(); 540 break; 541 case READY: 542 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_READY)); 543 ready(); 544 break; 545 case RUNNING: 546 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_RUNNING)); 547 running(); 548 break; 549 case SCHEDULED: 550 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_SCHEDULED)); 551 scheduled(); 552 break; 553 case SUCCEEDED: 554 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_SUCCEEDED)); 555 succeeded(); 556 break; 557 default: throw new AssertionError("Should be unreachable"); 558 } 559 }); 560 } 561 562 /** 563 * Cancels any currently running Task, if any. The state will be set to CANCELLED. 564 * @return returns true if the cancel was successful 565 */ 566 @Override public boolean cancel() { 567 checkThread(); 568 if (task == null) { 569 if (state.get() == State.CANCELLED || state.get() == State.SUCCEEDED) { 570 return false; 571 } 572 state.set(State.CANCELLED); 573 return true; 574 } else { 575 return task.cancel(true); 576 } 577 } 578 579 /** 580 * Cancels any currently running Task, if any, and restarts this Service. The state 581 * will be reset to READY prior to execution. This method should only be called on 582 * the FX application thread. 583 */ 584 public void restart() { 585 checkThread(); 586 587 // Cancel the current task, if there is one 588 if (task != null) { 589 task.cancel(); 590 task = null; 591 592 // RT-20880: IllegalStateException thrown from Service#restart() 593 // The problem is that the reset method explodes if the state 594 // is SCHEDULED or RUNNING. Although we have cancelled the 595 // task above, it is possible that cancelling does not change 596 // state to the CANCELLED state. However we still need to 597 // succeed in resetting. I believe that although the old task is 598 // still running away, everything is about to be unbound so 599 // we really can just let the old task run and create a new 600 // task and the Service will be none the wiser. 601 state.unbind(); 602 state.set(State.CANCELLED); 603 } 604 605 // Reset 606 reset(); 607 608 // Start the thing up again. 609 start(); 610 } 611 612 /** 613 * Resets the Service. May only be called while in one of the finish states, 614 * that is, SUCCEEDED, FAILED, or CANCELLED, or when READY. This method should 615 * only be called on the FX application thread. 616 */ 617 public void reset() { 618 checkThread(); 619 final State s = getState(); 620 if (s == State.SCHEDULED || s == State.RUNNING) { 621 throw new IllegalStateException(); 622 } 623 624 task = null; 625 state.unbind(); 626 state.set(State.READY); 627 value.unbind(); 628 value.set(null); 629 exception.unbind(); 630 exception.set(null); 631 workDone.unbind(); 632 workDone.set(-1); 633 totalWorkToBeDone.unbind(); 634 totalWorkToBeDone.set(-1); 635 progress.unbind(); 636 progress.set(-1); 637 running.unbind(); 638 running.set(false); 639 message.unbind(); 640 message.set(""); 641 title.unbind(); 642 title.set(""); 643 } 644 645 /** 646 * Starts this Service. The Service must be in the READY state to succeed in this call. 647 * This method should only be called on the FX application thread. 648 */ 649 public void start() { 650 checkThread(); 651 652 if (getState() != State.READY) { 653 throw new IllegalStateException( 654 "Can only start a Service in the READY state. Was in state " + getState()); 655 } 656 657 // Create the task 658 task = createTask(); 659 660 // Wire up all the properties so they use this task 661 state.bind(task.stateProperty()); 662 value.bind(task.valueProperty()); 663 exception.bind(task.exceptionProperty()); 664 workDone.bind(task.workDoneProperty()); 665 totalWorkToBeDone.bind(task.totalWorkProperty()); 666 progress.bind(task.progressProperty()); 667 running.bind(task.runningProperty()); 668 message.bind(task.messageProperty()); 669 title.bind(task.titleProperty()); 670 671 // Record that start has been called once, so we don't allow it to be called again from 672 // any thread other than the fx thread 673 startedOnce = true; 674 675 if (!isFxApplicationThread()) { 676 runLater(() -> { 677 // Advance the task to the "SCHEDULED" state 678 task.setState(State.SCHEDULED); 679 680 // Start the task 681 executeTask(task); 682 }); 683 } else { 684 // Advance the task to the "SCHEDULED" state 685 task.setState(State.SCHEDULED); 686 687 // Start the task 688 executeTask(task); 689 } 690 } 691 692 /** 693 * This is used by ScheduledService to cancel a Service that is in the READY state. The problem is 694 * that a ScheduledService will iterate from SUCCEEDED to READY, and then call start() to transition 695 * from READY to SCHEDULED and kick off a new iteration. However, if from the SUCCEEDED event handler 696 * a developer calls "cancel" to stop a ScheduledService, we have a problem, since the Service 697 * specification does not allow to transition from a terminal state (SUCCEEDED) to another terminal 698 * state (CANCELLED), but this is clearly what we need to do. So what this method will do is allow 699 * us to transition from the READY state to the CANCELLED state, by transitioning through SCHEDULED 700 */ 701 void cancelFromReadyState() { 702 state.set(State.SCHEDULED); 703 state.set(State.CANCELLED); 704 } 705 706 /** 707 * <p> 708 * Uses the <code>executor</code> defined on this Service to execute the 709 * given task. If the <code>executor</code> is null, then a default 710 * executor is used which will create a new daemon thread on which to 711 * execute this task. 712 * </p> 713 * <p> 714 * This method is intended only to be called by the Service 715 * implementation. 716 * </p> 717 * @param task a non-null task to execute 718 * @since JavaFX 2.1 719 */ 720 protected void executeTask(final Task<V> task) { 721 final AccessControlContext acc = AccessController.getContext(); 722 final Executor e = getExecutor() != null ? getExecutor() : EXECUTOR; 723 e.execute(() -> { 724 AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 725 task.run(); 726 return null; 727 }, acc); 728 }); 729 } 730 731 /*************************************************************************** 732 * * 733 * Event Dispatch * 734 * * 735 **************************************************************************/ 736 737 private EventHelper eventHelper = null; 738 private EventHelper getEventHelper() { 739 if (eventHelper == null) { 740 eventHelper = new EventHelper(this); 741 } 742 return eventHelper; 743 } 744 745 /** 746 * Registers an event handler to this task. Any event filters are first 747 * processed, then the specified onFoo event handlers, and finally any 748 * event handlers registered by this method. As with other events 749 * in the scene graph, if an event is consumed, it will not continue 750 * dispatching. 751 * 752 * @param <T> the specific event class of the handler 753 * @param eventType the type of the events to receive by the handler 754 * @param eventHandler the handler to register 755 * @throws NullPointerException if the event type or handler is null 756 * @since JavaFX 2.1 757 */ 758 public final <T extends Event> void addEventHandler( 759 final EventType<T> eventType, 760 final EventHandler<? super T> eventHandler) { 761 checkThread(); 762 getEventHelper().addEventHandler(eventType, eventHandler); 763 } 764 765 /** 766 * Unregisters a previously registered event handler from this task. One 767 * handler might have been registered for different event types, so the 768 * caller needs to specify the particular event type from which to 769 * unregister the handler. 770 * 771 * @param <T> the specific event class of the handler 772 * @param eventType the event type from which to unregister 773 * @param eventHandler the handler to unregister 774 * @throws NullPointerException if the event type or handler is null 775 * @since JavaFX 2.1 776 */ 777 public final <T extends Event> void removeEventHandler( 778 final EventType<T> eventType, 779 final EventHandler<? super T> eventHandler) { 780 checkThread(); 781 getEventHelper().removeEventHandler(eventType, eventHandler); 782 } 783 784 /** 785 * Registers an event filter to this task. Registered event filters get 786 * an event before any associated event handlers. 787 * 788 * @param <T> the specific event class of the filter 789 * @param eventType the type of the events to receive by the filter 790 * @param eventFilter the filter to register 791 * @throws NullPointerException if the event type or filter is null 792 * @since JavaFX 2.1 793 */ 794 public final <T extends Event> void addEventFilter( 795 final EventType<T> eventType, 796 final EventHandler<? super T> eventFilter) { 797 checkThread(); 798 getEventHelper().addEventFilter(eventType, eventFilter); 799 } 800 801 /** 802 * Unregisters a previously registered event filter from this task. One 803 * filter might have been registered for different event types, so the 804 * caller needs to specify the particular event type from which to 805 * unregister the filter. 806 * 807 * @param <T> the specific event class of the filter 808 * @param eventType the event type from which to unregister 809 * @param eventFilter the filter to unregister 810 * @throws NullPointerException if the event type or filter is null 811 * @since JavaFX 2.1 812 */ 813 public final <T extends Event> void removeEventFilter( 814 final EventType<T> eventType, 815 final EventHandler<? super T> eventFilter) { 816 checkThread(); 817 getEventHelper().removeEventFilter(eventType, eventFilter); 818 } 819 820 /** 821 * Sets the handler to use for this event type. There can only be one such 822 * handler specified at a time. This handler is guaranteed to be called 823 * first. This is used for registering the user-defined onFoo event 824 * handlers. 825 * 826 * @param <T> the specific event class of the handler 827 * @param eventType the event type to associate with the given eventHandler 828 * @param eventHandler the handler to register, or null to unregister 829 * @throws NullPointerException if the event type is null 830 * @since JavaFX 2.1 831 */ 832 protected final <T extends Event> void setEventHandler( 833 final EventType<T> eventType, 834 final EventHandler<? super T> eventHandler) { 835 checkThread(); 836 getEventHelper().setEventHandler(eventType, eventHandler); 837 } 838 839 /** 840 * Fires the specified event. Any event filter encountered will 841 * be notified and can consume the event. If not consumed by the filters, 842 * the event handlers on this task are notified. If these don't consume the 843 * event either, then all event handlers are called and can consume the 844 * event. 845 * <p> 846 * This method must be called on the FX user thread. 847 * 848 * @param event the event to fire 849 * @since JavaFX 2.1 850 */ 851 protected final void fireEvent(Event event) { 852 checkThread(); 853 getEventHelper().fireEvent(event); 854 } 855 856 @Override 857 public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 858 checkThread(); 859 return getEventHelper().buildEventDispatchChain(tail); 860 } 861 862 /** 863 * Invoked after the Service is started on the JavaFX Application Thread. 864 * Implementations should save off any state into final variables prior to 865 * creating the Task, since accessing properties defined on the Service 866 * within the background thread code of the Task will result in exceptions. 867 * 868 * For example: 869 * <pre><code> 870 * protected Task createTask() { 871 * final String url = myService.getUrl(); 872 * return new Task<String>() { 873 * protected String call() { 874 * URL u = new URL("http://www.oracle.com"); 875 * BufferedReader in = new BufferedReader( 876 * new InputStreamReader(u.openStream())); 877 * String result = in.readLine(); 878 * in.close(); 879 * return result; 880 * } 881 * } 882 * } 883 * </code></pre> 884 * 885 * <p> 886 * If the Task is a pre-defined class (as opposed to being an 887 * anonymous class), and if it followed the recommended best-practice, 888 * then there is no need to save off state prior to constructing 889 * the Task since its state is completely provided in its constructor. 890 * </p> 891 * 892 * <pre><code> 893 * protected Task createTask() { 894 * // This is safe because getUrl is called on the FX Application 895 * // Thread and the FirstLineReaderTasks stores it as an 896 * // immutable property 897 * return new FirstLineReaderTask(myService.getUrl()); 898 * } 899 * </code></pre> 900 * @return the Task to execute 901 */ 902 protected abstract Task<V> createTask(); 903 904 void checkThread() { 905 if (startedOnce && !isFxApplicationThread()) { 906 throw new IllegalStateException("Service must only be used from the FX Application Thread"); 907 } 908 } 909 910 // This method exists for the sake of testing, so I can subclass and override 911 // this method in the test and not actually use Platform.runLater. 912 void runLater(Runnable r) { 913 Platform.runLater(r); 914 } 915 916 // This method exists for the sake of testing, so I can subclass and override 917 // this method in the test and not actually use Platform.isFxApplicationThread. 918 boolean isFxApplicationThread() { 919 return Platform.isFxApplicationThread(); 920 } 921 }