package gw.lang.debugger;
import gw.config.CommonServices;
import gw.lang.parser.ExternalSymbolMapForMap;
import gw.lang.parser.InstrumentationManager;
import gw.lang.parser.expressions.IProgram;
import gw.lang.parser.Keyword;
import gw.lang.parser.IStack;
import gw.lang.parser.ISymbolTable;
import gw.lang.parser.IGosuParser;
import gw.lang.parser.IScope;
import gw.lang.reflect.IScriptabilityModifier;
import gw.lang.parser.IActivationContext;
import gw.lang.parser.StandardScope;
import gw.lang.parser.ISymbol;
import gw.lang.parser.IExpression;
import gw.lang.parser.GosuParserFactory;
import gw.lang.parser.RuntimeInfoAtStatement;
import gw.lang.parser.StandardSymbolTable;
import gw.lang.parser.exceptions.ParseResultsException;
import gw.lang.GosuShop;
import gw.lang.reflect.IFunctionType;
import gw.lang.reflect.java.IJavaType;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.util.CaseInsensitiveHashMap;
import gw.util.StreamUtil;
import gw.util.Stack;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
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 AbstractDebugDriver implements IDebugDriver, IDebugService
{
private static final String ARRAY_LENGTH_PREFIX = "length = ";
private static final String[]
EXCLUDE_SYMBOLS =
{
Keyword.KW_super.toString(),
IStack.UNASSIGNED_LABEL,
"print",
"now",
};
private static final GosuClassDebugContextProperties UNKNOWN_CONTEXT =
new GosuClassDebugContextProperties("UnknownContext", "unknown");
private DebugLocationContext _onLocationContext;
private ISymbolTable _suspendedSymbolTable;
private IDebugDriver.DebugCommands _cmd;
private boolean _bExecutionSuspended;
private Iterable _callStack = new Stack<RuntimeInfoAtStatement>();
private IGosuParser _parser;
private IScope _visibleSymbols;
private DebugExpression[] _establishedSymbols;
private String _strUserName;
private RunnableWithResult _task;
private Stack<RuntimeInfoAtStatement> _suspendedCtxStack;
/**
* Override to indicate which debug manager is driving.
*/
public abstract IDebugManager getDebugManager();
/**
* Lets have your symbol table.
*/
public abstract ISymbolTable getSymbolTable();
/**
* Override to control typeinfo visibility in debugger ui stack frame, watches, etc.
*/
public abstract IScriptabilityModifier getVisibility();
public String getUserName()
{
return _strUserName;
}
public void setUserName( String strUserName )
{
_strUserName = strUserName == null ? strUserName : strUserName.trim();
}
public DebugLocationContext debug(IDebugDriver.DebugCommands iCmd, List<BreakPoint> breakpoints )
{
synchronized( this )
{
switch( iCmd )
{
case CMD_RUN:
case CMD_STEP_INTO:
case CMD_STEP_OVER:
setCommand( iCmd );
break;
default:
throw new RuntimeException( "Invalid debug command." );
}
IDebugManager debugManager = getDebugManager();
debugManager.setDebugging( true, this );
List oldBreakpoints = debugManager.getBreakPoints();
for (Object oldBreakpoint : new ArrayList(oldBreakpoints)) {
debugManager.removeBreakPoint((BreakPoint) oldBreakpoint);
}
for( BreakPoint bp : breakpoints )
{
debugManager.addBreakPoint( bp );
}
// Notify script execution to complete the command.
notifyAll();
try
{
// Wait for script execution to complete the command.
waitForDebugCommandToComplete();
return getDebugLocationContext();
}
catch( InterruptedException e )
{
stopDebugging();
return null;
}
}
}
protected void waitForDebugCommandToComplete() throws InterruptedException
{
synchronized( this )
{
wait();
}
}
public void stopDebugging()
{
synchronized( this )
{
getDebugManager().setDebugging( false, this );
notifyAll();
}
try
{
// Wait a bit. Apache Axis is not thread safe on the client side, so we
// have to take care to ensure that we don't cause the "stop" command and
// the "debug" command to return simultaneously.
Thread.sleep( 500 );
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
}
public void addBreakPoint( BreakPoint breakPoint )
{
getDebugManager().addBreakPoint( breakPoint );
}
public void removeBreakPoint( BreakPoint breakPoint )
{
getDebugManager().removeBreakPoint( breakPoint );
}
public boolean areBreakpointsMuted()
{
return BreakPoint.areBreakpointsMuted();
}
public void setBreakpointsMuted( boolean bMuted )
{
BreakPoint.setBreakpointsMuted( bMuted );
}
public DebugExpression[] establishSymbols( final int iScopeIndex, final int iPrivateGlobalScopeIndex )
{
return (DebugExpression[])
runInSuspendedExectionThread(
new RunnableWithResult()
{
public Object executeTask()
{
TypeSystem.pushIncludeAll();
try
{
_establishedSymbols = makeSymbols( iScopeIndex, iPrivateGlobalScopeIndex );
return _establishedSymbols;
}
finally
{
TypeSystem.popIncludeAll();
}
}
} );
}
public DebugExpression[] getEstablishedSymbols()
{
return _establishedSymbols;
}
public DebugExpression[] evaluate( final String[] astrExpression )
{
return (DebugExpression[])
runInSuspendedExectionThread(
new RunnableWithResult()
{
public Object executeTask()
{
TypeSystem.pushIncludeAll();
try
{
return eval( astrExpression );
}
finally
{
TypeSystem.popIncludeAll();
}
}
} );
}
public String[] evaluate( final String strScript )
{
return (String[])
runInSuspendedExectionThread(
new RunnableWithResult()
{
public Object executeTask()
{
TypeSystem.pushIncludeAll();
InstrumentationManager.IS_TESTER_DEBUGGER.set(true);
try
{
return evaluateOrExecute( strScript );
}
finally
{
InstrumentationManager.IS_TESTER_DEBUGGER.set(false);
TypeSystem.popIncludeAll();
}
}
} );
}
public String executeTemplate( final String strTemplate )
{
return (String)
runInSuspendedExectionThread(
new RunnableWithResult()
{
public Object executeTask()
{
TypeSystem.pushIncludeAll();
try
{
return executeTemplateNow( strTemplate );
}
finally
{
TypeSystem.popIncludeAll();
}
}
} );
}
public IActivationContext[] getActivationContextStack()
{
return (IActivationContext[])
runInSuspendedExectionThread(
new RunnableWithResult()
{
public Object executeTask()
{
Stack<RuntimeInfoAtStatement> callStack = RuntimeInfoAtStatement.getCallStack();
if( callStack == null )
{
return new RemoteActivationContext[0];
}
int iCallStackSize = callStack.size();
IActivationContext[] activationCtxStack = new IActivationContext[iCallStackSize];
for( int i = 0; i < iCallStackSize; i++ )
{
RuntimeInfoAtStatement ctx = callStack.get( i );
DebugLocationContext debugCtx;
String stackDisplay;
if(ctx.getEnclosingType() != null) {
debugCtx = new DebugLocationContext( ((IGosuClass)ctx.getEnclosingType()).getManagedContext().getContext(), ctx.getLineNumber(), 0 );
stackDisplay = ctx.getStackDisplay();
} else {
debugCtx = new DebugLocationContext( UNKNOWN_CONTEXT, 0, 0);
stackDisplay = "Unknown Context";
if(i > 0 && activationCtxStack[iCallStackSize - i] != null) {
IActivationContext priorCtx = activationCtxStack[iCallStackSize - i];
if(priorCtx.getDebugContext().getContext() != UNKNOWN_CONTEXT) {
System.out.println("ERROR: Invalid debug context generated at or around " + priorCtx.getLabel());
}
}
}
activationCtxStack[iCallStackSize-1-i] = new RemoteActivationContext( debugCtx, i, stackDisplay );
}
return activationCtxStack;
}
} );
}
//----------------------------------------------------------------------------
// -- IDebugDriver impl --
public void onLocation( DebugLocationContext locationContext )
{
synchronized( this )
{
if( !getDebugManager().isDebugging() )
{
return;
}
if( isExecutionSuspended() )
{
// There is already a suspended script execution thread being debugged.
// We can only debug one thread at a time, so let other threads freely
// execute as to not clobber the current debug session.
return;
}
if( !sessionMatchesUserId() )
{
return;
}
setExecutionSuspended( true );
setDebugLocationCtx( locationContext );
setSuspendedSymbolTable( new Stack<RuntimeInfoAtStatement>( RuntimeInfoAtStatement.getCallStack() ),
makeSymbolTableFromCtxStack( -1 ) );
// Notify the debugger of the completed command and current location.
notifyAll();
try
{
// Wait for the debugger to notify with a new command e.g., wait for a step instruction.
do
{
wait();
RunnableWithResult task = getTask();
if( task == null )
{
break;
}
task.run();
notifyAll();
} while( true );
setStackTrace();
setSuspendedSymbolTable( null, null );
setDebugLocationCtx( null );
setExecutionSuspended( false );
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
}
}
private ISymbolTable makeSymbolTableFromCtxStack( int iScopeIndex )
{
StandardSymbolTable symTable = new StandardSymbolTable( true );
Stack<RuntimeInfoAtStatement> ctxStack = RuntimeInfoAtStatement.getCallStack();
if( iScopeIndex < 0 )
{
iScopeIndex = ctxStack.size()-1;
}
if( ctxStack != null )
{
RuntimeInfoAtStatement ctx = ctxStack.get( iScopeIndex );
for( ISymbol sym : ctx.getLocalSymbols() )
{
symTable.putSymbol( sym );
}
}
return symTable;
}
private Object runInSuspendedExectionThread( RunnableWithResult task )
{
synchronized( this )
{
if( !isExecutionSuspended() )
{
return task.run();
}
else
{
setTask( task );
// Notify suspended execution thread of the task.
notifyAll();
try
{
do
{
// Wait for suspended execution thread to execute the task.
wait();
}while( !task.isFinishedRunning() );
setTask( null );
Throwable error = task.getError();
if( error != null )
{
throw new RuntimeException( error );
}
return task.getResult();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
}
}
}
abstract protected boolean sessionMatchesUserId();
public Iterable getCallStack()
{
return _callStack;
}
public IDebugDriver.DebugCommands getCommand()
{
if( _cmd == null )
{
throw new RuntimeException( "Command not set." );
}
return _cmd;
}
//----------------------------------------------------------------------------
// -- private methods --
private IScope getVisibleSymbols()
{
return _visibleSymbols;
}
private void setVisibleSymbols( IScope visibleSymbols )
{
_visibleSymbols = visibleSymbols;
}
private void setStackTrace()
{
Stack<RuntimeInfoAtStatement> callStack = RuntimeInfoAtStatement.getCallStack();
Stack<RuntimeInfoAtStatement> copy = new Stack<RuntimeInfoAtStatement>();
for( RuntimeInfoAtStatement e : callStack )
{
copy.push( e.copy() );
}
_callStack = copy;
}
private void setCommand(IDebugDriver.DebugCommands iCommand )
{
_cmd = iCommand;
}
/*
* Tracks if script execution is suspended or not. Script exec is suspended when
* waiting for the debugger to issue a command e.g., step.
*/
private boolean isExecutionSuspended()
{
return _bExecutionSuspended;
}
private void setExecutionSuspended( boolean bExecutionSuspended )
{
_bExecutionSuspended = bExecutionSuspended;
}
private void setDebugLocationCtx( DebugLocationContext locationContext )
{
_onLocationContext = locationContext;
}
private DebugLocationContext getDebugLocationContext()
{
return _onLocationContext;
}
private ISymbolTable getSuspendedSymbolTable()
{
return getSuspendedSymbolTable( -1 );
}
private ISymbolTable getSuspendedSymbolTable( int iScopeIndex )
{
if( iScopeIndex >= 0 )
{
setSuspendedSymbolTable( _suspendedCtxStack, makeSymbolTableFromCtxStack( iScopeIndex ) );
}
return _suspendedSymbolTable;
}
private void setSuspendedSymbolTable( Stack<RuntimeInfoAtStatement> ctxStack, ISymbolTable suspendedSymbolTable )
{
_suspendedCtxStack = ctxStack;
_suspendedSymbolTable = suspendedSymbolTable;
}
private DebugExpression[] makeSymbols( int iScopeIndex, int iPrivageGlobalIndex )
{
ISymbolTable symTable = getSuspendedSymbolTable( iScopeIndex );
if( symTable == null )
{
return new DebugExpression[0];
}
Collection mapSymbols = symTable.getSymbols().values();
List listDebugSymbols = new ArrayList();
setVisibleSymbols( new StandardScope() );
for( Object listSymbol : mapSymbols )
{
ISymbol symbol = (ISymbol)listSymbol;
if( isExcluded( symbol ) )
{
continue;
}
getVisibleSymbols().put( symbol.getCaseInsensitiveName(), symbol );
IType type = symbol.getType();
DebugExpression debugSymbol;
if( type instanceof IFunctionType )
{
debugSymbol = new DebugExpression( symbol.getName(), symbol.getType(), null );
}
else
{
TypeSystem.getCompiledGosuClassSymbolTable().setCurrentIsolatedScope( iScopeIndex );
try
{
debugSymbol = eval( symbol.getName(), symbol.getName() );
}
finally
{
TypeSystem.getCompiledGosuClassSymbolTable().getStack().setCurrentScopeIndexForDebugger( -1 );
}
}
listDebugSymbols.add( debugSymbol );
}
return (DebugExpression[])listDebugSymbols.toArray( new DebugExpression[listDebugSymbols.size()] );
}
private boolean isExcluded( ISymbol symbol )
{
for (String EXCLUDE_SYMBOL : EXCLUDE_SYMBOLS) {
if (EXCLUDE_SYMBOL.equalsIgnoreCase(symbol.getName())) {
return true;
}
}
return false;
}
private DebugExpression[] eval( String[] astrExpression )
{
if( astrExpression == null )
{
return null;
}
DebugExpression[] astrValues = new DebugExpression[astrExpression.length];
for( int i = 0; i < astrExpression.length; i++ )
{
astrValues[i] = eval( astrExpression[i] );
}
return astrValues;
}
/**
* Evaluate a Gosu expression or program.
*
* @param strExpression A Gosu expression or program.
* @return The value of the expression (or return value of the program).
*/
private DebugExpression eval( String strExpression )
{
return eval( strExpression, strExpression );
}
private DebugExpression eval( String strName, String strExpression )
{
initParser();
IExpression expression;
try
{
expression = parseExpression( strExpression );
}
catch( Exception e )
{
try
{
expression = parseProgram( strExpression );
}
catch( Exception e2 )
{
return getExceptionDebugExpression( e2, strName );
}
}
// Note we need to push the scope here too because the call to expression.getType()
// (esp. for Identifier expressions) needs to access the relative symbols which may
// shadow what is otherwise in the symbol table.
_parser.getSymbolTable().pushScope( getVisibleSymbols() );
try
{
if( strName.equals( strExpression ) )
{
// Format the expression (e.g., for watch expressions)
IType type = expression.getType();
if( type.isArray() || type == IJavaType.STRING )
{
if( type.isArray() )
{
strExpression = "\"{\" + " + strExpression + " + \"} \" + " + "\"" + ARRAY_LENGTH_PREFIX + "\" + " + strExpression + ".length";
}
else if( type == IJavaType.STRING )
{
strExpression = "\"\\\"\" + " + strExpression + " + \"\\\"\"";
}
DebugExpression rde = eval( strName, strExpression );
rde.setType( type );
return rde;
}
}
//noinspection unchecked
String strValue = makeStringValue( expression.evaluate( new ExternalSymbolMapForMap( (CaseInsensitiveHashMap)getVisibleSymbols() ) ) );
return new DebugExpression( strName, expression.getType(), strValue );
}
catch( Throwable e )
{
return getExceptionDebugExpression( e, strName );
}
finally
{
_parser.getSymbolTable().popScope();
}
}
private DebugExpression getExceptionDebugExpression( Throwable e, String strName )
{
String strMsg = e.getMessage();
if( strMsg == null )
{
strMsg = e.getClass().getName();
}
return new DebugExpression( strName, IGosuParser.NULL_TYPE, strMsg );
}
private String makeStringValue( Object value )
{
if( value instanceof Date )
{
DateFormat formatter = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG );
return formatter.format( (Date)value );
}
return CommonServices.getCoercionManager().makeStringFrom( value );
}
private void initParser()
{
if( _parser == null )
{
_parser = GosuParserFactory.createParser( getSuspendedSymbolTable(), getVisibility() );
}
else
{
_parser.setSymbolTable( getSuspendedSymbolTable() );
}
}
private String[] evaluateOrExecute( String strScript )
{
initParser();
PrintStream sysout = System.out;
PrintStream syserr = System.err;
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream ps;
try
{
ps = new PrintStream( out, false, "UTF-8" );
} catch ( UnsupportedEncodingException e ) {
throw new RuntimeException( e ); // shouldn't happen with UTF-8
}
System.setOut( ps );
System.setErr( ps );
IExpression expression = null;
try
{
expression = parseExpression( strScript );
}
catch( Exception e )
{
try
{
expression = parseProgram( strScript );
}
catch( Exception e2 )
{
e2.printStackTrace();
}
}
Object ret = null;
_parser.getSymbolTable().pushScope( getVisibleSymbols() );
try
{
assert expression != null;
//noinspection unchecked
ret = expression.evaluate( new ExternalSymbolMapForMap( (CaseInsensitiveHashMap)getVisibleSymbols() ) );
}
catch( Exception e )
{
e.printStackTrace();
}
finally
{
_parser.getSymbolTable().popScope();
ps.flush();
System.setOut( sysout );
System.setErr( syserr );
}
return new String[]
{
(String) CommonServices.getCoercionManager().convertValue(ret, IJavaType.STRING),
StreamUtil.toString( out.toByteArray() ),
};
}
/**
* Parse a Gosu expression.
*
* @param strExpression A Gosu expression.
* @return The compiled expression.
* @throws gw.lang.parser.exceptions.ParseResultsException
*
*/
private IExpression parseExpression( String strExpression ) throws ParseResultsException
{
_parser.getSymbolTable().pushScope( getVisibleSymbols() );
try
{
_parser.setScript( strExpression );
return _parser.parseExp( null );
}
finally
{
_parser.getSymbolTable().popScope();
}
}
/**
* Compile a Gosu program.
*
* @param strProgram A Gosu program.
* @return The compiled program.
* @throws gw.lang.parser.exceptions.ParseResultsException
*/
private IProgram parseProgram( String strProgram ) throws ParseResultsException
{
_parser.getSymbolTable().pushScope( getVisibleSymbols() );
try
{
_parser.setScript( strProgram );
return _parser.parseProgram( null );
}
finally
{
_parser.getSymbolTable().popScope();
}
}
private String executeTemplateNow( String strTemplate )
{
initParser();
StringWriter writer = new StringWriter();
_parser.getSymbolTable().pushScope( getVisibleSymbols() );
try
{
GosuShop.generateTemplate( new StringReader( strTemplate ), writer, _parser.getSymbolTable() );
}
catch( Exception e )
{
throw new RuntimeException( e );
}
finally
{
_parser.getSymbolTable().popScope();
}
return writer.toString();
}
private RunnableWithResult getTask()
{
return _task;
}
private void setTask( RunnableWithResult task )
{
_task = task;
}
}