View Javadoc

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 }