Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
StackRecorder |
|
| 2.75;2.75 |
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.bytecode; | |
18 | ||
19 | import org.apache.commons.javaflow.utils.ReflectionUtils; | |
20 | import org.apache.commons.javaflow.ContinuationDeath; | |
21 | import org.apache.commons.logging.Log; | |
22 | import org.apache.commons.logging.LogFactory; | |
23 | ||
24 | /** | |
25 | * Adds additional behaviors necessary for stack capture/restore | |
26 | * on top of {@link Stack}. | |
27 | * | |
28 | * @author Kohsuke Kawaguchi | |
29 | */ | |
30 | public final class StackRecorder extends Stack { | |
31 | ||
32 | 0 | private static final Log log = LogFactory.getLog(StackRecorder.class); |
33 | private static final long serialVersionUID = 2L; | |
34 | ||
35 | 0 | private static final ThreadLocal threadMap = new ThreadLocal(); |
36 | ||
37 | /** | |
38 | * True, if the continuation restores the previous stack trace to the last | |
39 | * invocation of suspend(). | |
40 | * | |
41 | * <p> | |
42 | * This field is accessed from the byte code injected into application code, | |
43 | * and therefore defining a wrapper get method makes it awkward to | |
44 | * step through the user code. That's why this field is public. | |
45 | */ | |
46 | 0 | public transient boolean isRestoring = false; |
47 | ||
48 | /** | |
49 | * True, is the continuation freeze the strack trace, and stops the | |
50 | * continuation. | |
51 | * | |
52 | * @see #isRestoring | |
53 | */ | |
54 | 0 | public transient boolean isCapturing = false; |
55 | ||
56 | /** Context object given by the user */ | |
57 | private transient Object context; | |
58 | ||
59 | /** | |
60 | * Creates a new empty {@link StackRecorder} that runs the given target. | |
61 | */ | |
62 | public StackRecorder( final Runnable pTarget ) { | |
63 | 0 | super(pTarget); |
64 | 0 | } |
65 | ||
66 | /** | |
67 | * Creates a clone of the given {@link StackRecorder}. | |
68 | */ | |
69 | public StackRecorder(final Stack pParent) { | |
70 | 0 | super(pParent); |
71 | 0 | } |
72 | ||
73 | public static void suspend() { | |
74 | 0 | log.debug("suspend()"); |
75 | ||
76 | 0 | final StackRecorder stackRecorder = get(); |
77 | 0 | if(stackRecorder == null) { |
78 | 0 | throw new IllegalStateException("No continuation is running"); |
79 | } | |
80 | ||
81 | 0 | stackRecorder.isCapturing = !stackRecorder.isRestoring; |
82 | 0 | stackRecorder.isRestoring = false; |
83 | 0 | } |
84 | ||
85 | public StackRecorder execute(final Object context) { | |
86 | 0 | final StackRecorder old = registerThread(); |
87 | try { | |
88 | 0 | isRestoring = !isEmpty(); // start restoring if we have a filled stack |
89 | 0 | this.context = context; |
90 | ||
91 | 0 | if (isRestoring) { |
92 | 0 | log.debug("Restoring state of " + ReflectionUtils.getClassName(runnable) + "/" + ReflectionUtils.getClassLoaderName(runnable)); |
93 | } | |
94 | ||
95 | 0 | log.debug("calling runnable"); |
96 | 0 | runnable.run(); |
97 | ||
98 | 0 | if (isCapturing) { |
99 | 0 | if(isEmpty()) { |
100 | // if we were really capturing the stack, at least we should have | |
101 | // one object in the reference stack. Otherwise, it usually means | |
102 | // that the application wasn't instrumented correctly. | |
103 | 0 | throw new IllegalStateException("stack corruption. Is "+runnable.getClass()+" instrumented for javaflow?"); |
104 | } | |
105 | // top of the reference stack is the object that we'll call into | |
106 | // when resuming this continuation. we have a separate Runnable | |
107 | // for this, so throw it away | |
108 | 0 | popReference(); |
109 | 0 | return this; |
110 | } else { | |
111 | 0 | return null; // nothing more to continue |
112 | } | |
113 | 0 | } catch(ContinuationDeath cd) { |
114 | // this isn't an error, so no need to log | |
115 | 0 | throw cd; |
116 | 0 | } catch(Error e) { |
117 | 0 | log.error(e.getMessage(),e); |
118 | 0 | throw e; |
119 | 0 | } catch(RuntimeException e) { |
120 | 0 | log.error(e.getMessage(),e); |
121 | 0 | throw e; |
122 | } finally { | |
123 | 0 | this.context = null; |
124 | 0 | deregisterThread(old); |
125 | 0 | } |
126 | } | |
127 | ||
128 | public Object getContext() { | |
129 | 0 | return context; |
130 | } | |
131 | ||
132 | /** | |
133 | * Bind this stack recorder to running thread. | |
134 | */ | |
135 | private StackRecorder registerThread() { | |
136 | 0 | StackRecorder old = get(); |
137 | 0 | threadMap.set(this); |
138 | 0 | return old; |
139 | } | |
140 | ||
141 | /** | |
142 | * Unbind the current stack recorder to running thread. | |
143 | */ | |
144 | private void deregisterThread(final StackRecorder old) { | |
145 | 0 | threadMap.set(old); |
146 | 0 | } |
147 | ||
148 | /** | |
149 | * Return the continuation, which is associated to the current thread. | |
150 | */ | |
151 | public static StackRecorder get() { | |
152 | 0 | return (StackRecorder)threadMap.get(); |
153 | } | |
154 | } |