Aranea Continuations project supports waiting on Swing and Aranea events by introducing high-level API over the JavaFlow library, hiding the mechanics of continuations behind an annotation, blocking calls and an instrumentation agent. Download from SourceForge or read the reference.
Continuations is one of the most hip and misused terms in Java community. Popularized by Geert Bevin and RIFE framework they allow to wait for event to happen and resume the method execution from the point on. A common point of comparison is the readln() DOS function that waited for user to input before resuming. In Java we are interested in waiting on events like user clicking a button.
Support for continuations in generic Java has been available for some time via the JavaFlow library. However their API was relatively low-level and clumsy in day-to-day development. As a part of Aranea Continuations project we built a higher level API on top of it, which can be illustrated by the following Swing example:
[code]
class BlockingSwingExample extends JPanel {
//Method with continuations
@Resumable
public void init() {
JButton button = new JButton(”Click on me!”);
add(button);
updateUI();
for (int i = 1; i <= 10; i++) {
SwingBlockingUtil.waitForOneAction(button);
System.out.println("You have clicked me " + i);
}
System.exit(0);
}
//Swing boilerplate
public static void main(String[] args) {
BlockingSwingExample panel =
new BlockingSwingExample();
JFrame frame = new JFrame();
frame.add(panel);
frame.setVisible(true);
panel.init();
}
}
[/code]
The important part of the Aranea Continuations API is the @Resumable annotation and SwingBlockingUtil.waitForOneAction(button) call. The first one tells that the current method can be resumed later while the second will wait until the listener is called. The code will wait till user clicks the button exactly ten times and then will exit (the example is available with distribution by running ant run-swing-example).
To make the example run we need to instrument the classes bytecode. In addition to the preprocessing supported by JavaFlow we have implemented support for Java Instrumentation API, so it becomes as easy as registering an instrumentation agent:
java -javaagent:aranea-blocking.jar=example example.BlockingSwingExample
To limit preprocessing the agent receives a comma-separated list of the application packages as a argument (in this case the example package).
Aranea Continuations provide support for waiting for Aranea MVC events same way as in Swing. The core API here is the AraneaBlockingUtil. However in addition to events we provide support for invoking flows and waiting for the return value:
[code]
@Resumable
String askForName() {
String result =
AraneaBlockingUtil.call(
getFlowCtx(),
new DialogWidget(”Please enter your name:”));
return result;
}
[/code]
This will display the user a separate page with an input box and return the value of the text box when user submits the form.
Looking under the hood blocking calls are implemented using a simple call/cc semantics:
[code]
static void waitForOneAction(final AbstractButton button) {
BlockingUtil.call(new IndirectCallable() {
void call(final ReturnContext returnCtx) {
final ActionListener[] aListener = new ActionListener[1];
aListener[0] = new ActionListener() {
public void actionPerformed(ActionEvent e) {
button.removeActionListener(aListener[0]);
returnCtx.returnWith(null);
}
};
button.addActionListener(aListener[0]);
}
});
}
[/code]
ReturnContext represents the current continuation that can be called to return from method once or several times:
[code]
public interface ReturnContext {
void returnWith(Object result);
void failWith(Throwable t);
}
[/code]
Using that it is trivial to add support for other events or actions. In fact we only implemented Swing blocking API because we realized how easy it is on top of the existing API.
Nothing comes for free and this is doubly so for bytecode modification. JavaFlow will definitely add a performance penalty to your application. However you should consider that it will almost exclusively add CPU consumption and will not stress IO or memory. This means that in most projects the impact will not be considerable or possibly even noticeable. This goes specially to desktop Swing applications, which are highly unlikely to be CPU-bound. Of course one should stress-test the application to make sure what the impact is in your environment.
The first release is available from SourceForge with a (brief) reference also available. We will continue to improve the project and plan to release as much of changes upstream as we can. We are also looking for contributions supporting blocking calls in other frameworks (like SWT). We would like to express our gratitude to Rein Raudjärv for developing the project and to Oleg Mürk for the original idea.