eventType,
- final EventHandler super T> 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 super T> 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 super T> 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 super T> 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 super T> 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 super T> 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");
+ }
+}