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 | } |