/* * 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.

* *

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(); setValue(list); } /** * Create a new ObservableListTask, using the supplied list as the value * for this task. * * @param list The list to use. This cannot be null or a * NullPointerException will be thrown. */ protected ObservableListTask(ObservableList list) { if (list == null) { throw new NullPointerException("Supplied list cannot be null"); } this.list = list; setValue(list); } @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) { 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(); } } } }