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 }