package gw.lang.debugger;
import gw.config.CommonServices;
import gw.lang.parser.IManagedContext;
import gw.lang.parser.ISymbolTable;
import gw.lang.parser.InstrumentationManager;
import gw.lang.parser.RuntimeInfoAtStatement;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.ICompilableType;
import gw.lang.reflect.gs.IGosuClass;
import gw.util.Stack;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @Deprecated The debugger API is obsolete now that Gosu compiles directly to bytecode
*
* Copyright 2010 Guidewire Software, Inc.
*/
public abstract class AbstractDebugManager implements IDebugManager
{
private static boolean g_bDebuggable;
private static Boolean g_retainDebugInfo;
protected IDebugDriver _driver;
protected final List<BreakPoint> _breakPoints;
protected boolean _bDebugging;
protected AbstractDebugManager()
{
_breakPoints = new ArrayList<BreakPoint>();
}
/**
* Subclasses must implement this to provide their thread-local runtime symbol
* table.
*
* @return The symbol table
*/
abstract protected ISymbolTable getRuntimeSymbolTable();
public void setDebugging( boolean bDebugging, IDebugDriver driver )
{
// Refresh the type system when we start debugging
makeDebuggable();
if( !_bDebugging && bDebugging )
{
TypeSystem.refresh( true );
}
_bDebugging = bDebugging;
_driver = driver;
if( bDebugging )
{
InstrumentationManager.addRuntimeObserver( this );
}
else
{
InstrumentationManager.removeRuntimeObserver( this );
}
}
public IDebugDriver getDebugDriver() {
return _driver;
}
private void makeDebuggable()
{
if( !isDebuggable() )
{
TypeSystem.refresh();
}
setDebuggable( true );
}
public boolean isDebugging()
{
return _bDebugging;
}
public void addBreakPoint( BreakPoint bp )
{
// Refresh the type system when we start debugging
makeDebuggable();
synchronized( _breakPoints )
{
_breakPoints.remove( bp ); // remove the old
_breakPoints.add( bp ); // and the new
}
}
public void removeBreakPoint( BreakPoint bp )
{
// Refresh the type system when we start debugging
makeDebuggable();
synchronized( _breakPoints )
{
_breakPoints.remove( bp );
}
}
public List getBreakPoints()
{
// Refresh the type system when we start debugging
makeDebuggable();
synchronized( _breakPoints )
{
return Collections.unmodifiableList( _breakPoints );
}
}
@Override
public void onBeforeExecute( RuntimeInfoAtStatement ctx )
{
if( !isDebugging() )
{
return;
}
IType gsClass = TypeSystem.getCurrentCompilingType();
if( gsClass instanceof IGosuClass )
{
return;
}
DebugLocationContext debugCtx = getDebugLocationContext( ctx );
onBeforeExecute( debugCtx );
}
protected void onBeforeExecute( DebugLocationContext ctx )
{
if( !isDebugging() )
{
return;
}
if( !isDebuggable( ctx ) )
{
return;
}
if( !getThreadDebuggableInGosu() ) {
return;
}
switch( _driver.getCommand() )
{
case CMD_RUN:
if( isActiveBreakPoint( ctx ) )
{
_driver.onLocation( ctx );
}
break;
case CMD_STEP_OVER:
if( (!isSameLineNumber() &&
!isDescendantCall()) ||
isActiveBreakPoint( ctx ) )
{
_driver.onLocation( ctx );
}
break;
case CMD_STEP_INTO:
_driver.onLocation( ctx );
break;
}
}
protected boolean isDebuggable( DebugLocationContext ctx )
{
return ctx == null || ctx.getContext() == null || ctx.getContext().isDebuggable();
}
private boolean isSameLineNumber()
{
Stack current = RuntimeInfoAtStatement.getCallStack();
Stack last = (Stack)_driver.getCallStack();
return current.size() > 0 && last.size() > 0 && current.size() == last.size() &&
((RuntimeInfoAtStatement)current.peek()).getLineNumber() == ((RuntimeInfoAtStatement)last.peek()).getLineNumber();
}
/**
* The idea is that a call is assumed to be descendant if it's call stack is
* larger than the last call stack. Also if the stacks roots are not the same
* we assume the call is descendant e.g., a call to a java method that in
* turn invokes some gosu dynamically, as is the case for a lot of PCF
* classes.
*
* @return True if call descends from current execution stack
*/
private boolean isDescendantCall()
{
Stack current = RuntimeInfoAtStatement.getCallStack();
Stack last = (Stack)_driver.getCallStack();
return !areCallStackRootsSame( current, last ) ||
current.size() > last.size();
}
private boolean areCallStackRootsSame( Stack current, Stack last )
{
RuntimeInfoAtStatement currentRoot = getRoot( current );
RuntimeInfoAtStatement lastRoot = getRoot( last );
return currentRoot == null || lastRoot == null
? currentRoot == lastRoot
: currentRoot.getOrigin() == lastRoot.getOrigin();
}
private RuntimeInfoAtStatement getRoot( Stack currentLocationCallStack )
{
if( currentLocationCallStack != null && !currentLocationCallStack.isEmpty() )
{
return (RuntimeInfoAtStatement)currentLocationCallStack.get( 0 );
}
return null;
}
protected boolean isActiveBreakPoint( DebugLocationContext peLocationCtx )
{
synchronized( _breakPoints )
{
//noinspection ForLoopReplaceableByForEach
for( int i = 0; i < _breakPoints.size(); i++ )
{
BreakPoint bp = _breakPoints.get( i );
if( !BreakPoint.areBreakpointsMuted() && bp.isActive() && peLocationCtx.equals( bp.getContext() ) )
{
return true;
}
}
return false;
}
}
private DebugLocationContext getDebugLocationContext( RuntimeInfoAtStatement ctx )
{
IGosuClass aClass = (IGosuClass)ctx.getEnclosingType();
if( aClass == null )
{
CommonServices.getEntityAccess().getLogger().error( "No enclosing type for ctx in " + ctx.getEnclosingFunction() );
}
IManagedContext context = aClass.getManagedContext();
if( context == null )
{
CommonServices.getEntityAccess().getLogger().error( "No context for " + aClass.getName() );
}
IDebugContextProperties debugCtx = context.getContext();
if( debugCtx == null )
{
CommonServices.getEntityAccess().getLogger().error( "No debugCtx context for " + aClass.getName() );
}
return new DebugLocationContext( debugCtx, ctx.getLineNumber(), 0 );
}
public static boolean isDebuggable()
{
if( g_retainDebugInfo == null )
{
// A production server can be configured to RetainDebugInfo, in which case debug info is never cleared. This
// paves the way for a production server to be debuggable via studio without requiring a type sys refresh to
// recompile and include debug info.
g_retainDebugInfo = CommonServices.getEntityAccess().isRetainDebugInfo();
}
return g_bDebuggable || g_retainDebugInfo;
}
public static void setDebuggable( boolean bDebuggable )
{
g_bDebuggable = bDebuggable;
}
@Override
public boolean observes( ICompilableType gsClass )
{
return isDebugging();
}
// Access to gw.lang.reflect.StudioServiceHelper in pl module.
private Object _studioServiceHelper;
private Method _getThreadDebuggable;
private boolean getThreadDebuggableInGosu() {
try {
if(_getThreadDebuggable == null) {
Class<?> studioServiceHelperClass = Class.forName("gw.lang.reflect.StudioServiceHelper");
Method _getInstance = studioServiceHelperClass.getMethod("getInstance");
_studioServiceHelper = _getInstance.invoke(null);
_getThreadDebuggable = studioServiceHelperClass.getMethod("isSafeToDebug");
}
Boolean result = (Boolean) _getThreadDebuggable.invoke(_studioServiceHelper);
return result.booleanValue();
} catch(Exception ex) {
return false;
}
}
}