Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.concurrent; 027 028import java.security.AccessController; 029import java.security.PrivilegedAction; 030import java.util.concurrent.BlockingQueue; 031import java.util.concurrent.Executor; 032import java.util.concurrent.LinkedBlockingQueue; 033import java.util.concurrent.ThreadFactory; 034import java.util.concurrent.ThreadPoolExecutor; 035import java.util.concurrent.TimeUnit; 036import javafx.application.Platform; 037import javafx.beans.property.BooleanProperty; 038import javafx.beans.property.DoubleProperty; 039import javafx.beans.property.ObjectProperty; 040import javafx.beans.property.ReadOnlyBooleanProperty; 041import javafx.beans.property.ReadOnlyDoubleProperty; 042import javafx.beans.property.ReadOnlyObjectProperty; 043import javafx.beans.property.ReadOnlyStringProperty; 044import javafx.beans.property.SimpleBooleanProperty; 045import javafx.beans.property.SimpleDoubleProperty; 046import javafx.beans.property.SimpleObjectProperty; 047import javafx.beans.property.SimpleStringProperty; 048import javafx.beans.property.StringProperty; 049import javafx.beans.value.ChangeListener; 050import javafx.beans.value.ObservableValue; 051import javafx.event.Event; 052import javafx.event.EventDispatchChain; 053import javafx.event.EventHandler; 054import javafx.event.EventTarget; 055import javafx.event.EventType; 056import sun.util.logging.PlatformLogger; 057 058import static javafx.concurrent.WorkerStateEvent.*; 059 060/** 061 * <p> 062 * A Service is a non-visual component encapsulating the information required 063 * to perform some work on one or more background threads. As part of the 064 * JavaFX UI library, the Service knows about the JavaFX Application thread 065 * and is designed to relieve the application developer from the burden 066 * of manging multithreaded code that interacts with the user interface. As 067 * such, all of the methods and state on the Service are intended to be 068 * invoked exclusively from the JavaFX Application thread. 069 * </p> 070 * <p> 071 * Service implements {@link Worker}. As such, you can observe the state of 072 * the background operation and optionally cancel it. Service is a reusable 073 * Worker, meaning that it can be reset and restarted. Due to this, a Service 074 * can be constructed declaratively and restarted on demand. 075 * </p> 076 * <p> 077 * If an {@link java.util.concurrent.Executor} is specified on the Service, 078 * then it will be used to actually execute the service. Otherwise, 079 * a daemon thread will be created and executed. If you wish to create 080 * non-daemon threads, then specify a custom Executor (for example, 081 * you could use a {@link ThreadPoolExecutor} with a custom 082 * {@link java.util.concurrent.ThreadFactory}). 083 * </p> 084 * <p> 085 * Because a Service is intended to simplify declarative use cases, subclasses 086 * should expose as properties the input parameters to the work to be done. 087 * For example, suppose I wanted to write a Service which read the first line 088 * from any URL and returned it as a String. Such a Service might be defined, 089 * such that it had a single property, <code>url</code>. It might be implemented 090 * as: 091 * <pre><code> 092 * public static class FirstLineService extends Service<String> { 093 * private StringProperty url = new SimpleStringProperty(this, "url"); 094 * public final void setUrl(String value) { url.set(value); } 095 * public final String getUrl() { return url.get(); } 096 * public final StringProperty urlProperty() { return url; } 097 * 098 * protected Task createTask() { 099 * final String _url = getUrl(); 100 * return new Task<String>() { 101 * protected String call() throws Exception { 102 * URL u = new URL(_url); 103 * BufferedReader in = new BufferedReader( 104 * new InputStreamReader(u.openStream())); 105 * String result = in.readLine(); 106 * in.close(); 107 * return result; 108 * } 109 * }; 110 * } 111 * } 112 * </code></pre> 113 * </p> 114 * <p> 115 * The Service by default uses a thread pool Executor with some unspecified 116 * default or maximum thread pool size. This is done so that naive code 117 * will not completely swamp the system by creating thousands of Threads. 118 * </p> 119 * @param <V> 120 */ 121public abstract class Service<V> implements Worker<V>, EventTarget { 122 /** 123 * Logger used in the case of some uncaught exceptions 124 */ 125 private static final PlatformLogger LOG = PlatformLogger.getLogger(Service.class.getName()); 126 127 /* 128 The follow chunk of static state is for defining the default Executor used 129 with the Service. This is based on pre-existing JavaFX Script code and 130 experience with JavaFX Script. It was necessary to have a thread pool by default 131 because we found naive code could totally overwhelm the system otherwise 132 by spawning thousands of threads for fetching resources, for example. 133 We also set the priority and daemon status of the thread in its thread 134 factory. 135 */ 136 private static final int THREAD_POOL_SIZE = 32; 137 private static final long THREAD_TIME_OUT = 1000; 138 139 /** 140 * Because the ThreadPoolExecutor works completely backwards from what we want (ie: 141 * it doesn't increase thread count beyond the core pool size unless the queue is full), 142 * our queue has to be smart in that it will REJECT an item in the queue unless the 143 * thread size in the EXECUTOR is > 32, in which case we will queue up. 144 */ 145 private static final BlockingQueue<Runnable> IO_QUEUE = new LinkedBlockingQueue<Runnable>() { 146 @Override public boolean offer(Runnable runnable) { 147 if (EXECUTOR.getPoolSize() < THREAD_POOL_SIZE) { 148 return false; 149 } 150 return super.offer(runnable); 151 } 152 }; 153 154 // Addition of doPrivileged added due to RT-19580 155 private static final ThreadGroup THREAD_GROUP = AccessController.doPrivileged(new PrivilegedAction<ThreadGroup>() { 156 @Override public ThreadGroup run() { 157 return new ThreadGroup("javafx concurrent thread pool"); 158 } 159 }); 160 private static final Thread.UncaughtExceptionHandler UNCAUGHT_HANDLER = new Thread.UncaughtExceptionHandler() { 161 @Override public void uncaughtException(Thread thread, Throwable throwable) { 162 // Ignore IllegalMonitorStateException, these are thrown from the ThreadPoolExecutor 163 // when a browser navigates away from a page hosting an applet that uses 164 // asynchronous tasks. These exceptions generally do not cause loss of functionality. 165 if (!(throwable instanceof IllegalMonitorStateException)) { 166 LOG.warning("Uncaught throwable in " + THREAD_GROUP.getName(), throwable); 167 } 168 } 169 }; 170 171 private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { 172 @Override public Thread newThread(final Runnable run) { 173 // Addition of doPrivileged added due to RT-19580 174 return AccessController.doPrivileged(new PrivilegedAction<Thread>() { 175 @Override public Thread run() { 176 final Thread th = new Thread(THREAD_GROUP, run); 177 th.setUncaughtExceptionHandler(UNCAUGHT_HANDLER); 178 th.setPriority(Thread.MIN_PRIORITY); 179 th.setDaemon(true); 180 return th; 181 } 182 }); 183 } 184 }; 185 186 private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor( 187 2, THREAD_POOL_SIZE, 188 THREAD_TIME_OUT, TimeUnit.MILLISECONDS, 189 IO_QUEUE, THREAD_FACTORY, new ThreadPoolExecutor.AbortPolicy()); 190 191 static { 192 EXECUTOR.allowCoreThreadTimeOut(true); 193 } 194 195 private final ObjectProperty<State> state = new SimpleObjectProperty<State>(this, "state", State.READY); 196 @Override public final State getState() { checkThread(); return state.get(); } 197 @Override public final ReadOnlyObjectProperty<State> stateProperty() { checkThread(); return state; } 198 199 private final ObjectProperty<V> value = new SimpleObjectProperty<V>(this, "value"); 200 @Override public final V getValue() { checkThread(); return value.get(); } 201 @Override public final ReadOnlyObjectProperty<V> valueProperty() { checkThread(); return value; } 202 203 private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<Throwable>(this, "exception"); 204 @Override public final Throwable getException() { checkThread(); return exception.get(); } 205 @Override public final ReadOnlyObjectProperty<Throwable> exceptionProperty() { checkThread(); return exception; } 206 207 private final DoubleProperty workDone = new SimpleDoubleProperty(this, "workDone", -1); 208 @Override public final double getWorkDone() { checkThread(); return workDone.get(); } 209 @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone; } 210 211 private final DoubleProperty totalWorkToBeDone = new SimpleDoubleProperty(this, "totalWork", -1); 212 @Override public final double getTotalWork() { checkThread(); return totalWorkToBeDone.get(); } 213 @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWorkToBeDone; } 214 215 private final DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1); 216 @Override public final double getProgress() { checkThread(); return progress.get(); } 217 @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress; } 218 219 private final BooleanProperty running = new SimpleBooleanProperty(this, "running", false); 220 @Override public final boolean isRunning() { checkThread(); return running.get(); } 221 @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; } 222 223 private final StringProperty message = new SimpleStringProperty(this, "message", ""); 224 @Override public final String getMessage() { return message.get(); } 225 @Override public final ReadOnlyStringProperty messageProperty() { return message; } 226 227 private final StringProperty title = new SimpleStringProperty(this, "title", ""); 228 @Override public final String getTitle() { return title.get(); } 229 @Override public final ReadOnlyStringProperty titleProperty() { return title; } 230 231 /** 232 * The executor to use for running this Service. If no executor is specified, then 233 * a new daemon thread will be created and used for running the Service using some 234 * default executor. 235 */ 236 private final ObjectProperty<Executor> executor = new SimpleObjectProperty<Executor>(this, "executor"); 237 public final void setExecutor(Executor value) { checkThread(); executor.set(value); } 238 public final Executor getExecutor() { checkThread(); return executor.get(); } 239 public final ObjectProperty<Executor> executorProperty() { return executor; } 240 241 /** 242 * The onReady event handler is called whenever the Task state transitions 243 * to the READY state. 244 * 245 * @return the onReady event handler property 246 */ 247 public final ObjectProperty<EventHandler<WorkerStateEvent>> onReadyProperty() { 248 return getEventHelper().onReadyProperty(); 249 } 250 251 /** 252 * The onReady event handler is called whenever the Task state transitions 253 * to the READY state. 254 * 255 * @return the onReady event handler, if any 256 */ 257 public final EventHandler<WorkerStateEvent> getOnReady() { 258 return eventHelper == null ? null : eventHelper.getOnReady(); 259 } 260 261 /** 262 * The onReady event handler is called whenever the Task state transitions 263 * to the READY state. 264 * 265 * @param value the event handler, can be null to clear it 266 */ 267 public final void setOnReady(EventHandler<WorkerStateEvent> value) { 268 getEventHelper().setOnReady(value); 269 } 270 271 /** 272 * A protected convenience method for subclasses, called whenever the 273 * state of the Task has transitioned to the READY state. 274 * This method is invoked after any listeners of the state property 275 * and after the Task has been fully transitioned to the new state. 276 */ 277 protected void ready() { } 278 279 /** 280 * The onSchedule event handler is called whenever the Task state 281 * transitions to the SCHEDULED state. 282 * 283 * @return the onScheduled event handler property 284 */ 285 public final ObjectProperty<EventHandler<WorkerStateEvent>> onScheduledProperty() { 286 return getEventHelper().onScheduledProperty(); 287 } 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, if any 294 */ 295 public final EventHandler<WorkerStateEvent> getOnScheduled() { 296 return eventHelper == null ? null : eventHelper.getOnScheduled(); 297 } 298 299 /** 300 * The onSchedule event handler is called whenever the Task state 301 * transitions to the SCHEDULED state. 302 * 303 * @param value the event handler, can be null to clear it 304 */ 305 public final void setOnScheduled(EventHandler<WorkerStateEvent> value) { 306 getEventHelper().setOnScheduled(value); 307 } 308 309 /** 310 * A protected convenience method for subclasses, called whenever the 311 * state of the Task has transitioned to the SCHEDULED state. 312 * This method is invoked after any listeners of the state property 313 * and after the Task has been fully transitioned to the new state. 314 */ 315 protected void scheduled() { } 316 317 /** 318 * The onRunning event handler is called whenever the Task state 319 * transitions to the RUNNING state. 320 * 321 * @return the onRunning event handler property 322 */ 323 public final ObjectProperty<EventHandler<WorkerStateEvent>> onRunningProperty() { 324 return getEventHelper().onRunningProperty(); 325 } 326 327 /** 328 * The onRunning event handler is called whenever the Task state 329 * transitions to the RUNNING state. 330 * 331 * @return the onRunning event handler, if any 332 */ 333 public final EventHandler<WorkerStateEvent> getOnRunning() { 334 return eventHelper == null ? null : eventHelper.getOnRunning(); 335 } 336 337 /** 338 * The onRunning event handler is called whenever the Task state 339 * transitions to the RUNNING state. 340 * 341 * @param value the event handler, can be null to clear it 342 */ 343 public final void setOnRunning(EventHandler<WorkerStateEvent> value) { 344 getEventHelper().setOnRunning(value); 345 } 346 347 /** 348 * A protected convenience method for subclasses, called whenever the 349 * state of the Task has transitioned to the RUNNING state. 350 * This method is invoked after any listeners of the state property 351 * and after the Task has been fully transitioned to the new state. 352 */ 353 protected void running() { } 354 355 /** 356 * The onSucceeded event handler is called whenever the Task state 357 * transitions to the SUCCEEDED state. 358 * 359 * @return the onSucceeded event handler property 360 */ 361 public final ObjectProperty<EventHandler<WorkerStateEvent>> onSucceededProperty() { 362 return getEventHelper().onSucceededProperty(); 363 } 364 365 /** 366 * The onSucceeded event handler is called whenever the Task state 367 * transitions to the SUCCEEDED state. 368 * 369 * @return the onSucceeded event handler, if any 370 */ 371 public final EventHandler<WorkerStateEvent> getOnSucceeded() { 372 return eventHelper == null ? null : eventHelper.getOnSucceeded(); 373 } 374 375 /** 376 * The onSucceeded event handler is called whenever the Task state 377 * transitions to the SUCCEEDED state. 378 * 379 * @param value the event handler, can be null to clear it 380 */ 381 public final void setOnSucceeded(EventHandler<WorkerStateEvent> value) { 382 getEventHelper().setOnSucceeded(value); 383 } 384 385 /** 386 * A protected convenience method for subclasses, called whenever the 387 * state of the Task has transitioned to the SUCCEEDED state. 388 * This method is invoked after any listeners of the state property 389 * and after the Task has been fully transitioned to the new state. 390 */ 391 protected void succeeded() { } 392 393 /** 394 * The onCancelled event handler is called whenever the Task state 395 * transitions to the CANCELLED state. 396 * 397 * @return the onCancelled event handler property 398 */ 399 public final ObjectProperty<EventHandler<WorkerStateEvent>> onCancelledProperty() { 400 return getEventHelper().onCancelledProperty(); 401 } 402 403 /** 404 * The onCancelled event handler is called whenever the Task state 405 * transitions to the CANCELLED state. 406 * 407 * @return the onCancelled event handler, if any 408 */ 409 public final EventHandler<WorkerStateEvent> getOnCancelled() { 410 return eventHelper == null ? null : eventHelper.getOnCancelled(); 411 } 412 413 /** 414 * The onCancelled event handler is called whenever the Task state 415 * transitions to the CANCELLED state. 416 * 417 * @param value the event handler, can be null to clear it 418 */ 419 public final void setOnCancelled(EventHandler<WorkerStateEvent> value) { 420 getEventHelper().setOnCancelled(value); 421 } 422 423 /** 424 * A protected convenience method for subclasses, called whenever the 425 * state of the Task has transitioned to the CANCELLED state. 426 * This method is invoked after any listeners of the state property 427 * and after the Task has been fully transitioned to the new state. 428 */ 429 protected void cancelled() { } 430 431 /** 432 * The onFailed event handler is called whenever the Task state 433 * transitions to the FAILED state. 434 * 435 * @return the onFailed event handler property 436 */ 437 public final ObjectProperty<EventHandler<WorkerStateEvent>> onFailedProperty() { 438 return getEventHelper().onFailedProperty(); 439 } 440 441 /** 442 * The onFailed event handler is called whenever the Task state 443 * transitions to the FAILED state. 444 * 445 * @return the onFailed event handler, if any 446 */ 447 public final EventHandler<WorkerStateEvent> getOnFailed() { 448 return eventHelper == null ? null : eventHelper.getOnFailed(); 449 } 450 451 /** 452 * The onFailed event handler is called whenever the Task state 453 * transitions to the FAILED state. 454 * 455 * @param value the event handler, can be null to clear it 456 */ 457 public final void setOnFailed(EventHandler<WorkerStateEvent> value) { 458 getEventHelper().setOnFailed(value); 459 } 460 461 /** 462 * A protected convenience method for subclasses, called whenever the 463 * state of the Task has transitioned to the FAILED state. 464 * This method is invoked after any listeners of the state property 465 * and after the Task has been fully transitioned to the new state. 466 */ 467 protected void failed() { } 468 469 /** 470 * A reference to the last task that was executed. I need this reference so that in the 471 * restart method I can cancel the currently running task, and so the cancel method 472 * can cancel the currently running task. 473 */ 474 private Task<V> task; 475 476 /** 477 * Create a new Service. 478 */ 479 protected Service() { 480 // Add a listener to the state, such that we can fire the correct event 481 // notifications whenever the state of the Service has changed. 482 state.addListener(new ChangeListener<State>() { 483 @Override public void changed(ObservableValue<? extends State> observableValue, 484 State oldState, State newState) { 485 // Invoke the event handlers, and then call the protected methods. 486 switch (state.get()) { 487 case CANCELLED: 488 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_CANCELLED)); 489 cancelled(); 490 break; 491 case FAILED: 492 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_FAILED)); 493 failed(); 494 break; 495 case READY: 496 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_READY)); 497 ready(); 498 break; 499 case RUNNING: 500 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_RUNNING)); 501 running(); 502 break; 503 case SCHEDULED: 504 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_SCHEDULED)); 505 scheduled(); 506 break; 507 case SUCCEEDED: 508 fireEvent(new WorkerStateEvent(Service.this, WORKER_STATE_SUCCEEDED)); 509 succeeded(); 510 break; 511 default: throw new AssertionError("Should be unreachable"); 512 } 513 } 514 }); 515 } 516 517 @Override public final boolean cancel() { 518 checkThread(); 519 if (task == null) { 520 if (state.get() == State.CANCELLED || state.get() == State.SUCCEEDED) { 521 return false; 522 } 523 state.set(State.CANCELLED); 524 return true; 525 } else { 526 return task.cancel(true); 527 } 528 } 529 530 /** 531 * Cancels any currently running Task, if any, and restarts this Service. The state 532 * will be reset to READY prior to execution. This method should only be called on 533 * the FX application thread. 534 */ 535 public void restart() { 536 checkThread(); 537 538 // Cancel the current task, if there is one 539 if (task != null) { 540 task.cancel(); 541 task = null; 542 543 // RT-20880: IllegalStateException thrown from Service#restart() 544 // The problem is that the reset method explodes if the state 545 // is SCHEDULED or RUNNING. Although we have cancelled the 546 // task above, it is possible that cancelling does not change 547 // state to the CANCELLED state. However we still need to 548 // succeed in resetting. I believe that although the old task is 549 // still running away, everything is about to be unbound so 550 // we really can just let the old task run and create a new 551 // task and the Service will be none the wiser. 552 state.unbind(); 553 state.setValue(State.CANCELLED); 554 } 555 556 // Reset 557 reset(); 558 559 // Start the thing up again. 560 start(); 561 } 562 563 /** 564 * Resets the Service. May only be called while in one of the finish states, 565 * that is, SUCCEEDED, FAILED, or CANCELLED, or when READY. This method should 566 * only be called on the FX application thread. 567 */ 568 public void reset() { 569 checkThread(); 570 final State s = getState(); 571 if (s == State.SCHEDULED || s == State.RUNNING) { 572 throw new IllegalStateException(); 573 } 574 575 task = null; 576 state.unbind(); 577 state.set(State.READY); 578 value.unbind(); 579 value.set(null); 580 exception.unbind(); 581 exception.set(null); 582 workDone.unbind(); 583 workDone.set(-1); 584 totalWorkToBeDone.unbind(); 585 totalWorkToBeDone.set(-1); 586 progress.unbind(); 587 progress.set(-1); 588 running.unbind(); 589 running.set(false); 590 message.unbind(); 591 message.set(""); 592 title.unbind(); 593 title.set(""); 594 } 595 596 /** 597 * Starts this Service. The Service must be in the READY state to succeed in this call. 598 * This method should only be called on the FX application thread. 599 */ 600 public void start() { 601 checkThread(); 602 603 if (getState() != State.READY) { 604 throw new IllegalStateException( 605 "Can only start a Service in the READY state. Was in state " + getState()); 606 } 607 608 // Create the task 609 task = createTask(); 610 611 // Wire up all the properties so they use this task 612 state.bind(task.stateProperty()); 613 value.bind(task.valueProperty()); 614 exception.bind(task.exceptionProperty()); 615 workDone.bind(task.workDoneProperty()); 616 totalWorkToBeDone.bind(task.totalWorkProperty()); 617 progress.bind(task.progressProperty()); 618 running.bind(task.runningProperty()); 619 message.bind(task.messageProperty()); 620 title.bind(task.titleProperty()); 621 622 // Advance the task to the "SCHEDULED" state 623 task.setState(State.SCHEDULED); 624 625 // Start the task 626 executeTask(task); 627 } 628 629 /** 630 * <p> 631 * Uses the <code>executor</code> defined on this Service to execute the 632 * given task. If the <code>executor</code> is null, then a default 633 * executor is used which will create a new daemon thread on which to 634 * execute this task. 635 * </p> 636 * <p> 637 * This method is intended only to be called by the Service 638 * implementation. 639 * </p> 640 * @param task a non-null task to execute 641 */ 642 protected void executeTask(Task<V> task) { 643 Executor e = getExecutor(); 644 if (e != null) { 645 e.execute(task); 646 } else { 647 EXECUTOR.execute(task); 648 } 649 } 650 651 /*************************************************************************** 652 * * 653 * Event Dispatch * 654 * * 655 **************************************************************************/ 656 657 private EventHelper eventHelper = null; 658 private EventHelper getEventHelper() { 659 if (eventHelper == null) { 660 eventHelper = new EventHelper(this); 661 } 662 return eventHelper; 663 } 664 665 /** 666 * Registers an event handler to this task. Any event filters are first 667 * processed, then the specified onFoo event handlers, and finally any 668 * event handlers registered by this method. As with other events 669 * in the scene graph, if an event is consumed, it will not continue 670 * dispatching. 671 * 672 * @param <T> the specific event class of the handler 673 * @param eventType the type of the events to receive by the handler 674 * @param eventHandler the handler to register 675 * @throws NullPointerException if the event type or handler is null 676 */ 677 public final <T extends Event> void addEventHandler( 678 final EventType<T> eventType, 679 final EventHandler<? super T> eventHandler) { 680 getEventHelper().addEventHandler(eventType, eventHandler); 681 } 682 683 /** 684 * Unregisters a previously registered event handler from this task. One 685 * handler might have been registered for different event types, so the 686 * caller needs to specify the particular event type from which to 687 * unregister the handler. 688 * 689 * @param <T> the specific event class of the handler 690 * @param eventType the event type from which to unregister 691 * @param eventHandler the handler to unregister 692 * @throws NullPointerException if the event type or handler is null 693 */ 694 public final <T extends Event> void removeEventHandler( 695 final EventType<T> eventType, 696 final EventHandler<? super T> eventHandler) { 697 getEventHelper().removeEventHandler(eventType, eventHandler); 698 } 699 700 /** 701 * Registers an event filter to this task. Registered event filters get 702 * an event before any associated event handlers. 703 * 704 * @param <T> the specific event class of the filter 705 * @param eventType the type of the events to receive by the filter 706 * @param eventFilter the filter to register 707 * @throws NullPointerException if the event type or filter is null 708 */ 709 public final <T extends Event> void addEventFilter( 710 final EventType<T> eventType, 711 final EventHandler<? super T> eventFilter) { 712 getEventHelper().addEventFilter(eventType, eventFilter); 713 } 714 715 /** 716 * Unregisters a previously registered event filter from this task. One 717 * filter might have been registered for different event types, so the 718 * caller needs to specify the particular event type from which to 719 * unregister the filter. 720 * 721 * @param <T> the specific event class of the filter 722 * @param eventType the event type from which to unregister 723 * @param eventFilter the filter to unregister 724 * @throws NullPointerException if the event type or filter is null 725 */ 726 public final <T extends Event> void removeEventFilter( 727 final EventType<T> eventType, 728 final EventHandler<? super T> eventFilter) { 729 getEventHelper().removeEventFilter(eventType, eventFilter); 730 } 731 732 /** 733 * Sets the handler to use for this event type. There can only be one such 734 * handler specified at a time. This handler is guaranteed to be called 735 * first. This is used for registering the user-defined onFoo event 736 * handlers. 737 * 738 * @param <T> the specific event class of the handler 739 * @param eventType the event type to associate with the given eventHandler 740 * @param eventHandler the handler to register, or null to unregister 741 * @throws NullPointerException if the event type is null 742 */ 743 protected final <T extends Event> void setEventHandler( 744 final EventType<T> eventType, 745 final EventHandler<? super T> eventHandler) { 746 getEventHelper().setEventHandler(eventType, eventHandler); 747 } 748 749 /** 750 * Fires the specified event. Any event filter encountered will 751 * be notified and can consume the event. If not consumed by the filters, 752 * the event handlers on this task are notified. If these don't consume the 753 * event either, then all event handlers are called and can consume the 754 * event. 755 * <p> 756 * This method must be called on the FX user thread. 757 * 758 * @param event the event to fire 759 */ 760 protected final void fireEvent(Event event) { 761 checkThread(); 762 getEventHelper().fireEvent(event); 763 } 764 765 @Override 766 public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { 767 return getEventHelper().buildEventDispatchChain(tail); 768 } 769 770 /** 771 * Invoked after the Service is started on the JavaFX Application Thread. 772 * Implementations should save off any state into final variables prior to 773 * creating the Task, since accessing properties defined on the Service 774 * within the background thread code of the Task will result in exceptions. 775 * 776 * For example: 777 * <pre><code> 778 * protected Task createTask() { 779 * final String url = myService.getUrl(); 780 * return new Task<String>() { 781 * protected String call() { 782 * URL u = new URL("http://www.oracle.com"); 783 * BufferedReader in = new BufferedReader( 784 * new InputStreamReader(u.openStream())); 785 * String result = in.readLine(); 786 * in.close(); 787 * return result; 788 * } 789 * } 790 * } 791 * </code></pre> 792 * 793 * <p> 794 * If the Task is a pre-defined class (as opposed to being an 795 * anonymous class), and if it followed the recommended best-practice, 796 * then there is no need to save off state prior to constructing 797 * the Task since its state is completely provided in its constructor. 798 * </p> 799 * 800 * <pre><code> 801 * protected Task createTask() { 802 * // This is safe because getUrl is called on the FX Application 803 * // Thread and the FirstLineReaderTasks stores it as an 804 * // immutable property 805 * return new FirstLineReaderTask(myService.getUrl()); 806 * } 807 * </code></pre> 808 * @return the Task to execute 809 */ 810 protected abstract Task<V> createTask(); 811 812 void checkThread() { 813 if (!isFxApplicationThread()) { 814 throw new IllegalStateException("Service must only be used from the FX Application Thread"); 815 } 816 } 817 818 // This method exists for the sake of testing, so I can subclass and override 819 // this method in the test and not actually use Platform.runLater. 820 void runLater(Runnable r) { 821 Platform.runLater(r); 822 } 823 824 // This method exists for the sake of testing, so I can subclass and override 825 // this method in the test and not actually use Platform.isFxApplicationThread. 826 boolean isFxApplicationThread() { 827 return Platform.isFxApplicationThread(); 828 } 829}