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&lt;String&gt; {
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&ltString&gt;() {
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&lt;String&gt;() {
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}