changeset: 228:160b675b10e3 tag: partial-task tag: qbase tag: qtip tag: tip user: rbair date: Mon Jan 09 14:21:59 2012 -0800 summary: [mq]: partial-task diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/src/javafx/concurrent/ObservableListTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/src/javafx/concurrent/ObservableListTask.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,335 @@ +/* + * Copyright (c) 2011, 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.util.*; +import java.util.concurrent.Semaphore; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * The ObservableListTask is a concrete fully observable implementation of + * FutureTask which has special support for handling a result of type + * ObservableList. An ObservableListTask creates an ObservableList in its + * constructor which is never replaced. Within the call method, + * the implementation of ObservableListTask will call publish + * one or more times to add items to the observable list in a thread-safe + * manner. + * + *

This implementation is tuned for high performance by limiting the number + * of events placed on the event queue by coalescing items added via the + * publish methods. Calling publish with coarse or + * fine granularity should both perform well, allowing the implementation + * of the {@link #call()} method to remain simple.

+ * + *

If you desire to change the ObservableList (such as removing items or + * checking previously added items), you can do so with some complication by + * calling Platform.runLater and jumping on the FX application + * thread. For most cases this should not be necessary and is discouraged.

+ * + *

The ObservableListTask allows you to specify in one of its constructors + * a custom ObservableList to use. This instance is immutable, meaning it will + * be set on the ObservableListTask as its value during construction and will + * not change thereafter.

+ * + *

Examples

+ *

+ * The following set of examples demonstrate some of the most common uses of + * ObservableListTasks. + *

+ * + *

A Simple Loop Constructing Rectangles

+ * + *

This first example constructs an ObservableList of Rectangles. Note that, + * as with {@link Task}, cancellation of an ObservableListTask is a cooperative + * affair. The ObservableListTask implementation must check the isCancelled + * flag to determine whether it should terminate work.

+ * + *

+ *     ObservableListTask<Rectangle> task = new ObservableListTask<Rectangle>() {
+ *         @Override protected void call() throws Exception {
+ *             for (int i=0; i<900; i++) {
+ *                 if (isCancelled()) {
+ *                     break;
+ *                 }
+ *                 Rectangle r = new Rectangle(10, 10);
+ *                 r.setFill(Color.color(Math.random(), Math.random(), Math.random()));
+ *                 publish(r);
+ *                 Thread.sleep(20);
+ *                 updateProgress(i, 900);
+ *             }
+ *             return iterations;
+ *         }
+ *     };
+ * 
+ * + *

In this example, we simply iterate 900 times creating a single + * Rectangle in each iteration. We first check to see if the user has + * cancelled, and if so, we will terminate the loop. Otherwise we will create + * a Rectangle, assign it some random color, and then publish the Rectangle. + * The Thread.sleep call in this case is not required, but useful + * for slowing the task down for the sake of example.

+ * + *

Populating Items in a ListView

+ * + *

There are several ways that you can populate an ObservableList from an + * ObservableListTask and use it as the items on a ListView. You could either + * create the task and then get the value and set it as the items + * on the ListView, or you could pass the listView.getItems() + * to the ObservableListTask to be populated.

+ * + *

+ *     ListView<String> listView = new ListView<String>();
+ *     ObservableListTask<String> task = new ObservableListTask<String>(listView.getItems()) {
+ *         @Override protected void call() throws Exception {
+ *             for (int i=0; i<900; i++) {
+ *                 if (isCancelled()) {
+ *                     break;
+ *                 }
+ *                 publish("Item " + i);
+ *                 updateProgress(i, 900);
+ *             }
+ *             return iterations;
+ *         }
+ *     };
+ * 
+ * + *

Wiring an ObservableListTask to a FlowPane

+ * + *

In this example we will take the code from the previous example and + * wire it up to a FlowPane, and add animations. This is a simple example + * demonstrating a thread-safe way to construct Rectangles and animations on + * a background thread, and wire the ObservableList to the FlowPane children.

+ * + *

+ *     ObservableListTask<Rectangle> task = new ObservableListTask<Rectangle>() {
+ *         @Override protected void call() throws Exception {
+ *             for (int i=0; i<900; i++) {
+ *                 if (isCancelled()) {
+ *                     break;
+ *                 }
+ *                 Rectangle r = new Rectangle(10, 10);
+ *                 r.setFill(Color.color(Math.random(), Math.random(), Math.random()));
+ *                 r.setOpacity(0);
+ *                 FadeTransition tx = new FadeTransition(Duration.seconds(1));
+ *                 tx.setToValue(1.0);
+ *                 tx.setNode(r);
+ *                 tx.play();
+ *                 publish(r);
+ *                 Thread.sleep(20);
+ *                 updateProgress(i, 900);
+ *             }
+ *             return iterations;
+ *         }
+ *     };
+ *     FlowPane pane = new FlowPane(7, 7);
+ *     task.getValue().addListener(new ListChangeListener<Rectangle>() {
+ *         @Override
+ *         public void onChanged(Change<? extends Rectangle> change) {
+ *             while (change.next()) {
+ *                 if (change.wasAdded()) {
+ *                     group.getChildren().addAll(change.getAddedSubList());
+ *                 }
+ *             }
+ *         }
+ *     });
+ * 
+ */ +public abstract class ObservableListTask extends TaskBase> { + /** + * Used instead of normal Java synchronization, not sure if there is a + * strong benefit to doing so. What I require is the ability to atomically + * check the queue -- if it is null, supply a new queue with the new items; + * and if it is not null then to simply append to the queue. But this must + * be done atomically. I use this semaphore to create that atomic boundary. + */ + private Semaphore semaphore = new Semaphore(1); + + /** + * The queue of items that need to be added. This is added to in a + * background thread, and cleared out on the FX thread. + */ + private LinkedList queue = null; + + /** + * A final reference to the ObservableList for this task. I need this + * reference because this must be returned from doInBackground, but + * I cannot call the getter safely from the background thread. So I + * just store the reference to it here. + */ + private final ObservableList list; + + /** + * Create a new ObservableListTask. This constructor will use a default + * ObservableList as the value for this ObservableListTask. + */ + public ObservableListTask() { + list = FXCollections.observableArrayList(); + initValue(list); + } + + /** + * Create a new ObservableListTask, using the supplied list as the value + * for this task. The supplied list cannot be null. Likewise, it must not + * be an unmodifiable list. + * + *

The supplied ObservableList will become the value of this + * ObservableListTask. It will be populated only on the FX thread. You may + * safely add listeners to this list or bind to this list in order to + * receive notification when items are added to it.

+ * + * @param list The list to use. This cannot be null or a + * NullPointerException will be thrown. It must not be + * unmodifiable, or an exception will be thrown from the + * background thread. + */ + public ObservableListTask(ObservableList list) { + if (list == null) { + throw new NullPointerException("Supplied list cannot be null"); + } + + this.list = list; + initValue(list); + } + + /** + * @inheritDoc + */ + @Override final ObservableList doInBackground() throws Exception { + call(); + return list; + } + + /** + * Invoked when the Task is executed, the call method must be overridden and + * implemented by subclasses. The call method actually performs the + * background thread logic. Only the updateProgress, updateMessage, + * updateTitle, and publish methods of may be called from code + * within this method. Any other interaction with the ObservableListTask + * from the background thread will result in runtime exceptions. + * + *

Implementations of this method must call publish + * with any items to be added to the ObservableList.

+ * + * @throws Exception an unhandled exception which occurred during the + * background operation + */ + protected abstract void call() throws Exception; + + /** + * Adds the given items to the ObservableList. This + * can be done at any time. This method allows ObservableListTask + * implementations to provide intermediate results. + * + *

For example, you may have an ObservableListTask which constructs + * records from a ResultSet. In this case you can add records to the + * ObservableList incrementally from the background thread.

+ * + *

As with the other update methods, Calls to publish + * are coalesced and run later on the FX application thread, so calls + * to publish, 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. However, all items added via this method will + * eventually be added to the ObservableList in the order in which + * they were added. + *

+ * This method is safe to be called from any thread. + *

+ * + * @param items The items to add to the ObservableList + */ + protected void publish(E... items) { + if (items == null) return; + publish(Arrays.asList(items)); + } + + /** + * Adds the given items to the ObservableList. This + * can be done at any time. This method allows ObservableListTask + * implementations to provide intermediate results. + * + *

For example, you may have an ObservableListTask which constructs + * records from a ResultSet. In this case you can add records to the + * ObservableList incrementally from the background thread.

+ * + *

As with the other update methods, Calls to publish + * are coalesced and run later on the FX application thread, so calls + * to publish, 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. However, all items added via this method will + * eventually be added to the ObservableList in the order in which + * they were added. + *

+ * This method is safe to be called from any thread. + *

+ * + * @param items The items to add to the ObservableList + */ + protected void publish(Collection items) { + if (items == null) return; + if (isFxApplicationThread()) { + ObservableList values = getValue(); + if (values != null) { + values.addAll(items); + } + } else { + // I have a List of queued up items. From the background thread, + // I want to just continue adding to the queue. If the queue was + // empty, then I will populate it and fire off a runLater. On + // the FX thread, I will read the queue and set the shared queue + // to null. The tricky part is that I must check the queue to see + // if it is null and set it to be a new queue atomically if it + // was null, but simply add to it if it was not null. For this + // I will use a basic synchronization block. + try { + semaphore.acquire(); + if (queue == null) { + queue = new LinkedList(items); + runLater(new Runnable() { + @Override public void run() { + semaphore.acquireUninterruptibly(); + final Collection items = queue; + queue = null; + semaphore.release(); + ObservableList values = getValue(); + if (values != null) { + values.addAll(items); + } + } + }); + } else { + queue.addAll(items); + } + } catch (InterruptedException ex) { + // The task was probably cancelled. + } finally { + semaphore.release(); + } + } + } +} diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/src/javafx/concurrent/Task.java --- a/javafx-concurrent/src/javafx/concurrent/Task.java Fri Jan 06 12:34:11 2012 -0800 +++ b/javafx-concurrent/src/javafx/concurrent/Task.java Mon Jan 09 14:21:59 2012 -0800 @@ -25,13 +25,8 @@ package javafx.concurrent; -import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicReference; -import javafx.application.Platform; -import javafx.beans.property.*; -import javafx.event.*; -import static javafx.concurrent.WorkerStateEvent.*; /** *

@@ -432,6 +427,9 @@ *

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.

+ * + *

In general, you may find it easier to use the {@link ObservableListTask} + * when creating a task which returns an ObservableList.

* *

A Task Which Returns Partial Results

* @@ -444,50 +442,33 @@ * 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 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.

+ *

Simply invoke the updateValue method with each + * intermediate value.

* *

- *     public class PartialResultsTask extends Task<ObservableList<Rectangle>> {
- *         // Uses Java 7 diamond operator
- *         private ReadOnlyObjectWrapper> partialResults =
- *                 new ReadOnlyObjectWrapper<>(this, "partialResults",
- *                         FXCollections.observableArrayList(new ArrayList()));
- *
- *         public final ObservableList getPartialResults() { return partialResults.get(); }
- *         public final ReadOnlyObjectProperty> partialResultsProperty() {
- *             return partialResults.getReadOnlyProperty();
+ *     // A silly task which cycles through ten million random numbers
+ *     // before settling on the final answer, reporting each intermediate
+ *     // answer as it goes.
+ *     Task<Integer> task = new Task<Integer>() {
+ *         @Override protected Integer call() throws Exception {
+ *             updateMessage("Finding random integer...");
+ *             for (int i=0; i<10000000; i++) {
+ *                 if (isCancelled()) break;
+ *                 updateValue((int)(Math.random() * Integer.MAX_VALUE));
+ *                 updateProgress(i, 10000000);
+ *             }
+ *             return (int)(Math.random() * Integer.MAX_VALUE);
  *         }
- *
- *         @Override protected ObservableList 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();
- *         }
- *     }
+ *     };
  * 
* - * + *

Note that as with the other update methods, you may call updateValue + * after the Task has been cancelled. Also note that whatever the most recent + * value was specified by updateValue is used even after it has been cancelled. + * For example, suppose i in the above was 100 at the time the + * task were cancelled. Whatever the most recent value passed to + * updateValue will be the value of the Task, not + * the result of the call method!

* *

A Task Which Modifies The Scene Graph

* @@ -554,58 +535,21 @@ * }; * */ -public abstract class Task extends FutureTask implements Worker, EventTarget { +public abstract class Task extends TaskBase { /** - * Used to send workDone updates in a thread-safe manner from the subclass - * to the FX application thread and workDone related properties. AtomicReference - * is used so as to coalesce updates such that we don't flood the event queue. - */ - private AtomicReference progressUpdate = new AtomicReference(); - - /** - * Used to send message updates in a thread-safe manner from the subclass + * Used to send value updates in a thread-safe manner from the subclass * to the FX application thread. AtomicReference is used so as to coalesce * updates such that we don't flood the event queue. */ - private AtomicReference messageUpdate = new AtomicReference(); - - /** - * Used to send title updates in a thread-safe manner from the subclass - * to the FX application thread. AtomicReference is used so as to coalesce - * updates such that we don't flood the event queue. - */ - private AtomicReference titleUpdate = new AtomicReference(); - - /** - * Creates a new Task. - */ - public Task() { - this(new TaskCallable()); - } - - /** - * This bit of construction trickery is necessary because otherwise there is - * no way for the main constructor to both create the callable and maintain - * a reference to it, which is necessary because an anonymous callable construction - * cannot reference the implicit "this". We leverage an internal Callable - * so that all the pre-built semantics around cancel and so forth are - * handled correctly. - * - * @param callableAdapter non-null implementation of the - * TaskCallable adapter - */ - private Task(final TaskCallable callableAdapter) { - super(callableAdapter); - callableAdapter.task = this; - } + private AtomicReference valueUpdate = new AtomicReference(); /** * Invoked when the Task is executed, the call method must be overridden and * implemented by subclasses. The call method actually performs the - * background thread logic. Only the updateProgress, updateMessage, and - * updateTitle methods of Task may be called from code within this method. - * Any other interaction with the Task from the background thread will result - * in runtime exceptions. + * background thread logic. Only the updateProgress, updateMessage, + * updateTitle, and updateValue methods of Task may be called from code + * within this method. Any other interaction with the Task from the + * background thread will result in runtime exceptions. * * @return The result of the background work, if any. * @throws Exception an unhandled exception which occurred during the @@ -613,674 +557,50 @@ */ protected abstract V call() throws Exception; - private ObjectProperty state = new SimpleObjectProperty(this, "state", State.READY); - final void setState(State value) { // package access for the Service - checkThread(); - final State s = getState(); - if (s != State.CANCELLED) { - this.state.set(value); - // Make sure the running flag is set - setRunning(value == State.SCHEDULED || value == State.RUNNING); - - // Invoke the event handlers, and then call the protected methods. - switch (state.get()) { - case CANCELLED: - fireEvent(new WorkerStateEvent(this, WORKER_STATE_CANCELLED)); - cancelled(); - break; - case FAILED: - fireEvent(new WorkerStateEvent(this, WORKER_STATE_FAILED)); - failed(); - break; - case READY: - // This even can never meaningfully occur, because the - // Task begins life as ready and can never go back to it! - break; - case RUNNING: - fireEvent(new WorkerStateEvent(this, WORKER_STATE_RUNNING)); - running(); - break; - case SCHEDULED: - fireEvent(new WorkerStateEvent(this, WORKER_STATE_SCHEDULED)); - scheduled(); - break; - case SUCCEEDED: - fireEvent(new WorkerStateEvent(this, WORKER_STATE_SUCCEEDED)); - succeeded(); - break; - default: throw new AssertionError("Should be unreachable"); - } - } - } - @Override public final State getState() { checkThread(); return state.get(); } - @Override public final ReadOnlyObjectProperty stateProperty() { checkThread(); return state; } - /** - * The onSchedule event handler is called whenever the Task state - * transitions to the SCHEDULED state. + * Updates the value property with the supplied value. This + * can be done at any time. A final updateValue is called + * automatically by Task at the completion of the call + * method. This method allows Task implementations to provide partial + * results. * - * @return the onScheduled event handler property - */ - public final ObjectProperty> onScheduledProperty() { - return getEventHelper().onScheduledProperty(); - } - - /** - * The onSchedule event handler is called whenever the Task state - * transitions to the SCHEDULED state. + *

For example, you may have a Task which computes prime numbers. Each + * prime number computed could become the new value of the + * Task. Simply invoke updateValue passing the most recently computed + * prime number.

* - * @return the onScheduled event handler, if any - */ - public final EventHandler getOnScheduled() { - return eventHelper == null ? null : eventHelper.getOnScheduled(); - } - - /** - * The onSchedule event handler is called whenever the Task state - * transitions to the SCHEDULED state. - * - * @param value the event handler, can be null to clear it - */ - public final void setOnScheduled(EventHandler value) { - getEventHelper().setOnScheduled(value); - } - - /** - * A protected convenience method for subclasses, called whenever the - * state of the Task has transitioned to the SCHEDULED state. - * This method is invoked on the FX Application Thread after any listeners - * of the state property and after the Task has been fully transitioned to - * the new state. - */ - protected void scheduled() { } - - /** - * The onRunning event handler is called whenever the Task state - * transitions to the RUNNING state. - * - * @return the onRunning event handler property - */ - public final ObjectProperty> onRunningProperty() { - return getEventHelper().onRunningProperty(); - } - - /** - * The onRunning event handler is called whenever the Task state - * transitions to the RUNNING state. - * - * @return the onRunning event handler, if any - */ - public final EventHandler getOnRunning() { - return eventHelper == null ? null : eventHelper.getOnRunning(); - } - - /** - * The onRunning event handler is called whenever the Task state - * transitions to the RUNNING state. - * - * @param value the event handler, can be null to clear it - */ - public final void setOnRunning(EventHandler value) { - getEventHelper().setOnRunning(value); - } - - /** - * A protected convenience method for subclasses, called whenever the - * state of the Task has transitioned to the RUNNING state. - * This method is invoked on the FX Application Thread after any listeners - * of the state property and after the Task has been fully transitioned to - * the new state. - */ - protected void running() { } - - /** - * The onSucceeded event handler is called whenever the Task state - * transitions to the SUCCEEDED state. - * - * @return the onSucceeded event handler property - */ - public final ObjectProperty> onSucceededProperty() { - return getEventHelper().onSucceededProperty(); - } - - /** - * The onSucceeded event handler is called whenever the Task state - * transitions to the SUCCEEDED state. - * - * @return the onSucceeded event handler, if any - */ - public final EventHandler getOnSucceeded() { - return eventHelper == null ? null : eventHelper.getOnSucceeded(); - } - - /** - * The onSucceeded event handler is called whenever the Task state - * transitions to the SUCCEEDED state. - * - * @param value the event handler, can be null to clear it - */ - public final void setOnSucceeded(EventHandler value) { - getEventHelper().setOnSucceeded(value); - } - - /** - * A protected convenience method for subclasses, called whenever the - * state of the Task has transitioned to the SUCCEEDED state. - * This method is invoked on the FX Application Thread after any listeners - * of the state property and after the Task has been fully transitioned to - * the new state. - */ - protected void succeeded() { } - - /** - * The onCancelled event handler is called whenever the Task state - * transitions to the CANCELLED state. - * - * @return the onCancelled event handler property - */ - public final ObjectProperty> onCancelledProperty() { - return getEventHelper().onCancelledProperty(); - } - - /** - * The onCancelled event handler is called whenever the Task state - * transitions to the CANCELLED state. - * - * @return the onCancelled event handler, if any - */ - public final EventHandler getOnCancelled() { - return eventHelper == null ? null : eventHelper.getOnCancelled(); - } - - /** - * The onCancelled event handler is called whenever the Task state - * transitions to the CANCELLED state. - * - * @param value the event handler, can be null to clear it - */ - public final void setOnCancelled(EventHandler value) { - getEventHelper().setOnCancelled(value); - } - - /** - * A protected convenience method for subclasses, called whenever the - * state of the Task has transitioned to the CANCELLED state. - * This method is invoked on the FX Application Thread after any listeners - * of the state property and after the Task has been fully transitioned to - * the new state. - */ - protected void cancelled() { } - - /** - * The onFailed event handler is called whenever the Task state - * transitions to the FAILED state. - * - * @return the onFailed event handler property - */ - public final ObjectProperty> onFailedProperty() { - return getEventHelper().onFailedProperty(); - } - - /** - * The onFailed event handler is called whenever the Task state - * transitions to the FAILED state. - * - * @return the onFailed event handler, if any - */ - public final EventHandler getOnFailed() { - return eventHelper == null ? null : eventHelper.getOnFailed(); - } - - /** - * The onFailed event handler is called whenever the Task state - * transitions to the FAILED state. - * - * @param value the event handler, can be null to clear it - */ - public final void setOnFailed(EventHandler value) { - getEventHelper().setOnFailed(value); - } - - /** - * A protected convenience method for subclasses, called whenever the - * state of the Task has transitioned to the FAILED state. - * This method is invoked on the FX Application Thread after any listeners - * of the state property and after the Task has been fully transitioned to - * the new state. - */ - protected void failed() { } - - private ObjectProperty value = new SimpleObjectProperty(this, "value"); - private void setValue(V v) { checkThread(); value.set(v); } - @Override public final V getValue() { checkThread(); return value.get(); } - @Override public final ReadOnlyObjectProperty valueProperty() { checkThread(); return value; } - - private ObjectProperty exception = new SimpleObjectProperty(this, "exception"); - private void _setException(Throwable value) { checkThread(); exception.set(value); } - @Override public final Throwable getException() { checkThread(); return exception.get(); } - @Override public final ReadOnlyObjectProperty exceptionProperty() { checkThread(); return exception; } - - private DoubleProperty workDone = new SimpleDoubleProperty(this, "workDone", -1); - private void setWorkDone(double value) { checkThread(); workDone.set(value); } - @Override public final double getWorkDone() { checkThread(); return workDone.get(); } - @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone; } - - private DoubleProperty totalWork = new SimpleDoubleProperty(this, "totalWork", -1); - private void setTotalWork(double value) { checkThread(); totalWork.set(value); } - @Override public final double getTotalWork() { checkThread(); return totalWork.get(); } - @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWork; } - - private DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1); - private void setProgress(double value) { checkThread(); progress.set(value); } - @Override public final double getProgress() { checkThread(); return progress.get(); } - @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress; } - - private BooleanProperty running = new SimpleBooleanProperty(this, "running", false); - private void setRunning(boolean value) { checkThread(); running.set(value); } - @Override public final boolean isRunning() { checkThread(); return running.get(); } - @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; } - - private StringProperty message = new SimpleStringProperty(this, "message", ""); - @Override public final String getMessage() { return message.get(); } - @Override public final ReadOnlyStringProperty messageProperty() { return message; } - - private StringProperty title = new SimpleStringProperty(this, "title", ""); - @Override public final String getTitle() { return title.get(); } - @Override public final ReadOnlyStringProperty titleProperty() { return title; } - - @Override public final boolean cancel() { - return cancel(true); - } - - @Override public boolean cancel(boolean mayInterruptIfRunning) { - // Delegate to the super implementation to actually attempt to cancel this thing - boolean flag = super.cancel(mayInterruptIfRunning); - // If cancel succeeded (according to the semantics of the Future cancel method), - // then we need to make sure the State flag is set appropriately - if (flag) { - // If this method was called on the FX application thread, then we can - // just update the state directly and this will make sure that after - // the cancel method was called, the state will be set correctly - // (otherwise it would be indeterminate. However if the cancel method was - // called off the FX app thread, then we must use runLater, and the - // state flag will not be readable immediately after this call. However, - // that would be the case anyway since these properties are not thread-safe. - if (isFxApplicationThread()) { - setState(State.CANCELLED); - } else { - runLater(new Runnable() { - @Override public void run() { - setState(State.CANCELLED); - } - }); - } - } - // return the flag - return flag; - } - - /** - * Updates the workDone, 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 -1 up to max. If the value is greater - * than max, an illegal argument exception is thrown. - * If the value passed is -1, then the resulting percent - * done will be -1 (thus, indeterminate). - * @param max A value from -1 to Long.MAX_VALUE. Any value outside this - * range results in an IllegalArgumentException. - */ - protected void updateProgress(long workDone, long max) { - // Perform the argument sanity check that workDone is < max - if (workDone > max) { - throw new IllegalArgumentException("The workDone must be <= the max"); - } - - // Make sure neither workDone nor max is < -1 - if (workDone < -1 || max < -1) { - throw new IllegalArgumentException("The workDone and max cannot be less than -1"); - } - - if (isFxApplicationThread()) { - _updateProgress(workDone, max); - } else if (progressUpdate.getAndSet(new ProgressUpdate(workDone, max)) == null) { - runLater(new Runnable() { - @Override public void run() { - 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 the message property. Calls to updateMessage + *

As with the other update methods, Calls to updateValue * are coalesced and run later on the FX application thread, so calls - * to updateMessage, even from the FX Application thread, may not + * to updateValue, 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 + * intermediate values may be coalesced to save on event * notifications. *

* This method is safe to be called from any thread. *

* - * @param message the new message + * @param value The new value for the value property of Task. */ - protected void updateMessage(String message) { + protected void updateValue(V value) { if (isFxApplicationThread()) { - this.message.set(message); + setValue(value); } else { // As with the workDone, it might be that the background thread - // will update this message quite frequently, and we need + // will update this value quite frequently, and we need // to throttle the updates so as not to completely clobber // the event dispatching system. - if (messageUpdate.getAndSet(message) == null) { + if (valueUpdate.getAndSet(value) == null) { runLater(new Runnable() { @Override public void run() { - final String message = messageUpdate.getAndSet(null); - Task.this.message.set(message); + final V value = valueUpdate.getAndSet(null); + setValue(value); } }); } } } - /** - * Updates the title 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); - } - }); - } - } - } - - /* - * IMPLEMENTATION - */ - - private void checkThread() { - if (!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 the specific event class of the handler - * @param eventType the type of the events to receive by the handler - * @param eventHandler the handler to register - */ - public final void addEventHandler( - final EventType eventType, - final EventHandler eventHandler) { - getEventHelper().addEventHandler(eventType, eventHandler); - } - - /** - * Unregisters a previously registered event handler from this task. One - * handler might have been registered for different event types, so the - * caller needs to specify the particular event type from which to - * unregister the handler. - * - * @param the specific event class of the handler - * @param eventType the event type from which to unregister - * @param eventHandler the handler to unregister - */ - public final void removeEventHandler( - final EventType eventType, - final EventHandler eventHandler) { - getEventHelper().removeEventHandler(eventType, eventHandler); - } - - /** - * Registers an event filter to this task. Registered event filters get - * an event before any associated event handlers. - * - * @param the specific event class of the filter - * @param eventType the type of the events to receive by the filter - * @param eventFilter the filter to register - */ - public final void addEventFilter( - final EventType eventType, - final EventHandler eventFilter) { - getEventHelper().addEventFilter(eventType, eventFilter); - } - - /** - * Unregisters a previously registered event filter from this task. One - * filter might have been registered for different event types, so the - * caller needs to specify the particular event type from which to - * unregister the filter. - * - * @param the specific event class of the filter - * @param eventType the event type from which to unregister - * @param eventFilter the filter to unregister - */ - public final void removeEventFilter( - final EventType eventType, - final EventHandler eventFilter) { - getEventHelper().removeEventFilter(eventType, eventFilter); - } - - /** - * Sets the handler to use for this event type. There can only be one such - * handler specified at a time. This handler is guaranteed to be called - * first. This is used for registering the user-defined onFoo event - * handlers. - * - * @param the specific event class of the handler - * @param eventType the event type to associate with the given eventHandler - * @param eventHandler the handler to register, or null to unregister - */ - protected final void setEventHandler( - final EventType eventType, - final EventHandler eventHandler) { - getEventHelper().setEventHandler(eventType, eventHandler); - } - - /** - * Fires the specified event. Any event filter encountered will - * be notified and can consume the event. If not consumed by the filters, - * the event handlers on this task are notified. If these don't consume the - * event either, then all event handlers are called and can consume the - * event. - *

- * This method must be called on the FX user thread. - * - * @param event the event to fire - */ - public final void fireEvent(Event event) { - checkThread(); - getEventHelper().fireEvent(event); - } - - @Override - public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { - 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 double workDone; - private 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 - */ - private static final class TaskCallable implements Callable { - /** - * The Task that is going to use this TaskCallable - */ - private Task task; - - /** - * Create a TaskCallable. The concurrent and other fields MUST be set - * immediately after creation. - */ - private TaskCallable() { } - - /** - * Invoked by the system when it is time to run the client code. This - * implementation is where we modify the state and other properties - * and from which we invoke the events. - * - * @return The result of the Task call method - * @throws Exception any exception which occurred - */ - @Override public V call() throws Exception { - // If the Task is sent to an ExecutorService for execution, then we - // will need to make sure that we transition first to the SCHEDULED - // state before then transitioning to the RUNNING state. If the - // Task was executed by a Service, then it will have already been - // in the SCHEDULED state and setting it again here has no negative - // effect. But we must ensure that SCHEDULED is visited before RUNNING - // in all cases so that developer code can be consistent. - task.runLater(new Runnable() { - @Override public void run() { - task.setState(State.SCHEDULED); - task.setState(State.RUNNING); - } - }); - // Go ahead and delegate to the wrapped callable - try { - final V result = task.call(); - if (!task.isCancelled()) { - // If it was not cancelled, then we take the return - // value and set it as the result. - task.runLater(new Runnable() { - @Override public void run() { - // The result must be set first, so that when the - // SUCCEEDED flag is set, the value will be available - // The alternative is not the case, because you - // can assume if the result is set, it has - // succeeded. - task.setValue(result); - task.setState(State.SUCCEEDED); - } - }); - return result; - } else { - // There may have been some intermediate result in the - // task set from the background thread, so I want to be - // sure to return the most recent intermediate value - return task.getValue(); - } - } catch (final Throwable th) { - // Be sure to set the state after setting the cause of failure - // so that developers handling the state change events have a - // throwable to inspect when they get the FAILED state. Note - // that the other way around is not important -- when a developer - // observes the causeOfFailure is set to a non-null value, even - // though the state has not yet been updated, he can infer that - // it will be FAILED because it can be nothing other than FAILED - // in that circumstance. - task.runLater(new Runnable() { - @Override public void run() { - task._setException(th); - task.setState(State.FAILED); - } - }); - // Some error occurred during the call (it might be - // an exception (either runtime or checked), or it might - // be an error. In any case, we capture the throwable, - // record it as the causeOfFailure, and then rethrow. However - // since the Callable interface requires that we throw an - // Exception (not Throwable), we have to wrap the exception - // if it is not already one. - if (th instanceof Exception) { - throw (Exception) th; - } else { - throw new Exception(th); - } - } - } + @Override V doInBackground() throws Exception { + return call(); } } diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/src/javafx/concurrent/TaskBase.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/src/javafx/concurrent/TaskBase.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2011, 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.util.concurrent.Callable; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReference; +import javafx.application.Platform; +import javafx.beans.property.*; +import javafx.event.*; +import static javafx.concurrent.WorkerStateEvent.*; +import static javafx.concurrent.WorkerStateEvent.WORKER_STATE_SUCCEEDED; + +/** + * Base class for various JavaFX implementations of Worker based on + * {@link java.util.concurrent.FutureTask}. This class itself cannot be + * used directly. Rather, see {@link Task} and {@link ObservableListTask} + * for concrete classes to use. + */ +public abstract class TaskBase extends FutureTask implements Worker, EventTarget { + /** + * Used to send workDone updates in a thread-safe manner from the subclass + * to the FX application thread and workDone related properties. AtomicReference + * is used so as to coalesce updates such that we don't flood the event queue. + */ + private AtomicReference progressUpdate = new AtomicReference(); + + /** + * Used to send message updates in a thread-safe manner from the subclass + * to the FX application thread. AtomicReference is used so as to coalesce + * updates such that we don't flood the event queue. + */ + private AtomicReference messageUpdate = new AtomicReference(); + + /** + * Used to send title updates in a thread-safe manner from the subclass + * to the FX application thread. AtomicReference is used so as to coalesce + * updates such that we don't flood the event queue. + */ + private AtomicReference titleUpdate = new AtomicReference(); + + /** + * Creates a new Task. + */ + protected TaskBase() { + this(new TaskCallable()); + } + + /** + * This bit of construction trickery is necessary because otherwise there is + * no way for the main constructor to both create the callable and maintain + * a reference to it, which is necessary because an anonymous callable construction + * cannot reference the implicit "this". We leverage an internal Callable + * so that all the pre-built semantics around cancel and so forth are + * handled correctly. + * + * @param callableAdapter non-null implementation of the + * TaskCallable adapter + */ + private TaskBase(final TaskCallable callableAdapter) { + super(callableAdapter); + callableAdapter.task = this; + } + + /** + * Invoked when the Task is executed, the call method must be overridden and + * implemented by subclasses. The call method actually performs the + * background thread logic. Only the updateProgress, updateMessage, and + * updateTitle methods of Task may be called from code within this method. + * Any other interaction with the Task from the background thread will result + * in runtime exceptions. + * + * @return The result of the background work, if any. + * @throws Exception an unhandled exception which occurred during the + * background operation + */ + abstract V doInBackground() throws Exception; + + private ObjectProperty state = new SimpleObjectProperty(this, "state", Worker.State.READY); + final void setState(Worker.State value) { // package access for the Service + checkThread(); + final Worker.State s = getState(); + if (s != Worker.State.CANCELLED) { + this.state.set(value); + // Make sure the running flag is set + setRunning(value == Worker.State.SCHEDULED || value == Worker.State.RUNNING); + + // Invoke the event handlers, and then call the protected methods. + switch (state.get()) { + case CANCELLED: + fireEvent(new WorkerStateEvent(this, WORKER_STATE_CANCELLED)); + cancelled(); + break; + case FAILED: + fireEvent(new WorkerStateEvent(this, WORKER_STATE_FAILED)); + failed(); + break; + case READY: + // This even can never meaningfully occur, because the + // Task begins life as ready and can never go back to it! + break; + case RUNNING: + fireEvent(new WorkerStateEvent(this, WORKER_STATE_RUNNING)); + running(); + break; + case SCHEDULED: + fireEvent(new WorkerStateEvent(this, WORKER_STATE_SCHEDULED)); + scheduled(); + break; + case SUCCEEDED: + fireEvent(new WorkerStateEvent(this, WORKER_STATE_SUCCEEDED)); + succeeded(); + break; + default: throw new AssertionError("Should be unreachable"); + } + } + } + @Override public final Worker.State getState() { checkThread(); return state.get(); } + @Override public final ReadOnlyObjectProperty stateProperty() { checkThread(); return state; } + + /** + * The onSchedule event handler is called whenever the Task state + * transitions to the SCHEDULED state. + * + * @return the onScheduled event handler property + */ + public final ObjectProperty> onScheduledProperty() { + return getEventHelper().onScheduledProperty(); + } + + /** + * The onSchedule event handler is called whenever the Task state + * transitions to the SCHEDULED state. + * + * @return the onScheduled event handler, if any + */ + public final EventHandler getOnScheduled() { + return eventHelper == null ? null : eventHelper.getOnScheduled(); + } + + /** + * The onSchedule event handler is called whenever the Task state + * transitions to the SCHEDULED state. + * + * @param value the event handler, can be null to clear it + */ + public final void setOnScheduled(EventHandler value) { + getEventHelper().setOnScheduled(value); + } + + /** + * A protected convenience method for subclasses, called whenever the + * state of the Task has transitioned to the SCHEDULED state. + * This method is invoked on the FX Application Thread after any listeners + * of the state property and after the Task has been fully transitioned to + * the new state. + */ + protected void scheduled() { } + + /** + * The onRunning event handler is called whenever the Task state + * transitions to the RUNNING state. + * + * @return the onRunning event handler property + */ + public final ObjectProperty> onRunningProperty() { + return getEventHelper().onRunningProperty(); + } + + /** + * The onRunning event handler is called whenever the Task state + * transitions to the RUNNING state. + * + * @return the onRunning event handler, if any + */ + public final EventHandler getOnRunning() { + return eventHelper == null ? null : eventHelper.getOnRunning(); + } + + /** + * The onRunning event handler is called whenever the Task state + * transitions to the RUNNING state. + * + * @param value the event handler, can be null to clear it + */ + public final void setOnRunning(EventHandler value) { + getEventHelper().setOnRunning(value); + } + + /** + * A protected convenience method for subclasses, called whenever the + * state of the Task has transitioned to the RUNNING state. + * This method is invoked on the FX Application Thread after any listeners + * of the state property and after the Task has been fully transitioned to + * the new state. + */ + protected void running() { } + + /** + * The onSucceeded event handler is called whenever the Task state + * transitions to the SUCCEEDED state. + * + * @return the onSucceeded event handler property + */ + public final ObjectProperty> onSucceededProperty() { + return getEventHelper().onSucceededProperty(); + } + + /** + * The onSucceeded event handler is called whenever the Task state + * transitions to the SUCCEEDED state. + * + * @return the onSucceeded event handler, if any + */ + public final EventHandler getOnSucceeded() { + return eventHelper == null ? null : eventHelper.getOnSucceeded(); + } + + /** + * The onSucceeded event handler is called whenever the Task state + * transitions to the SUCCEEDED state. + * + * @param value the event handler, can be null to clear it + */ + public final void setOnSucceeded(EventHandler value) { + getEventHelper().setOnSucceeded(value); + } + + /** + * A protected convenience method for subclasses, called whenever the + * state of the Task has transitioned to the SUCCEEDED state. + * This method is invoked on the FX Application Thread after any listeners + * of the state property and after the Task has been fully transitioned to + * the new state. + */ + protected void succeeded() { } + + /** + * The onCancelled event handler is called whenever the Task state + * transitions to the CANCELLED state. + * + * @return the onCancelled event handler property + */ + public final ObjectProperty> onCancelledProperty() { + return getEventHelper().onCancelledProperty(); + } + + /** + * The onCancelled event handler is called whenever the Task state + * transitions to the CANCELLED state. + * + * @return the onCancelled event handler, if any + */ + public final EventHandler getOnCancelled() { + return eventHelper == null ? null : eventHelper.getOnCancelled(); + } + + /** + * The onCancelled event handler is called whenever the Task state + * transitions to the CANCELLED state. + * + * @param value the event handler, can be null to clear it + */ + public final void setOnCancelled(EventHandler value) { + getEventHelper().setOnCancelled(value); + } + + /** + * A protected convenience method for subclasses, called whenever the + * state of the Task has transitioned to the CANCELLED state. + * This method is invoked on the FX Application Thread after any listeners + * of the state property and after the Task has been fully transitioned to + * the new state. + */ + protected void cancelled() { } + + /** + * The onFailed event handler is called whenever the Task state + * transitions to the FAILED state. + * + * @return the onFailed event handler property + */ + public final ObjectProperty> onFailedProperty() { + return getEventHelper().onFailedProperty(); + } + + /** + * The onFailed event handler is called whenever the Task state + * transitions to the FAILED state. + * + * @return the onFailed event handler, if any + */ + public final EventHandler getOnFailed() { + return eventHelper == null ? null : eventHelper.getOnFailed(); + } + + /** + * The onFailed event handler is called whenever the Task state + * transitions to the FAILED state. + * + * @param value the event handler, can be null to clear it + */ + public final void setOnFailed(EventHandler value) { + getEventHelper().setOnFailed(value); + } + + /** + * A protected convenience method for subclasses, called whenever the + * state of the Task has transitioned to the FAILED state. + * This method is invoked on the FX Application Thread after any listeners + * of the state property and after the Task has been fully transitioned to + * the new state. + */ + protected void failed() { } + + private ObjectProperty value = new SimpleObjectProperty(this, "value"); + void setValue(V v) { checkThread(); value.set(v); } + void initValue(V v) { value.set(v); } // may only be called during construction! + @Override public final V getValue() { checkThread(); return value.get(); } + @Override public final ReadOnlyObjectProperty valueProperty() { checkThread(); return value; } + + private ObjectProperty exception = new SimpleObjectProperty(this, "exception"); + private void _setException(Throwable value) { checkThread(); exception.set(value); } + @Override public final Throwable getException() { checkThread(); return exception.get(); } + @Override public final ReadOnlyObjectProperty exceptionProperty() { checkThread(); return exception; } + + private DoubleProperty workDone = new SimpleDoubleProperty(this, "workDone", -1); + private void setWorkDone(double value) { checkThread(); workDone.set(value); } + @Override public final double getWorkDone() { checkThread(); return workDone.get(); } + @Override public final ReadOnlyDoubleProperty workDoneProperty() { checkThread(); return workDone; } + + private DoubleProperty totalWork = new SimpleDoubleProperty(this, "totalWork", -1); + private void setTotalWork(double value) { checkThread(); totalWork.set(value); } + @Override public final double getTotalWork() { checkThread(); return totalWork.get(); } + @Override public final ReadOnlyDoubleProperty totalWorkProperty() { checkThread(); return totalWork; } + + private DoubleProperty progress = new SimpleDoubleProperty(this, "progress", -1); + private void setProgress(double value) { checkThread(); progress.set(value); } + @Override public final double getProgress() { checkThread(); return progress.get(); } + @Override public final ReadOnlyDoubleProperty progressProperty() { checkThread(); return progress; } + + private BooleanProperty running = new SimpleBooleanProperty(this, "running", false); + private void setRunning(boolean value) { checkThread(); running.set(value); } + @Override public final boolean isRunning() { checkThread(); return running.get(); } + @Override public final ReadOnlyBooleanProperty runningProperty() { checkThread(); return running; } + + private StringProperty message = new SimpleStringProperty(this, "message", ""); + @Override public final String getMessage() { return message.get(); } + @Override public final ReadOnlyStringProperty messageProperty() { return message; } + + private StringProperty title = new SimpleStringProperty(this, "title", ""); + @Override public final String getTitle() { return title.get(); } + @Override public final ReadOnlyStringProperty titleProperty() { return title; } + + @Override public final boolean cancel() { + return cancel(true); + } + + @Override public boolean cancel(boolean mayInterruptIfRunning) { + // Delegate to the super implementation to actually attempt to cancel this thing + boolean flag = super.cancel(mayInterruptIfRunning); + // If cancel succeeded (according to the semantics of the Future cancel method), + // then we need to make sure the State flag is set appropriately + if (flag) { + // If this method was called on the FX application thread, then we can + // just update the state directly and this will make sure that after + // the cancel method was called, the state will be set correctly + // (otherwise it would be indeterminate. However if the cancel method was + // called off the FX app thread, then we must use runLater, and the + // state flag will not be readable immediately after this call. However, + // that would be the case anyway since these properties are not thread-safe. + if (isFxApplicationThread()) { + setState(Worker.State.CANCELLED); + } else { + runLater(new Runnable() { + @Override public void run() { + setState(Worker.State.CANCELLED); + } + }); + } + } + // return the flag + return flag; + } + + /** + * Updates the workDone, 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 -1 up to max. If the value is greater + * than max, an illegal argument exception is thrown. + * If the value passed is -1, then the resulting percent + * done will be -1 (thus, indeterminate). + * @param max A value from -1 to Long.MAX_VALUE. Any value outside this + * range results in an IllegalArgumentException. + */ + protected void updateProgress(long workDone, long max) { + // Perform the argument sanity check that workDone is < max + if (workDone > max) { + throw new IllegalArgumentException("The workDone must be <= the max"); + } + + // Make sure neither workDone nor max is < -1 + if (workDone < -1 || max < -1) { + throw new IllegalArgumentException("The workDone and max cannot be less than -1"); + } + + if (isFxApplicationThread()) { + _updateProgress(workDone, max); + } else if (progressUpdate.getAndSet(new ProgressUpdate(workDone, max)) == null) { + runLater(new Runnable() { + @Override public void run() { + 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 the message 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); + TaskBase.this.message.set(message); + } + }); + } + } + } + + /** + * Updates the title 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); + TaskBase.this.title.set(title); + } + }); + } + } + } + + /* + * IMPLEMENTATION + */ + + private void checkThread() { + if (!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 the specific event class of the handler + * @param eventType the type of the events to receive by the handler + * @param eventHandler the handler to register + */ + public final void addEventHandler( + final EventType eventType, + final EventHandler eventHandler) { + getEventHelper().addEventHandler(eventType, eventHandler); + } + + /** + * Unregisters a previously registered event handler from this task. One + * handler might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the handler. + * + * @param the specific event class of the handler + * @param eventType the event type from which to unregister + * @param eventHandler the handler to unregister + */ + public final void removeEventHandler( + final EventType eventType, + final EventHandler eventHandler) { + getEventHelper().removeEventHandler(eventType, eventHandler); + } + + /** + * Registers an event filter to this task. Registered event filters get + * an event before any associated event handlers. + * + * @param the specific event class of the filter + * @param eventType the type of the events to receive by the filter + * @param eventFilter the filter to register + */ + public final void addEventFilter( + final EventType eventType, + final EventHandler eventFilter) { + getEventHelper().addEventFilter(eventType, eventFilter); + } + + /** + * Unregisters a previously registered event filter from this task. One + * filter might have been registered for different event types, so the + * caller needs to specify the particular event type from which to + * unregister the filter. + * + * @param the specific event class of the filter + * @param eventType the event type from which to unregister + * @param eventFilter the filter to unregister + */ + public final void removeEventFilter( + final EventType eventType, + final EventHandler eventFilter) { + getEventHelper().removeEventFilter(eventType, eventFilter); + } + + /** + * Sets the handler to use for this event type. There can only be one such + * handler specified at a time. This handler is guaranteed to be called + * first. This is used for registering the user-defined onFoo event + * handlers. + * + * @param the specific event class of the handler + * @param eventType the event type to associate with the given eventHandler + * @param eventHandler the handler to register, or null to unregister + */ + protected final void setEventHandler( + final EventType eventType, + final EventHandler eventHandler) { + getEventHelper().setEventHandler(eventType, eventHandler); + } + + /** + * Fires the specified event. Any event filter encountered will + * be notified and can consume the event. If not consumed by the filters, + * the event handlers on this task are notified. If these don't consume the + * event either, then all event handlers are called and can consume the + * event. + *

+ * This method must be called on the FX user thread. + * + * @param event the event to fire + */ + public final void fireEvent(Event event) { + checkThread(); + getEventHelper().fireEvent(event); + } + + @Override + public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) { + 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 double workDone; + private 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 + */ + private static final class TaskCallable implements Callable { + /** + * The Task that is going to use this TaskCallable + */ + private TaskBase task; + + /** + * Create a TaskCallable. The concurrent and other fields MUST be set + * immediately after creation. + */ + private TaskCallable() { } + + /** + * Invoked by the system when it is time to run the client code. This + * implementation is where we modify the state and other properties + * and from which we invoke the events. + * + * @return The result of the Task call method + * @throws Exception any exception which occurred + */ + @Override public V call() throws Exception { + // If the Task is sent to an ExecutorService for execution, then we + // will need to make sure that we transition first to the SCHEDULED + // state before then transitioning to the RUNNING state. If the + // Task was executed by a Service, then it will have already been + // in the SCHEDULED state and setting it again here has no negative + // effect. But we must ensure that SCHEDULED is visited before RUNNING + // in all cases so that developer code can be consistent. + task.runLater(new Runnable() { + @Override public void run() { + task.setState(Worker.State.SCHEDULED); + task.setState(Worker.State.RUNNING); + } + }); + // Go ahead and delegate to the wrapped callable + try { + final V result = task.doInBackground(); + if (!task.isCancelled()) { + // If it was not cancelled, then we take the return + // value and set it as the result. + task.runLater(new Runnable() { + @Override public void run() { + // The result must be set first, so that when the + // SUCCEEDED flag is set, the value will be available + // The alternative is not the case, because you + // can assume if the result is set, it has + // succeeded. + task.setValue(result); + task.setState(Worker.State.SUCCEEDED); + } + }); + return result; + } else { + // There may have been some intermediate result in the + // task set from the background thread, so I want to be + // sure to return the most recent intermediate value + return task.getValue(); + } + } catch (final Throwable th) { + // An exception may have been thrown after the task was + // cancelled. In such a case, we don't want to record that + // there was an exception. Essentially, after a task is + // cancelled, we don't care what happens on the background + // thread, for all intents and purposes, the task background + // thread is dead. + if (!task.isCancelled()) { + // Be sure to set the state after setting the cause of failure + // so that developers handling the state change events have a + // throwable to inspect when they get the FAILED state. Note + // that the other way around is not important -- when a developer + // observes the causeOfFailure is set to a non-null value, even + // though the state has not yet been updated, he can infer that + // it will be FAILED because it can be nothing other than FAILED + // in that circumstance. + task.runLater(new Runnable() { + @Override public void run() { + task._setException(th); + task.setState(Worker.State.FAILED); + } + }); + // Some error occurred during the call (it might be + // an exception (either runtime or checked), or it might + // be an error. In any case, we capture the throwable, + // record it as the causeOfFailure, and then rethrow. However + // since the Callable interface requires that we throw an + // Exception (not Throwable), we have to wrap the exception + // if it is not already one. + if (th instanceof Exception) { + throw (Exception) th; + } else { + throw new Exception(th); + } + } else { + // If it was cancelled, then the value of the task is + // the value that is already set on the task + return task.getValue(); + } + } + } + }} diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/src/javafx/concurrent/Worker.java --- a/javafx-concurrent/src/javafx/concurrent/Worker.java Fri Jan 06 12:34:11 2012 -0800 +++ b/javafx-concurrent/src/javafx/concurrent/Worker.java Mon Jan 09 14:21:59 2012 -0800 @@ -176,9 +176,10 @@ public ReadOnlyObjectProperty stateProperty(); /** - * Specifies the value, or result, of this Worker. This is set upon entering - * the SUCCEEDED state, and cleared (set to null) if the Worker is reinitialized - * (that is, if the Worker is a reusable Worker and is reset or restarted). + * Specifies the value, or result, of this Worker. This is typically set + * upon entering the SUCCEEDED state and cleared (for example, set to null) + * if the Worker is reinitialized (that is, if the Worker is a reusable + * Worker and is reset or restarted). * * @return the current value of this Worker */ diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/test/javafx/concurrent/AbstractObservableListTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/test/javafx/concurrent/AbstractObservableListTask.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011, 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 javafx.collections.ObservableList; + +/** + * An implementation of ObservableListTask which uses the test thread as + * if it were the FX thread. + */ +public abstract class AbstractObservableListTask extends ObservableListTask { + public AbstractObservableListTask() { + super(); + } + + public AbstractObservableListTask(ObservableList srcList) { + super(srcList); + } + + // For most tests, we want to pretend that we are on the FX app thread, always. + @Override boolean isFxApplicationThread() { + return true; + } + + // For most tests, we want to just run this stuff immediately + @Override void runLater(Runnable r) { + r.run(); + } +} diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/test/javafx/concurrent/ObservableListTaskTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/test/javafx/concurrent/ObservableListTaskTest.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2011, 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.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.junit.Test; +import javafx.concurrent.mocks.SimpleObservableListTask; +import static org.junit.Assert.*; + +/** + * Tests for the additional functionality contained in the ObservableListTask. + * + */ +public class ObservableListTaskTest { + + @Test public void createObservableListTaskResultsInNonNullObservableList() { + ObservableListTask task = new SimpleObservableListTask(); + assertNotNull(task.getValue()); + } + + @Test public void createObservableListTaskWithSpecificObservableList() { + ObservableList mine = FXCollections.observableArrayList(); + ObservableListTask task = new SimpleObservableListTask(mine); + assertSame(mine, task.getValue()); + } + + @Test(expected = NullPointerException.class) + public void createObservableListTaskWithNullThrowsNPE() { + new SimpleObservableListTask(null); + } + + @Test public void createObservableListTaskWithUnmodifiableListResultsInRuntimeException() { + ObservableList mine = FXCollections.unmodifiableObservableList( + FXCollections.observableArrayList()); + ObservableListTask task = new SimpleObservableListTask(mine); + task.run(); + assertNotNull(task.getException()); + } + + // publish(null) + @Test public void publishNullAddsANull() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + publish((String) null); + } + }; + task.run(); + assertNull(task.getException()); + assertEquals(1, task.getValue().size()); + assertNull(task.getValue().get(0)); + } + + @Test public void publishNullArrayDoesNothing() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + publish((String[]) null); + } + }; + task.run(); + assertNull(task.getException()); + assertTrue(task.getValue().isEmpty()); + } + + // publish(item) + @Test public void publishItem() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + publish("Pineapple"); + } + }; + task.run(); + assertNull(task.getException()); + assertEquals(1, task.getValue().size()); + assertEquals("Pineapple", task.getValue().get(0)); + } + + // publish(item, item) + @Test public void publishItems_Varargs() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + publish("Pineapple", "Squash"); + } + }; + task.run(); + assertNull(task.getException()); + assertEquals(2, task.getValue().size()); + assertEquals("Pineapple", task.getValue().get(0)); + assertEquals("Squash", task.getValue().get(1)); + } + + // publish(items) // set + @Test public void publishItems_Set() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + HashSet set = new HashSet(); + set.add("Pineapple"); + set.add("Squash"); + publish(set); + } + }; + task.run(); + assertNull(task.getException()); + assertEquals(2, task.getValue().size()); + assertTrue(task.getValue().contains("Pineapple")); + assertTrue(task.getValue().contains("Squash")); + } + + // publish(items) // list + @Test public void publishItems_List() { + ObservableListTask task = new AbstractObservableListTask() { + @Override + protected void call() throws Exception { + List set = new ArrayList(); + set.add("Pineapple"); + set.add("Squash"); + publish(set); + } + }; + task.run(); + assertNull(task.getException()); + assertEquals(2, task.getValue().size()); + assertEquals("Pineapple", task.getValue().get(0)); + assertEquals("Squash", task.getValue().get(1)); + } +} diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/test/javafx/concurrent/TaskCancelTest.java --- a/javafx-concurrent/test/javafx/concurrent/TaskCancelTest.java Fri Jan 06 12:34:11 2012 -0800 +++ b/javafx-concurrent/test/javafx/concurrent/TaskCancelTest.java Mon Jan 09 14:21:59 2012 -0800 @@ -124,7 +124,12 @@ * */ @Test public void aFreeRunningCancelledTaskReturnValueShouldBeIgnored() throws Exception { - RunAwayTask runAway = new RunAwayTask(); + RunAwayTask runAway = new RunAwayTask() { + @Override + protected void loop(int count) throws Exception { + updateMessage("Loop " + count); + } + }; Thread th = new Thread(runAway); th.start(); runAway.runningSemaphore.acquire(); @@ -133,9 +138,30 @@ th.join(); assertEquals(Task.State.CANCELLED, runAway.getState()); - // TODO why is this commented out? -// assertNull(task.getException()); assertNull(runAway.getValue()); assertTrue(runAway.isDone()); } + + /** + * + */ + @Test public void aFreeRunningCancelledTaskCanContinueToSetTheValue() throws Exception { + RunAwayTask runAway = new RunAwayTask() { + @Override + protected void loop(int count) throws Exception { + updateMessage("Loop " + count); + updateValue("Loop " + count); + } + }; + Thread th = new Thread(runAway); + th.start(); + runAway.runningSemaphore.acquire(); + assertTrue(runAway.cancel()); + runAway.stopLooping.set(true); + th.join(); + + assertEquals(Task.State.CANCELLED, runAway.getState()); + assertEquals(runAway.getMessage(), runAway.getValue()); + assertTrue(runAway.isDone()); + } } diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/test/javafx/concurrent/mocks/RunAwayTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/test/javafx/concurrent/mocks/RunAwayTask.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2011, 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.mocks; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import javafx.concurrent.AbstractTask; + +/** + * A Task which will simply loop forever without end. This is used to simulate + * what happens when a task doesn't head the "isCancelled" flag or + * cancellation in any way. To make sure it finally does terminate (so the + * tests will work) there is a stopLooping atomic boolean that + * the test needs to set to true. + * + *

To make sure that a single loop has occurred before we quit, there is + * a loopHasHappened boolean. This way we always know that + * a single iteration has completed, at least, before the task was + * terminated.

+ * + *

Different tests want to use a RunAwayTask differently. Some want to + * call updateValue in the body of the loop, while others do not. For this + * reason, the RunAwayTask is abstract and an abstract loop + * method is defined that a subclass implements to implement the body + * of the loop.

+ */ +public abstract class RunAwayTask extends AbstractTask { + public AtomicBoolean stopLooping = new AtomicBoolean(false); + private boolean loopHasHappened = false; + + @Override protected String call() throws Exception { + int count = 0; + while (!loopHasHappened || !stopLooping.get()) { + count++; + loop(count); + loopHasHappened = true; + } + return "" + count; + } + + protected abstract void loop(int count) throws Exception; + +} diff -r b138ee292e7b -r 160b675b10e3 javafx-concurrent/test/javafx/concurrent/mocks/SimpleObservableListTask.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/javafx-concurrent/test/javafx/concurrent/mocks/SimpleObservableListTask.java Mon Jan 09 14:21:59 2012 -0800 @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2011, 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.mocks; + +import javafx.collections.ObservableList; +import javafx.concurrent.AbstractObservableListTask; + +/** + * A very simple ObservableListTask which publishes, in a single call, + * "Apple", "Orange", "Pear". + */ +public class SimpleObservableListTask extends AbstractObservableListTask { + public SimpleObservableListTask() { + super(); + } + + public SimpleObservableListTask(ObservableList srcList) { + super(srcList); + } + + @Override + protected void call() throws Exception { + publish("Apple", "Orange", "Pear"); + } +}