1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.javaflow; 18 19 import java.io.Serializable; 20 import org.apache.commons.javaflow.bytecode.StackRecorder; 21 import org.apache.commons.javaflow.utils.ReflectionUtils; 22 import org.apache.commons.logging.Log; 23 import org.apache.commons.logging.LogFactory; 24 25 /** 26 * Snapshot of a thread execution state. 27 * 28 * <p> 29 * A {@link Continuation} object is an immutable object that captures everything in 30 * the Java stack. This includes 31 * (1) current instruction pointer, 32 * (2) return addresses, and 33 * (3) local variables. 34 * 35 * <p> 36 * <tt>Continuation</tt> objects are used to restore the captured execution states 37 * later. 38 * 39 * @author <a href="mailto:stephan@apache.org">Stephan Michels</a> 40 * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a> 41 * @version CVS $Revision: 480487 $ 42 */ 43 public final class Continuation implements Serializable { 44 45 private static final Log log = LogFactory.getLog(Continuation.class); 46 private static final long serialVersionUID = 2L; 47 48 private final StackRecorder stackRecorder; 49 50 /** 51 * Create a new continuation, which continue a previous continuation. 52 */ 53 private Continuation( final StackRecorder pStackRecorder ) { 54 stackRecorder = pStackRecorder; 55 } 56 57 58 /** 59 * get the current context. 60 * 61 * <p> 62 * This method returns the same context object given to {@link #startWith(Runnable, Object)} 63 * or {@link #continueWith(Continuation, Object)}. 64 * 65 * <p> 66 * A different context can be used for each run of a continuation, so 67 * this mechanism can be used to associate some state with each execution. 68 * 69 * @return 70 * null if this method is invoked outside {@link #startWith(Runnable, Object)} 71 * or {@link #continueWith(Continuation, Object)} . 72 */ 73 public static Object getContext() { 74 return StackRecorder.get().getContext(); 75 } 76 77 /** 78 * Creates a new {@link Continuation} object from the specified {@link Runnable} 79 * object. 80 * 81 * <p> 82 * Unlike the {@link #startWith(Runnable)} method, this method doesn't actually 83 * execute the <tt>Runnable</tt> object. It will be executed when 84 * it's {@link #continueWith(Continuation) continued}. 85 * 86 * @return 87 * always return a non-null valid object. 88 */ 89 public static Continuation startSuspendedWith( final Runnable pTarget ) { 90 return new Continuation(new StackRecorder(pTarget)); 91 } 92 93 /** 94 * Starts executing the specified {@link Runnable} object in an environment 95 * that allows {@link Continuation#suspend()}. 96 * 97 * <p> 98 * This is a short hand for <tt>startWith(target,null)</tt>. 99 * 100 * @see #startWith(Runnable, Object). 101 */ 102 public static Continuation startWith( final Runnable pTarget ) { 103 return startWith(pTarget, null); 104 } 105 106 /** 107 * Starts executing the specified {@link Runnable} object in an environment 108 * that allows {@link Continuation#suspend()}. 109 * 110 * This method blocks until the continuation suspends or completes. 111 * 112 * @param pTarget 113 * The object whose <tt>run</tt> method will be executed. 114 * @param pContext 115 * This value can be obtained from {@link #getContext()} until this method returns. 116 * Can be null. 117 * @return 118 * If the execution completes and there's nothing more to continue, return null. 119 * Otherwise, the execution has been {@link #suspend() suspended}, in which case 120 * a new non-null continuation is returned. 121 * @see #getContext() 122 */ 123 public static Continuation startWith( final Runnable pTarget, final Object pContext ) { 124 if(pTarget == null) { 125 throw new IllegalArgumentException("target is null"); 126 } 127 128 log.debug("starting new flow from " + ReflectionUtils.getClassName(pTarget) + "/" + ReflectionUtils.getClassLoaderName(pTarget)); 129 130 return continueWith(new Continuation(new StackRecorder(pTarget)), pContext); 131 } 132 133 /** 134 * Resumes the execution of the specified continuation from where it's left off. 135 * 136 * <p> 137 * This is a short hand for <tt>continueWith(resumed,null)</tt>. 138 * 139 * @see #continueWith(Continuation, Object) 140 */ 141 public static Continuation continueWith(final Continuation pOldContinuation) { 142 return continueWith(pOldContinuation, null); 143 } 144 145 /** 146 * Resumes the execution of the specified continuation from where it's left off 147 * and creates a new continuation representing the new state. 148 * 149 * This method blocks until the continuation suspends or completes. 150 * 151 * @param pOldContinuation 152 * The resumed continuation to be executed. Must not be null. 153 * @param pContext 154 * This value can be obtained from {@link #getContext()} until this method returns. 155 * Can be null. 156 * @return 157 * If the execution completes and there's nothing more to continue, return null. 158 * Otherwise, the execution has been {@link #suspend() suspended}, in which case 159 * a new non-null continuation is returned. 160 * @see #getContext() 161 */ 162 public static Continuation continueWith(final Continuation pOldContinuation, final Object pContext) { 163 if(pOldContinuation == null) { 164 throw new IllegalArgumentException("continuation parameter must not be null."); 165 } 166 167 log.debug("continueing with continuation " + ReflectionUtils.getClassName(pOldContinuation) + "/" + ReflectionUtils.getClassLoaderName(pOldContinuation)); 168 169 while(true) { 170 try { 171 StackRecorder pStackRecorder = 172 new StackRecorder(pOldContinuation.stackRecorder).execute(pContext); 173 if(pStackRecorder == null) { 174 return null; 175 } else { 176 return new Continuation(pStackRecorder); 177 } 178 } catch (ContinuationDeath e) { 179 if(e.mode.equals(ContinuationDeath.MODE_AGAIN)) 180 continue; // re-execute immediately 181 if(e.mode.equals(ContinuationDeath.MODE_EXIT)) 182 return null; // no more thing to continue 183 if(e.mode.equals(ContinuationDeath.MODE_CANCEL)) 184 return pOldContinuation; 185 throw new IllegalStateException("Illegal mode "+e.mode); 186 } 187 } 188 } 189 190 public boolean isSerializable() { 191 return stackRecorder.isSerializable(); 192 } 193 194 /** 195 * Stops the running continuation. 196 * 197 * <p> 198 * This method can be only called inside {@link #continueWith} or {@link #startWith} methods. 199 * When called, the thread returns from the above methods with a new {@link Continuation} 200 * object that captures the thread state. 201 * 202 * @throws IllegalStateException 203 * if this method is called outside the {@link #continueWith} or {@link #startWith} methods. 204 */ 205 public static void suspend() { 206 StackRecorder.suspend(); 207 } 208 209 /** 210 * Completes the execution of the running continuation. 211 * 212 * <p> 213 * This method can be only called inside {@link #continueWith} or {@link #startWith} methods. 214 * When called, the thread returns from the above methods with null, 215 * indicating that there's nothing more to continue. 216 * 217 * <p> 218 * This method is similiar to how {@link System#exit(int)} works for JVM. 219 */ 220 public static void exit() { 221 throw new ContinuationDeath(ContinuationDeath.MODE_EXIT); 222 } 223 224 /** 225 * Jumps to where the execution was resumed. 226 * 227 * <p> 228 * This method can be only called inside {@link #continueWith} or {@link #startWith} methods. 229 * When called, the execution jumps to where it was resumed 230 * (if the execution has never resumed before, from the beginning 231 * of {@link Runnable#run()}.) 232 * 233 * <p> 234 * Consider the following example: 235 * 236 * <pre> 237 * Continuation.suspend(); 238 * System.out.println("resumed"); 239 * 240 * r = new Random().nextInt(5); 241 * if(r!=0) { 242 * System.out.println("do it again"); 243 * Continuation.again(); 244 * } 245 * 246 * System.out.println("done"); 247 * </pre> 248 * 249 * <p> 250 * This program produces an output like this (the exact number of 251 * 'do it again' depends on each execution, as it's random.) 252 * 253 * <pre> 254 * resumed 255 * do it again 256 * resumed 257 * do it again 258 * resumed 259 * do it again 260 * resumed 261 * done 262 * </pre> 263 * 264 * <p> 265 * The calling {@link Continuation#startWith(Runnable)} method and 266 * {@link Continuation#continueWith(Continuation)} method does not 267 * return when a program running inside uses this method. 268 */ 269 public static void again() { 270 throw new ContinuationDeath(ContinuationDeath.MODE_AGAIN); 271 } 272 273 /** 274 * Jumps to where the execution was resumed, and suspend execution. 275 * 276 * <p> 277 * This method almost works like the {@link #again()} method, 278 * but instead of re-executing, this method first suspends the execution. 279 * 280 * <p> 281 * Therefore, 282 * the calling {@link Continuation#startWith(Runnable)} method and 283 * {@link Continuation#continueWith(Continuation)} method 284 * return when a program running inside uses this method. 285 */ 286 public static void cancel() { 287 throw new ContinuationDeath(ContinuationDeath.MODE_CANCEL); 288 } 289 290 public String toString() { 291 return "Continuation@" + hashCode() + "/" + ReflectionUtils.getClassLoaderName(this); 292 } 293 }