/* * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.concurrent; import java.security.AccessController; import java.security.Permission; import java.security.PrivilegedAction; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyStringProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.event.Event; import javafx.event.EventDispatchChain; import javafx.event.EventHandler; import javafx.event.EventTarget; import javafx.event.EventType; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicReference; import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_CANCELLED; import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_FAILED; import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_RUNNING; import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SCHEDULED; import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SUCCEEDED; /** *
* A fully observable implementation of a {@link FutureTask}. {@code Task} exposes * additional state and observable properties useful for programming asynchronous * tasks in JavaFX, as defined in the {@link Worker} interface. An implementation * of Task must override the {@link javafx.concurrent.Task#call()} method. This method * is invoked on the background thread. Any state which is used in this method * must be safe to read and write from a background thread. For example, manipulating * a live scene graph from this method is unsafe and will result in runtime * exceptions. *
** Tasks are flexible and extremely useful for the encapsulation of "work". Because * {@link Service} is designed to execute a Task, any Tasks defined by the application * or library code can easily be used with a Service. Likewise, since Task extends * from FutureTask, it is very easy and natural to use a Task with the java concurrency * {@link java.util.concurrent.Executor} API. Since a Task is Runnable, you * can also call it directly (by invoking the {@link javafx.concurrent.Task#run()} method) * from another background thread. This allows for composition of work, or pass it to * a new Thread constructed and executed manually. Finally, since you can * manually create a new Thread, passing it a Runnable, it is possible to use * the following idiom: *
*
* Thread th = new Thread(task);
* th.setDaemon(true);
* th.start();
*
* * Note that this code sets the daemon flag of the Thread to true. If you * want a background thread to prevent the VM from existing after the last * stage is closed, then you would want daemon to be false. However, if * you want the background threads to simply terminate after all the * stages are closed, then you must set daemon to true. *
*
* Although {@link java.util.concurrent.ExecutorService} defines several methods which
* take a Runnable, you should generally limit yourself to using the execute
* method inherited from {@link java.util.concurrent.Executor}.
*
* As with FutureTask, a Task is a one-shot class and cannot be reused. See {@link Service} * for a reusable {@link Worker}. *
** Because the Task is designed for use with JavaFX GUI applications, it ensures * that every change to its public properties, as well as change notifications * for state, errors, and for event handlers, all occur on the main JavaFX application * thread. Accessing these properties from a background thread (including the * {@link #call()} method) will result in runtime exceptions being raised. The only exception * to this, is when initially configuring a Task, which may safely be done * from any thread. However, once the Task has been initialized and * started, it may only thereafter be used from the FX thread (except for those methods clearly * marked as being appropriate for the subclass to invoke from the background thread). *
** It is strongly encouraged that all Tasks be initialized with * immutable state upon which the Task will operate. This should be done by providing * a Task constructor which takes the parameters necessary for execution of the Task. * Immutable state makes it easy and safe to use from any thread and ensures * correctness in the presence of multiple threads. *
*
* In Java there is no reliable way to "kill" a thread in process. However,
* when cancel
is called on a Task, it is important that
* the Task stop processing. A "run-away" Task might continue processing
* and updating the message, text, and progress properties even after the
* Task has been cancelled! In Java, cancelling a Task is a cooperative
* endeavor. The user of the Task will request that it be cancelled, and
* the author of the Task must check whether is has been cancelled within
* the body of the call
method. There are two ways this can
* be done. First, the Task author may check the isCancelled method,
* inherited from FutureTask
, to see whether the Task has
* been cancelled. Second, if the Task implementation makes use of any
* blocking calls (such as NIO InterruptibleChannels or Thread.sleep) and
* the task is cancelled while in such a blocking call, an
* InterruptedException is thrown. Task implementations which have blocking
* calls should recognize that an interrupted thread may be the signal for
* a cancelled task and should double check the isCancelled method to ensure
* that the InterruptedException was thrown due to the cancellation of the
* Task.
*
* The following set of examples demonstrate some of the most common uses of * Tasks. *
* ** The first example is a simple loop that does nothing particularly useful, * but demonstrates the fundamental aspects of writing a Task correctly. This * example will simply loop and print to standard out on each loop iteration. * When it completes, it returns the number of times it iterated. *
* *
* Task<Integer> task = new Task<Integer>() {
* @Override protected Integer call() throws Exception {
* int iterations;
* for (iterations = 0; iterations < 100000; iterations++) {
* if (isCancelled()) {
* break;
* }
* System.out.println("Iteration " + iterations);
* }
* return iterations;
* }
* };
*
*
*
* First, we define what type of value is returned from this Task. In this
* case, we want to return the number of times we iterated, so we will
* specify the Task to be of type Integer by using generics. Then, within
* the implementation of the call
method, we iterate from
* 0 to 100000. On each iteration, we check to see whether this Task has
* been cancelled. If it has been, then we break out of the loop and return
* the number of times we iterated. Otherwise a message is printed to
* the console and the iteration count increased and we continue looping.
*
* Checking for isCancelled() in the loop body is critical, otherwise the
* developer may cancel the task, but the task will continue running
* and updating both the progress and returning the incorrect result
* from the end of the call
method. A correct implementation
* of a Task will always check for cancellation.
*
* Similar to the previous example, except this time we will modify the * progress of the Task in each iteration. Note that we have a choice * to make in the case of cancellation. Do we want to set the progress back * to -1 (indeterminate) when the Task is cancelled, or do we want to leave * the progress where it was at? In this case, let's leave the progress alone * and only update the message on cancellation, though updating the * progress after cancellation is a perfectly valid choice. *
* *
* Task<Integer> task = new Task<Integer>() {
* @Override protected Integer call() throws Exception {
* int iterations;
* for (iterations = 0; iterations < 10000000; iterations++) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, 10000000);
* }
* return iterations;
* }
* };
*
*
* * As before, within the for loop we check whether the Task has been * cancelled. If it has been cancelled, we will update the Task's * message to indicate that it has been cancelled, and then break as * before. If the Task has not been cancelled, then we will update its * message to indicate the current iteration and then update the * progress to indicate the current progress. *
* ** This example adds to the previous examples a blocking call. Because a * blocking call may thrown an InterruptedException, and because an * InterruptedException may occur as a result of the Task being cancelled, * we need to be sure to handle the InterruptedException and check on the * cancel state. *
* *
* Task<Integer> task = new Task<Integer>() {
* @Override protected Integer call() throws Exception {
* int iterations;
* for (iterations = 0; iterations < 1000; iterations++) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, 1000);
*
* // Now block the thread for a short time, but be sure
* // to check the interrupted exception for cancellation!
* try {
* Thread.sleep(100);
* } catch (InterruptedException interrupted) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* }
* }
* return iterations;
* }
* };
*
*
*
* Here we have added to the body of the loop a Thread.sleep
* call. Since this is a blocking call, I have to handle the potential
* InterruptedException. Within the catch block, I will check whether
* the Task has been cancelled, and if so, update the message accordingly
* and break out of the loop.
*
* Most Tasks require some parameters in order to do useful work. For
* example, a DeleteRecordTask needs the object or primary key to delete
* from the database. A ReadFileTask needs the URI of the file to be read.
* Because Tasks operate on a background thread, care must be taken to
* make sure the body of the call
method does not read or
* modify any shared state. There are two techniques most useful for
* doing this: using final variables, and passing variables to a Task
* during construction.
*
* When using a Task as an anonymous class, the most natural way to pass * parameters to the Task is by using final variables. In this example, * we pass to the Task the total number of times the Task should iterate. *
* *
* final int totalIterations = 9000000;
* Task<Integer> task = new Task<Integer>() {
* @Override protected Integer call() throws Exception {
* int iterations;
* for (iterations = 0; iterations < totalIterations; iterations++) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, totalIterations);
* }
* return iterations;
* }
* };
*
*
*
* Since totalIterations
is final, the call
* method can safely read it and refer to it from a background thread.
*
* When writing Task libraries (as opposed to specific-use implementations), * we need to use a different technique. In this case, I will create an * IteratingTask which performs the same work as above. This time, since * the IteratingTask is defined in its own file, it will need to have * parameters passed to it in its constructor. These parameters are * assigned to final variables. *
* *
* public class IteratingTask extends Task<Integer> {
* private final int totalIterations;
*
* public IteratingTask(int totalIterations) {
* this.totalIterations = totalIterations;
* }
*
* @Override protected Integer call() throws Exception {
* int iterations = 0;
* for (iterations = 0; iterations < totalIterations; iterations++) {
* if (isCancelled()) {
* updateMessage("Cancelled");
* break;
* }
* updateMessage("Iteration " + iterations);
* updateProgress(iterations, totalIterations);
* }
* return iterations;
* }
* }
*
*
* And then when used:
* *
* IteratingTask task = new IteratingTask(8000000);
*
*
* In this way, parameters are passed to the IteratingTask in a safe
* manner, and again, are final. Thus, the call
method can
* safely read this state from a background thread.
WARNING: Do not pass mutable state to a Task and then operate on it * from a background thread. Doing so may introduce race conditions. In * particular, suppose you had a SaveCustomerTask which took a Customer * in its constructor. Although the SaveCustomerTask may have a final * reference to the Customer, if the Customer object is mutable, then it * is possible that both the SaveCustomerTask and some other application code * will be reading or modifying the state of the Customer from different * threads. Be very careful in such cases, that while a mutable object such * as this Customer is being used from a background thread, that it is * not being used also from another thread. In particular, if the background * thread is reading data from the database and updating the Customer object, * and the Customer object is bound to scene graph nodes (such as UI * controls), then there could be a violation of threading rules! For such * cases, modify the Customer object from the FX Application Thread rather * than from the background thread.
* *
* public class UpdateCustomerTask extends Task<Customer> {
* private final Customer customer;
*
* public UpdateCustomerTask(Customer customer) {
* this.customer = customer;
* }
*
* @Override protected Customer call() throws Exception {
* // pseudo-code:
* // query the database
* // read the values
*
* // Now update the customer
* Platform.runLater(new Runnable() {
* @Override public void run() {
* customer.setFirstName(rs.getString("FirstName"));
* // etc
* }
* });
*
* return customer;
* }
* }
*
*
* * Many, if not most, Tasks should return a value upon completion. For * CRUD Tasks, one would expect that a "Create" Task would return the newly * created object or primary key, a "Read" Task would return the read * object, an "Update" task would return the number of records updated, * and a "Delete" task would return the number of records deleted. *
* *
* However sometimes there just isn't anything truly useful to return.
* For example, I might have a Task which writes to a file. Task has built
* into it a mechanism for indicating whether it has succeeded or failed
* along with the number of bytes written (the progress), and thus there is
* nothing really for me to return. In such a case, you can use the Void
* type. This is a special type in the Java language which can only be
* assigned the value of null
. You would use it as follows:
*
* final String filePath = "/foo.txt";
* final String contents = "Some contents";
* Task<Void> task = new Task<Void>() {
* @Override protected Void call() throws Exception {
* File file = new File(filePath);
* FileOutputStream out = new FileOutputStream(file);
* // ... and other code to write the contents ...
*
* // Return null at the end of a Task of type Void
* return null;
* }
* };
*
*
* Because the ListView, TableView, and other UI controls and scene graph
* nodes make use of ObservableList, it is common to want to create and return
* an ObservableList from a Task. When you do not care to display intermediate
* values, the easiest way to correctly write such a Task is simply to
* construct an ObservableList within the call
method, and then
* return it at the conclusion of the Task.
* Task<ObservableList<Rectangle>> task = new Task<ObservableList<Rectangle>>() {
* @Override protected ObservableList<Rectangle> call() throws Exception {
* updateMessage("Creating Rectangles");
* ObservableList<Rectangle> results = FXCollections.observableArrayList();
* for (int i=0; i<100; i++) {
* if (isCancelled()) break;
* Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* results.add(r);
* updateProgress(i, 100);
* }
* return results;
* }
* };
*
*
* In the above example, we are going to create 100 rectangles and return
* them from this task. An ObservableList is created within the
* call
method, populated, and then returned.
Sometimes you want to create a Task which will return partial results. * Perhaps you are building a complex scene graph and want to show the * scene graph as it is being constructed. Or perhaps you are reading a large * amount of data over the network and want to display the entries in a * TableView as the data is arriving. In such cases, there is some shared state * available both to the FX Application Thread and the background thread. * Great care must be taken to never update shared state from any * thread other than the FX Application Thread.
* *The easiest way to do this is to take advantage of the {@link #updateValue(Object)} method. * This method may be called repeatedly from the background thread. Updates are coalesced to * prevent saturation of the FX event queue. This means you can call it as frequently as * you like from the background thread but only the most recent set is ultimately set.
* *
* Task<Long> task = new Task<Long>() {
* @Override protected Long call() throws Exception {
* long a=0;
* long b=1;
* for (long i = 0; i < Long.MAX_VALUE; i++){
* updateValue(a);
* a += b;
* b = a - b;
* }
* return a;
* }
* };
*
*
* Another way to do this is to expose a new property on the Task
* which will represent the partial result. Then make sure to use
* Platform.runLater
when updating the partial result.
* Task<Long> task = new Task<Long>() {
* @Override protected Long call() throws Exception {
* long a=0;
* long b=1;
* for (long i = 0; i < Long.MAX_VALUE; i++){
* final long v = a;
* Platform.runLater(new Runnable() {
* @Override public void run() {
* updateValue(v);
* }
* }
* a += b;
* b = a - b;
* }
* return a;
* }
* };
*
*
* Suppose instead of updating a single value, you want to populate an ObservableList
* with results as they are obtained. One approach is to expose a new property on the Task
* which will represent the partial result. Then make sure to use
* Platform.runLater
when adding new items to the partial
* result.
* public class PartialResultsTask extends Task<ObservableList<Rectangle>> {
* // Uses Java 7 diamond operator
* private ReadOnlyObjectWrapper<ObservableList<Rectangle>> partialResults =
* new ReadOnlyObjectWrapper<>(this, "partialResults",
* FXCollections.observableArrayList(new ArrayList<Rectangle>()));
*
* public final ObservableList<Rectangle> getPartialResults() { return partialResults.get(); }
* public final ReadOnlyObjectProperty<ObservableList<Rectangle>> partialResultsProperty() {
* return partialResults.getReadOnlyProperty();
* }
*
* @Override protected ObservableList<Rectangle> call() throws Exception {
* updateMessage("Creating Rectangles...");
* for (int i=0; i<100; i++) {
* if (isCancelled()) break;
* final Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* Platform.runLater(new Runnable() {
* @Override public void run() {
* partialResults.get().add(r);
* }
* });
* updateProgress(i, 100);
* }
* return partialResults.get();
* }
* }
*
*
* Generally, Tasks should not interact directly with the UI. Doing so
* creates a tight coupling between a specific Task implementation and a
* specific part of your UI. However, when you do want to create such a
* coupling, you must ensure that you use Platform.runLater
* so that any modifications of the scene graph occur on the
* FX Application Thread.
* final Group group = new Group();
* Task<Void> task = new Task<Void>() {
* @Override protected Void call() throws Exception {
* for (int i=0; i<100; i++) {
* if (isCancelled()) break;
* final Rectangle r = new Rectangle(10, 10);
* r.setX(10 * i);
* Platform.runLater(new Runnable() {
* @Override public void run() {
* group.getChildren().add(r);
* }
* });
* }
* return null;
* }
* };
*
*
* Sometimes you may want to write a Task which updates its progress, * message, text, or in some other way reacts whenever a state change * happens on the Task. For example, you may want to change the status * message on the Task on Failure, Success, Running, or Cancelled state changes. *
*
* Task<Integer> task = new Task<Integer>() {
* @Override protected Integer call() throws Exception {
* int iterations = 0;
* for (iterations = 0; iterations < 100000; iterations++) {
* if (isCancelled()) {
* break;
* }
* System.out.println("Iteration " + iterations);
* }
* return iterations;
* }
*
* @Override protected void succeeded() {
* super.succeeded();
* updateMessage("Done!");
* }
*
* @Override protected void cancelled() {
* super.cancelled();
* updateMessage("Cancelled!");
* }
*
* @Override protected void failed() {
* super.failed();
* updateMessage("Failed!");
* }
* };
*
* @since JavaFX 2.0
*/
public abstract class TaskworkDone
, totalWork
,
* and progress
properties. Calls to updateProgress
* are coalesced and run later on the FX application thread, and calls
* to updateProgress, even from the FX Application thread, may not
* necessarily result in immediate updates to these properties, and
* intermediate workDone values may be coalesced to save on event
* notifications. max
becomes the new value for
* totalWork
.
* * This method is safe to be called from any thread. *
* * @param workDone A value from Long.MIN_VALUE up to max. If the value is greater * than max, then it will be clamped at max. * If the value passed is negative then the resulting percent * done will be -1 (thus, indeterminate). * @param max A value from Long.MIN_VALUE to Long.MAX_VALUE. * @see #updateProgress(double, double) */ protected void updateProgress(long workDone, long max) { updateProgress((double)workDone, (double)max); } /** * Updates theworkDone
, totalWork
,
* and progress
properties. Calls to updateProgress
* are coalesced and run later on the FX application thread, and calls
* to updateProgress, even from the FX Application thread, may not
* necessarily result in immediate updates to these properties, and
* intermediate workDone values may be coalesced to save on event
* notifications. max
becomes the new value for
* totalWork
.
* * This method is safe to be called from any thread. *
* * @param workDone A value from Double.MIN_VALUE up to max. If the value is greater * than max, then it will be clamped at max. * If the value passed is negative, or Infinity, or NaN, * then the resulting percentDone will be -1 (thus, indeterminate). * @param max A value from Double.MIN_VALUE to Double.MAX_VALUE. Infinity and NaN are treated as -1. * @since JavaFX 2.2 */ protected void updateProgress(double workDone, double max) { // Adjust Infinity / NaN to be -1 for both workDone and max. if (Double.isInfinite(workDone) || Double.isNaN(workDone)) { workDone = -1; } if (Double.isInfinite(max) || Double.isNaN(max)) { max = -1; } if (workDone < 0) { workDone = -1; } if (max < 0) { max = -1; } // Clamp the workDone if necessary so as not to exceed max if (workDone > max) { workDone = max; } if (isFxApplicationThread()) { _updateProgress(workDone, max); } else if (progressUpdate.getAndSet(new ProgressUpdate(workDone, max)) == null) { runLater(() -> { final ProgressUpdate update = progressUpdate.getAndSet(null); _updateProgress(update.workDone, update.totalWork); }); } } private void _updateProgress(double workDone, double max) { setTotalWork(max); setWorkDone(workDone); if (workDone == -1) { setProgress(-1); } else { setProgress(workDone / max); } } /** * Updates themessage
property. Calls to updateMessage
* are coalesced and run later on the FX application thread, so calls
* to updateMessage, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate message values may be coalesced to save on event
* notifications.
* * This method is safe to be called from any thread. *
* * @param message the new message */ protected void updateMessage(String message) { if (isFxApplicationThread()) { this.message.set(message); } else { // As with the workDone, it might be that the background thread // will update this message quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. if (messageUpdate.getAndSet(message) == null) { runLater(new Runnable() { @Override public void run() { final String message = messageUpdate.getAndSet(null); Task.this.message.set(message); } }); } } } /** * Updates thetitle
property. Calls to updateTitle
* are coalesced and run later on the FX application thread, so calls
* to updateTitle, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate title values may be coalesced to save on event
* notifications.
* * This method is safe to be called from any thread. *
* * @param title the new title */ protected void updateTitle(String title) { if (isFxApplicationThread()) { this.title.set(title); } else { // As with the workDone, it might be that the background thread // will update this title quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. if (titleUpdate.getAndSet(title) == null) { runLater(new Runnable() { @Override public void run() { final String title = titleUpdate.getAndSet(null); Task.this.title.set(title); } }); } } } /** * Updates thevalue
property. Calls to updateValue
* are coalesced and run later on the FX application thread, so calls
* to updateValue, even from the FX Application thread, may not
* necessarily result in immediate updates to this property, and
* intermediate values may be coalesced to save on event
* notifications.
* * This method is safe to be called from any thread. *
* * @param value the new value * @since JavaFX 8.0 */ protected void updateValue(V value) { if (isFxApplicationThread()) { this.value.set(value); } else { // As with the workDone, it might be that the background thread // will update this value quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. if (valueUpdate.getAndSet(value) == null) { runLater(() -> Task.this.value.set(valueUpdate.getAndSet(null))); } } } /* * IMPLEMENTATION */ private void checkThread() { if (started && !isFxApplicationThread()) { throw new IllegalStateException("Task must only be used from the FX Application Thread"); } } // This method exists for the sake of testing, so I can subclass and override // this method in the test and not actually use Platform.runLater. void runLater(Runnable r) { Platform.runLater(r); } // This method exists for the sake of testing, so I can subclass and override // this method in the test and not actually use Platform.isFxApplicationThread. boolean isFxApplicationThread() { return Platform.isFxApplicationThread(); } /*************************************************************************** * * * Event Dispatch * * * **************************************************************************/ private EventHelper eventHelper = null; private EventHelper getEventHelper() { if (eventHelper == null) { eventHelper = new EventHelper(this); } return eventHelper; } /** * Registers an event handler to this task. Any event filters are first * processed, then the specified onFoo event handlers, and finally any * event handlers registered by this method. As with other events * in the scene graph, if an event is consumed, it will not continue * dispatching. * * @param
* This method must be called on the FX user thread.
*
* @param event the event to fire
* @since JavaFX 2.1
*/
public final void fireEvent(Event event) {
checkThread();
getEventHelper().fireEvent(event);
}
@Override
public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
checkThread();
return getEventHelper().buildEventDispatchChain(tail);
}
/**
* A struct like class that contains the last workDone update information.
* What we do when updateProgress is called, is we create a new ProgressUpdate
* object and store it. If it was null, then we fire off a new Runnable
* using RunLater, which will eventually read the latest and set it to null
* atomically. If it was not null, then we simply update it.
*/
private static final class ProgressUpdate {
private final double workDone;
private final double totalWork;
private ProgressUpdate(double p, double m) {
this.workDone = p;
this.totalWork = m;
}
}
/**
* TaskCallable actually implements the Callable contract as defined for
* the FutureTask class, and is necessary so as to allow us to intercept
* the call() operation to update state on the Task as appropriate.
* @param