package com.sap.runlet.abstractinterpreter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
/**
* If a {@link DebugSession} is attached to a {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType>}, the interpreter will
* check for various sorts of breakpoints. The breakpoints are managed by an instance of this
* class.<p>
*
* Furthermore, an debug session offers the interfaces to suspend, resume and terminate
* a running session. It also manages the stepping behavior of an interpreter in the sense
* that it will negotiate with the interpreter when to suspend execution next, based on
* the command given to the debug session, such as "step into" or "step over."<p>
*
* The debug session is also the interface for remote-controlling the execution of an interpreter
* in debug mode.
*
* @author Axel Uhl (D043530)
*/
public class DebugSession {
/**
* The listener will be notified about suspend/resume/terminate/etc. events. May be <tt>null</tt>
* which means that no events will be communicated by this session.
*/
private DebugListener listener;
/**
* These are the elements at whose evaluation the interpreter to which this object is attached
* is supposed to stop.
*/
private Set<EObject> elementBreakpoints;
/**
* Step-into breakpoints, managed per {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType> interpreter}. No need to remember
* the stack level where this was requested because the interpreter will stop at the next
* opportunity in any case, regardless the value because this is what "step into" is actually
* asking for. Think about stepping into while at the end of a method...
*/
private Set<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> stepIntoBreakpoints;
/**
* Step-over breakpoints, managed per {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType> interpreter}. The value represents
* the index of the stack frame <tt>s</tt> in {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType>#getCallstack} such that when
* <tt>t</tt> was the current stack frame when the "step over"
* was issued, <tt>s</tt> is <tt>t</tt>'s outermost {@link StackFrame#getScopeParent() scope parent}
* if <tt>t</tt> has a non-<tt>null</tt> scope parent, or <tt>t</tt> if <tt>t</tt> has no
* scope parent.
*/
private Map<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, Integer> stepOverBreakpoints;
/**
* Step-return breakpoints, managed per {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType> interpreter}. The value represents
* the index of the stack frame in {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType>#getCallstack} where the "step return"
* was issued.
*/
private Map<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, Integer> stepReturnBreakpoints;
/**
* Contains those interpreters that shall be suspended. When one of those interpreters calls
* to announce an evaluation, this will suspend that interpreter.
*/
private Set<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>> interpretersToSuspend;
/**
* For each interpreter can maintain the reason why the interpreter resumes
*/
private Map<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, DebugListener.ResumeReason> resumeReasons;
public DebugSession(DebugListener listener) {
this.listener = listener;
elementBreakpoints = new HashSet<EObject>();
interpretersToSuspend = new HashSet<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>>();
stepIntoBreakpoints = new HashSet<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>>();
stepOverBreakpoints = new HashMap<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, Integer>();
stepReturnBreakpoints = new HashMap<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, Integer>();
resumeReasons = new HashMap<AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?>, DebugListener.ResumeReason>();
}
public void addBreakpoint(EObject elementToStopAt) {
elementBreakpoints.add(elementToStopAt);
}
public void removeBreakpoint(EObject elementToNoLongerStopAt) {
elementBreakpoints.remove(elementToNoLongerStopAt);
}
/**
* Requests that the <tt>interpreter</tt> be suspended at the next possible point
*/
public synchronized void suspend(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
interpretersToSuspend.add(interpreter);
}
/**
* Resumes the interpreter if it is currently suspended or removes the request to
* suspend it.
* @param reason TODO
*/
public synchronized void resume(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter, DebugListener.ResumeReason reason) {
interpretersToSuspend.remove(interpreter);
resumeReasons.put(interpreter, reason);
notifyAll();
}
/**
* An interpreter connected to this debug session calls this operation before each evaluation of
* a model element and before it is set to {@link AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType>#isRunning()} for the first
* evaluation. In case a breakpoint is set for the element or the interpreter is supposed to get
* suspended, this method will block. If it was a breakpoint, the interpreter is entered into
* those to be suspended. Only once the interpreter is resumed will the calling thread get
* unblocked.
*/
public void aboutToEvaluate(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter, EObject element) {
if (!interpreter.isRunning()) {
if (listener != null) {
listener.started(interpreter);
}
}
if (!interpreter.getCallstack().isEmpty()) {
interpreter.getCallstack().peek().setCurrentlyEvaluating(element);
}
boolean suspended = false;
DebugListener.SuspendReason suspendReason = null;
if (interpretersToSuspend.contains(interpreter)) {
suspendReason = DebugListener.SuspendReason.CLIENT_REQUEST;
}
if (elementBreakpoints.contains(element)) {
interpretersToSuspend.add(interpreter);
suspendReason = DebugListener.SuspendReason.BREAKPOINT;
}
suspendReason = setSuspendForInterpreterBasedOnSteppingRequests(interpreter, suspendReason);
if (interpretersToSuspend.contains(interpreter) && listener != null) {
suspended = true;
listener.suspended(interpreter, suspendReason);
}
while (interpretersToSuspend.contains(interpreter)) {
synchronized(this) {
try {
wait();
} catch (InterruptedException e) {
// no problem; just try again
}
}
}
if (suspended) {
if (listener != null) {
listener.resumed(interpreter, resumeReasons.get(interpreter));
}
}
}
private DebugListener.SuspendReason setSuspendForInterpreterBasedOnSteppingRequests(
AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter, DebugListener.SuspendReason suspendReason) {
if (stepIntoBreakpoints.contains(interpreter)) {
interpretersToSuspend.add(interpreter); // step into means stop at the next opportunity
stepIntoBreakpoints.remove(interpreter);
suspendReason = DebugListener.SuspendReason.STEP_END;
} else {
if (stepOverBreakpoints.containsKey(interpreter)) {
int requestLevel = stepOverBreakpoints.get(interpreter);
StackFrame<?, ?, ?, ?> currentFrame = null;
int currentLevel = 0;
if (interpreter.getCallstack().size() > 0) {
currentFrame = interpreter.getCallstack().peek();
while (currentFrame.getScopeParent() != null) {
currentFrame = currentFrame.getScopeParent();
}
currentLevel = interpreter.getCallstack().indexOf(currentFrame);
}
if (currentLevel <= requestLevel) {
interpretersToSuspend.add(interpreter);
stepOverBreakpoints.remove(interpreter);
suspendReason = DebugListener.SuspendReason.STEP_END;
}
}
if (stepReturnBreakpoints.containsKey(interpreter)) {
int requestLevel = stepReturnBreakpoints.get(interpreter);
StackFrame<?, ?, ?, ?> currentFrame = null;
int currentLevel = 0;
if (interpreter.getCallstack().size() > 0) {
currentFrame = interpreter.getCallstack().peek();
while (currentFrame.getScopeParent() != null) {
currentFrame = currentFrame.getScopeParent();
}
currentLevel = interpreter.getCallstack().indexOf(currentFrame);
}
if (currentLevel < requestLevel) {
interpretersToSuspend.add(interpreter);
stepReturnBreakpoints.remove(interpreter);
suspendReason = DebugListener.SuspendReason.STEP_END;
}
}
}
return suspendReason;
}
protected void terminated(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
if (listener != null) {
listener.terminated(interpreter);
}
}
public boolean isSuspended(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
return interpretersToSuspend.contains(interpreter);
}
public void stepInto(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
stepIntoBreakpoints.add(interpreter);
resume(interpreter, DebugListener.ResumeReason.STEP_INTO);
}
public void stepOver(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
stepOverBreakpoints.put(interpreter, interpreter.getCallstack().size()-1);
resume(interpreter, DebugListener.ResumeReason.STEP_OVER);
}
public void stepReturn(AbstractRunletInterpreter<?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?> interpreter) {
stepReturnBreakpoints.put(interpreter, interpreter.getCallstack().size()-1);
resume(interpreter, DebugListener.ResumeReason.STEP_RETURN);
}
}