/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dragome.callbackevictor.enhancers;
/**
* Adds additional behaviors necessary for stack capture/restore
* on top of {@link Stack}.
*/
public final class StackRecorder extends Stack {
private static final long serialVersionUID = 2L;
private static final ThreadLocal<StackRecorder> threadMap = new ThreadLocal<StackRecorder>();
/**
* True, if the continuation restores the previous stack trace to the last
* invocation of suspend().
*
* <p>
* This field is accessed from the byte code injected into application code,
* and therefore defining a wrapper get method makes it awkward to
* step through the user code. That's why this field is public.
*/
public transient boolean isRestoring = false;
/**
* True, is the continuation freeze the strack trace, and stops the
* continuation.
*
* @see #isRestoring
*/
public transient boolean isCapturing = false;
/** Context object passed by the client code to continuation during resume */
private transient Object context;
/** Result object passed by the continuation to the client code during suspend */
public transient Object value;
/**
* Creates a new empty {@link StackRecorder} that runs the given target.
*/
public StackRecorder( final Runnable pTarget ) {
super(pTarget);
}
/**
* Creates a clone of the given {@link StackRecorder}.
*/
public StackRecorder(final Stack pParent) {
super(pParent);
}
public static Object suspend(final Object value) {
final StackRecorder stackRecorder = get();
if(stackRecorder == null) {
throw new IllegalStateException("No continuation is running");
}
stackRecorder.isCapturing = !stackRecorder.isRestoring;
stackRecorder.isRestoring = false;
stackRecorder.value = value;
// flow breaks here, actual return will be executed in resumed continuation
// return in continuation to be suspended is executed as well but ignored
return stackRecorder.context;
}
public StackRecorder execute(final Object context) {
final StackRecorder old = registerThread();
try {
isRestoring = !isEmpty(); // start restoring if we have a filled stack
this.context = context;
if (isRestoring) {
}
runnable.run();
if (isCapturing) {
if(isEmpty()) {
// if we were really capturing the stack, at least we should have
// one object in the reference stack. Otherwise, it usually means
// that the application wasn't instrumented correctly.
throw new IllegalStateException("stack corruption. Is "+runnable.getClass()+" instrumented for javaflow?");
}
// top of the reference stack is the object that we'll call into
// when resuming this continuation. we have a separate Runnable
// for this, so throw it away
popReference();
return this;
} else {
return null; // nothing more to continue
}
} catch(final ContinuationDeath cd) {
// this isn't an error, so no need to log
throw cd;
} catch(final Error e) {
throw e;
} catch(final RuntimeException e) {
throw e;
} finally {
this.context = null;
deregisterThread(old);
}
}
public Object getContext() {
return context;
}
/**
* Bind this stack recorder to running thread.
*/
private StackRecorder registerThread() {
final StackRecorder old = get();
threadMap.set(this);
return old;
}
/**
* Unbind the current stack recorder to running thread.
*/
private void deregisterThread(final StackRecorder old) {
threadMap.set(old);
}
/**
* Return the continuation, which is associated to the current thread.
*/
public static StackRecorder get() {
return threadMap.get();
}
}