| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| Continuation |
|
| 2.3846153846153846;2.385 |
| 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 | 0 | 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 | 0 | private Continuation( final StackRecorder pStackRecorder ) { |
| 54 | 0 | stackRecorder = pStackRecorder; |
| 55 | 0 | } |
| 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 | 0 | 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 | 0 | 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 | 0 | 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 | 0 | if(pTarget == null) { |
| 125 | 0 | throw new IllegalArgumentException("target is null"); |
| 126 | } | |
| 127 | ||
| 128 | 0 | log.debug("starting new flow from " + ReflectionUtils.getClassName(pTarget) + "/" + ReflectionUtils.getClassLoaderName(pTarget)); |
| 129 | ||
| 130 | 0 | 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 | 0 | 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 | 0 | if(pOldContinuation == null) { |
| 164 | 0 | throw new IllegalArgumentException("continuation parameter must not be null."); |
| 165 | } | |
| 166 | ||
| 167 | 0 | log.debug("continueing with continuation " + ReflectionUtils.getClassName(pOldContinuation) + "/" + ReflectionUtils.getClassLoaderName(pOldContinuation)); |
| 168 | ||
| 169 | while(true) { | |
| 170 | try { | |
| 171 | 0 | StackRecorder pStackRecorder = |
| 172 | new StackRecorder(pOldContinuation.stackRecorder).execute(pContext); | |
| 173 | 0 | if(pStackRecorder == null) { |
| 174 | 0 | return null; |
| 175 | } else { | |
| 176 | 0 | return new Continuation(pStackRecorder); |
| 177 | } | |
| 178 | 0 | } catch (ContinuationDeath e) { |
| 179 | 0 | if(e.mode.equals(ContinuationDeath.MODE_AGAIN)) |
| 180 | 0 | continue; // re-execute immediately |
| 181 | 0 | if(e.mode.equals(ContinuationDeath.MODE_EXIT)) |
| 182 | 0 | return null; // no more thing to continue |
| 183 | 0 | if(e.mode.equals(ContinuationDeath.MODE_CANCEL)) |
| 184 | 0 | return pOldContinuation; |
| 185 | 0 | throw new IllegalStateException("Illegal mode "+e.mode); |
| 186 | } | |
| 187 | } | |
| 188 | } | |
| 189 | ||
| 190 | public boolean isSerializable() { | |
| 191 | 0 | 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 | 0 | StackRecorder.suspend(); |
| 207 | 0 | } |
| 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 | 0 | 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 | 0 | 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 | 0 | throw new ContinuationDeath(ContinuationDeath.MODE_CANCEL); |
| 288 | } | |
| 289 | ||
| 290 | public String toString() { | |
| 291 | 0 | return "Continuation@" + hashCode() + "/" + ReflectionUtils.getClassLoaderName(this); |
| 292 | } | |
| 293 | } |