Jumping in Java

Tagged:  

Anyone who has ever programmed (hopefully briefly) in some type of assembly language has gone to the bare metal in implementing loops, if statements, and other programming language features. When programming in assembly, these constructs use some form of J* jump instruction together with labels to accomplish the task. In higher-level languages, jump instructions are usually associated with the infamous and properly banned goto statements (which are unrestricted jumps), but restricted jumps are far more common: return statements, the throw keyword (for exceptions), and of course the standard loops, if statements, and the like.

Being mainly a Java programmer, I occasionally miss language features such as continuations and co-routines. I came upon just such a situation recently, and I wondered if it was possible to imitate a continuation in Java without needing to resort to more sophisticated techniques such as Javaflow or RIFE's own continuation library. What I found out was that I could, albeit in a limited way.

The use case was this: I needed to perform a certain sequence of operations in a rather linear order, but I needed to be able to pause the execution anywhere in the middle and be able to re-resume the operations exactly where I had left off. Here's an initial pass:

public void myOperations() {
     doOperation1();
     if(isPaused())
          return;
     doOperation2();
     if(isPaused())
          return;
     doOperation3();
     if(isPaused())
          return;
     doOperation4();
}

What I am doing is executing each operation, then checking to see if a paused flag has been set. If it has, I simply call return to exit the method (and return control to the calling method). But how to resume where I left off? Well, turns out there is another form of limited jumping in Java, inherited directly from C/C++: the switch statement. So here's a revised version with an implementation of re-entry:

public void myOperations() {
     doOperations(1);
}

public void reenterOperations(int phase) {
     doOperations(phase);
}

private void doOperations(int phase) 
{
     switch(phase) {
          case 1:   doOperation1();
                           if(isPaused())
                                return;
          case 2:   doOperation2();
                           if(isPaused())
                                return;
          case 3:   doOperation3();
                           if(isPaused())
                                return;
          case 4:   doOperation4();
                           if(isPaused())
                                return;
     }
}

What am I doing here? Well, I am simply jumping to the appropriate case based upon where I want to re-enter/continue. Notice here the lack of any break statement, because I am taking advantage of an odd "feature" in C/C++/Java switch statements, which is the ability to "fall through" to the next case unless the break is called. Hence, the method call reenterOperations(2) here would be the equivalent of executing this (assuming no pause):

doOperation2();
doOperation3();
doOperation4();

If I now alter the method signature to include a "context" object--a Map for holding variables (in a way similar to Commons Chain)--I can save variables into this object and re-access them when I re-enter the sequence of operations. Now all of my operations take the form:

public void doOperationXXX(Map context) { ... }

There, I've done it! It's not a real continuation or even co-routine, of course, but it's accomplished what I've wanted to do without resorting to bytecode manipulation or customized classloaders. And I've kept it relatively simple. A more sophisticated version would use an enum instead of an int, callbacks in order to set this enum value on the "visitor" or execution, plus a more flexible design using the "chain of responsibility" pattern a la Commons Chain. Which is what I am working on now...

What you do here is very similar to what RIFE does in byte-code manipulation, except that you don't restore the local variable stack. This means that you only care about the control flow of your application and not about the associated state at each continuation. The latest versions of RIFE don't need you to use a customer classloader anymore, you can register an agent, which is imho much cleaner.

Best regards

@Geert Yep, that's right. The "context object" here is a simple imitation of variable state here, certainly not as powerful or flexible as a full-blown continuation (it's actually more akin to a co-routine). But it fits the case at hand...I would certainly encourage anyone who needs something more sophisticated to look at RIFE (or Javaflow).

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <div> <blockquote> <object> <embed> <img> <param>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Copy the characters (respecting upper/lower case) from the image.