See: Description
Interface | Description |
---|---|
ObservableTask |
A
Task that notifies its observers when it is finished
executing. |
ServiceProperties |
Reserved keywords for OSGi service properties (meta data).
|
SynchronousTaskManager<T> |
A marker interface that indicates that the TaskManager in question will
execute the tasks found in the TaskFactory synchronously with the current thread,
blocking code execution until all tasks finish.
|
Task |
This interface specifies a unit of work to be executed
asynchronously in its own
Thread along with a user interface to
display its progress, provide a means for the user to
cancel the Task , and show information
about any Exception s thrown during its
execution. |
TaskFactory |
Returns an instance of a
TaskIterator . |
TaskManager<T,C> | |
TaskMonitor |
Used by a
Task 's implementation run method
to inform users of the status of its execution. |
TaskObserver |
An observer that gets notified when an
ObservableTask finishes
executing. |
TunableHandler |
Interface for classes that deal with reading out and writing back
Tunable s and their properties. |
TunableHandlerFactory<T extends TunableHandler> |
A factory service to create a
TunableHandler for a single type of object,
determined by the type of the field or the return value of the getter method in the
appropriate methods. |
TunableMutator<T extends TunableHandler,S> |
This is a type of tunable interceptor that reads and
modifies the values annotated with the
Tunable
annotation. |
TunableRecorder<T extends TunableHandler> |
TunableRecorder is a special type of tunable interceptor that
reads the state of the tunables but does not modify the
value of the tunables.
|
TunableSetter |
An API for setting tunable fields and methods with predetermined
values in the Tasks found in the specified TaskIterator.
|
TunableValidator |
If implemented, this interface is used to apply a test to the modified values of a Tunable.
|
Class | Description |
---|---|
AbstractTask |
A base class for tasks that need to be able to access the TaskIterator that contains them.
|
AbstractTaskFactory |
A TaskFactory that is always ready to produce a TaskIterator.
|
AbstractTaskManager<T,C> |
Provides access to a TunableInterceptor to all derived classes and a
utility method to determine if an object has been annotated with Tunables.
|
AbstractTunableHandler |
Provides the standard implementation for most of the methods declared by the
TunableHandler interface.
|
AbstractTunableInterceptor<T extends TunableHandler> |
An abstract base class for TunableRecorder and TunableMutator implementations.
|
BasicTunableHandlerFactory<T extends TunableHandler> |
A convenience implementation of TunableHandlerFactory that will construct a TunableHandler
of the specified type given the TunableHandler in question has at least two constructors, one
with Field, Object, Tunable parameters and the other with Method, Method, Object, Tunable parameters.
|
FinishStatus |
Indicates the status of a task iterator when it has finished for
TaskObserver s. |
TaskIterator |
A TaskIterator provides the functionality of sequencing
Task s. |
TunableGravityOrderer |
This class provides a comparator to order the
Tunable s based on their
gravity value. |
Enum | Description |
---|---|
FinishStatus.Type | |
TaskMonitor.Level |
Used by the
showMessage method to indicate the severity of the message. |
TunableValidator.ValidationState |
The states the the validator can return.
|
Annotation Type | Description |
---|---|
ContainsTunables |
An annotation designed to signal that the annotated field contains
fields and methods that are annotated with the
Tunable annotation. |
ProvidesTitle |
An annotation type that can be applied to a method which returns a
String that will be used for the title of a Tunable user interface dialog
window.
|
Tunable |
Tasks are somewhat analogous to Swing action listeners. To recap Swing action listeners, let's say you want to create a menu item and listen to its selection. You would follow these steps:
JMenuItem
and assign it a name, icon, and tooltip.ActionListener
to the menu item by calling the addActionListener
method.
Cytoscape is in an OSGi environment. Its components are deliberately made to be loosely coupled, and this loose coupling changes how we think about action listeners. With action listeners, you have to explicitly tell an object you want to listen to it by calling a method on that object. In a loosely decoupled environment, action listeners are not appropriate. Instead, you implicitly tell an object you want to listen to it by exporting an OSGi service. That object knows all the OSGi services interested in it and calls them when necessary.
We establish Task
s as a replacement for action listeners to take advantage
of OSGi's loose coupling. A single task object can be thought of as an
action listener. Tasks implement the run
method, which is analogous
to the action listener's actionPerformed
method. A TaskFactory
creates tasks. You export a task factory as an OSGi service, not tasks.
Why even have task factories? Why not just use tasks directly?
Many times tasks need input parameters.
For example, if you're performing the zoom in task, how would the task know which network
view on which to perform the zoom? The run
method, after all,
does not allow for input parameters.
To deal with this issue, you create a task factory that can take
some parameters, then it returns a new task initialized with those parameters.
The task can obtain those parameters either through its constructor or as an
inner class that has access to the task factory's members.
Moreover, a well written task factory returns new instances of Task
each time
the task factory is invoked. A new task object per invocation neatly contains state that does not
spill over into separate task invocations. Incorrectly written task factories
return the same task instance each time it is invoked.
(If you know a functional programming language, here is an
analogy: a task is like an inner function
that the task factory function returns; the task gets inputs from free variables
defined in the task factory function.)
To illustrate tasks and task factories, here is how you create a menu item in Cytoscape's OSGi environment:
Sometimes when a task is executing, you need to run additional
tasks after it is done. TaskIterator
s let you create a series of tasks
where you choose the task to execute next, even while a task in that iterator
is executing.
When you write your task factory implementation, you return a TaskIterator
that
encapsulates your tasks. In most cases, you want to execute a
single task, not a sequence of tasks. In this case, when you write a task factory
implementation, return a new task iterator that only contains your single task.
Most of the time, you export a task factory service, and Cytoscape
invokes the task for you. Sometimes, you will want to invoke a task yourself.
In this case, you import the TaskManager
service
and call the TaskManager.execute(org.cytoscape.work.TaskIterator)
method to
run the task.
The analogy of tasks as an OSGi equivalent of action listeners sweeps many of its advantages under the rug. Here are some additional benefits of tasks:
SwingUtilities.invokeLater
.
TaskMonitor
.
TaskMonitor
s are passed in to the task's run
method.
In the task's run
method, you call its methods to update the user interface.
run
method. Cytoscape will catch the exception and
inform the user. When a task encounters an exception, it is considered good practice to catch
the exception and throw another exception with a high-level explanation that the user
can understand. For example, let's say your task is reading from a file, which can cause
an IOException
to be thrown. Because the explanation of the IOException
will
probably be unhelpful for the user, you should catch IOException
and throw your own
exception with an easily understandable explanation and some solutions for the user to remedy the problem.
cancel
method.
Long-running tasks must respond to user cancellations and return from the
run
method gracefully.
run
method does not explicitly use Swing,
the task can be used in other environments besides Swing, like
the command-line.
org.cytoscape.task
Package
As stated above, the primary purpose of task factories is that they accept input parameters
to be passed on to task instances. While developing Cytoscape, we found that many of our task
factories accept the same input parameters. At the top level of the org.cytoscape.task
package, we created a dozen specialized task factories that accept specific inputs.
Each interface in this package is a specialized task factory that groups together
task factory implementations based on common inputs.
For instance, the NodeViewTaskFactory
takes a node view as an input
parameter. Instead of a TaskFactory
, when you write an
implementation to NodeViewTaskFactory
, Cytoscape understands that your task factory
expects a node view as input. In terms of the user interface, Cytoscape will create a menu
item for your task factory when the user right-clicks on a node. Your task factory
will be invoked with the node view the user right-clicked on when the user selects your menu item.
In this respect, a NodeViewTaskFactory
defines an action that operates on a node view.
Subpackages in the org.cytoscape.task
package contain interfaces denoting
common user actions in Cytoscape. In most cases, you do not write implementations to these
interfaces. Instead, if you want to use the functionality specified by one of these
interfaces, you import them as an OSGi service.
Here's an example. The CloneNetworkTaskFactory
interface in the org.cytoscape.task.create
subpackage represents the action of
duplicating a network. If you want to duplicate a CyNetwork
, you would import the
CloneNetworkTaskFactory
as an OSGi service, then invoke its task by passing in
the CyNetwork
you want to duplicate to the task factory.
Sometimes you do write your own implementation to an interface in a
org.cytoscape.task
subpackage. For instance, let's say you
are developing your own method that analyzses the topology of a network.
You encapsulate the analysis in a task. You then export your task factory service
as an AnalyzeNetworkCollectionTaskFactory
.
Exporting your interface using AnalyzeNetworkCollectionTaskFactory
instead of just an NetworkCollectionTaskFactory
makes the purpose of your task more explicit.
Before exporting your task through a basic TaskFactory
,
it is important to think about the inputs the task needs and its objectives.
Is it the case that your task operates on a CyNetwork
?
Your task factory should implement NetworkTaskFactory
.
Specialized task factory interfaces in org.cytoscape.task
make
the inputs and the objectives explicit.
class MyTask implements Task {
public void run(TaskMonitor monitor) {
logger.info("Hey chef");
}
public void cancel() {
}
}
...
// Example that displays a user message at the bottom of the Cytoscape desktop
Logger logger = LoggerFactory.getLogger("CyUserMessages");
TaskFactory myTaskFactory = new TaskFactory() {
public TaskIterator createTaskIterator() {
return new TaskIterator(new MyTask());
}
public boolean isReady() {
return true;
}
};
Properties props = new Properties();
props.setProperty(PREFERRED_MENU,"Apps");
props.setProperty(TITLE,"Why, hello there children");
props.setProperty(MENU_GRAVITY,"1.0");
props.setProperty(TOOLTIP,"Demonstrates how cool the work framework is");
registerService(bc, myTaskFactory, TaskFactory.class, props);
// Example that approximates pi Logger logger = LoggerFactory.getLogger("CyUserMessages"); class MyPiTask implements Task { final int iterations = 1000; public void run(TaskMonitor monitor) { double pi = 2.0; for (int n = 1; n <= iterations; n++) { pi *= ((double) (2 * n) * (2 * n)) / ((2 * n - 1) * (2 * n + 1)); } logger.info("Our approximation of pi is: " + Double.toString(pi)); } public void cancel() { } } ... TaskFactory myTaskFactory = new TaskFactory() { public TaskIterator createTaskIterator() { return new TaskIterator(new MyPiTask()); } public boolean isReady() { return true; } }; Properties props = new Properties(); props.setProperty(PREFERRED_MENU,"Apps"); props.setProperty(TITLE,"Approximate Pi"); props.setProperty(MENU_GRAVITY,"1.0"); props.setProperty(TOOLTIP,"Approximates pi using the Wallis product"); registerService(bc, myTaskFactory, TaskFactory.class, props);
MyPiTask
so that it informs the user during this long calculation
by using the task monitor.
class MyPiTask implements Task { final int iterations = 1000; public void run(TaskMonitor monitor) { monitor.setTitle("Calculating Pi"); double pi = 2.0; for (int n = 1; n <= iterations; n++) { monitor.setProgress(((double) n) / iterations); pi *= ((double) (2 * n) * (2 * n)) / ((2 * n - 1) * (2 * n + 1)); } logger.info("Our approximation of pi is: " + Double.toString(pi)); } public void cancel() { } }
cancel
method. The main challenge of dealing with cancellation
is that the thread executing run
is different
from the one executing cancel
. We have to come up with a way
to communicate cancellation from cancel
to run
.
Here is how we deal with this issue:
class MyPiTask implements Task { final int iterations = 1000; volatile boolean cancelled = false; public void run(TaskMonitor monitor) { monitor.setTitle("Calculating Pi"); double pi = 2.0; for (int n = 1; n <= iterations; n++) { if (cancelled) break; monitor.setProgress(((double) n) / iterations); pi *= ((double) (2 * n) * (2 * n)) / ((2 * n - 1) * (2 * n + 1)); } if (!cancelled) logger.info("Our approximation of pi is: " + Double.toString(pi)); } public void cancel() { cancelled = true; } }When the user hits the Cancel button, Cytoscape invokes the
cancel
method.
This method switches the cancelled
variable to true. When run
starts another
iteration of the for
loop, it checks the cancelled
variable.
Since cancelled
was flipped, it stops the loop.
In other words, the cancel
method tells the run
method to stop through
the cancelled
variable.
Communicating cancellation through a boolean variable works well for tasks with loops, since each loop iteration can just check if the variable has changed and stop. Tasks that do I/O operations, however, require more consideration, like a task that downloads a file from some URL. There are several ways you can deal with cancellation during an I/O operation:
run
. When
run
first starts, fill in this variable with the method
Thread.currentThread()
. Now the cancel
method
can know the thread that is executing run
.
When cancellation occurs, call Thread.interrupt()
on the thread executing run
. Interrupting a thread
in an I/O operation will sometimes stop it. Here's how this would look:
class MyTask implements Task { volatile Thread runThread = null; public void run(TaskMonitor monitor) { runThread = Thread.currentThread(); ... // complicated I/O operations } public void cancel() { runThread.interrupt(); } }
run
method is reading from an InputStream
,
a Reader
, or a Socket
,
call close
from the cancel
method.
This will stop the I/O operation immediately in the run
method.
It is discouraged to use URLConnection
, because
it is not possible to cancel a pending connection. Consider
using Apache HttpClient library instead.
java.nio
. Since non-blocking I/O operations
are usually done in a loop, it works well with the approach of checking for cancellation
from a variable. Using non-blocking I/O is more elegant, but it takes
a lot more effort to write.
iterations
a tunable whose value the user can specify.
Task myPiTask = new Task() { @Tunable(description="How many iterations of the Wallis product to compute?") public int iterations = 1000; boolean cancelled = false; public void run(TaskMonitor monitor) { monitor.setTitle("Calculating Pi"); double pi = 2.0; for (int n = 1; n <= iterations; n++) { if (cancelled) break; monitor.setProgress(((double) n) / iterations); pi *= (2 * n) * (2 * n) / ((2 * n - 1) * (2 * n + 1)); } if (!cancelled) logger.info("Our approximation of pi is: " + Double.toString(pi)); } public void cancel() { cancelled = true; } };
Copyright 2011-2015 Cytoscape Consortium. All rights reserved.