/*
* Copyright 2013 Guidewire Software, Inc.
*/
/*
*/
package gw.plugin.ij.debugger.evaluation;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.JVMNameUtil;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator;
import com.intellij.debugger.engine.evaluation.expression.Modifier;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.jdi.LocalVariableProxyImpl;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.openapi.project.Project;
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.ArrayType;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ByteValue;
import com.sun.jdi.CharValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.DoubleValue;
import com.sun.jdi.FloatValue;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.LongValue;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.PrimitiveValue;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ShortValue;
import com.sun.jdi.StackFrame;
import com.sun.jdi.Value;
import gw.internal.gosu.parser.ContextSensitiveCodeRunner;
import gw.plugin.ij.util.GosuModuleUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class GosuExpressionEvaluatorImpl implements ExpressionEvaluator {
private Value _value;
private Project _project;
private String _strFragName;
private String _strText;
private String _strClassContext;
private String _strContextElementClass;
private int _iContextLocation;
public GosuExpressionEvaluatorImpl( Project project, String strFragName, String strText,
String strClassContext, String strContextElementClass,
int iContextLocation ) {
_project = project;
_strFragName = strFragName;
_strText = strText;
_strClassContext = GosuModuleUtil.getActualClassName( strClassContext, project );
_strContextElementClass = GosuModuleUtil.getActualClassName( strContextElementClass, project );
_iContextLocation = iContextLocation;
}
//call evaluate before
public Value getValue() {
return _value;
}
//call evaluate before
public Modifier getModifier() {
return null; //## todo: implement me
}
// EvaluationContextImpl should be at the same stackFrame as it was in the call to EvaluatorBuilderImpl.build
public Value evaluate( final EvaluationContext context ) throws EvaluateException {
if( !context.getDebugProcess().isAttached() ) {
throw EvaluateExceptionUtil.createEvaluateException( DebuggerBundle.message( "error.vm.disconnected" ) );
}
try {
if( context.getFrameProxy() == null ) {
throw EvaluateExceptionUtil.NULL_STACK_FRAME;
}
final DebuggerSession session = DebuggerManagerEx.getInstanceEx( _project ).getContext().getDebuggerSession();
final DebugProcessImpl process = session.getProcess();
final VirtualMachineProxyImpl vm = process.getVirtualMachineProxy();
List<ReferenceType> types = vm.classesByName( ContextSensitiveCodeRunner.class.getName() );
ClassType classType = (ClassType)types.get( 0 );
Value thisObject = findThisObjectFromCtx( context );
_value = vm.getDebugProcess().invokeMethod( context, classType, classType.methodsByName( "runMeSomeCode" ).get( 0 ),
Arrays.asList(
thisObject,
context.getClassLoader(),
makeExternalsSymbolsForLocals( process, context ),
vm.mirrorOf( _strText ),
vm.mirrorOf( _strClassContext ),
vm.mirrorOf( _strContextElementClass ),
vm.mirrorOf( _iContextLocation ) ) );
// Primitive boolean value needed for conditional breakpoint expression
return unboxIfBoxed( context, _value );
}
catch( Throwable/*IncompatibleThreadStateException*/ e ) {
if( e instanceof EvaluateException ) {
throw ((EvaluateException)e);
}
else {
throw EvaluateExceptionUtil.createEvaluateException( e );
}
}
}
private Value findThisObjectFromCtx( EvaluationContext context ) {
List<LocalVariable> localVariables = null;
StackFrame frame;
try {
frame = context.getFrameProxy().getStackFrame();
}
catch( EvaluateException e ) {
throw new RuntimeException( e );
}
try {
localVariables = frame.visibleVariables();
}
catch( AbsentInformationException e ) {
return context.getThisObject();
}
for( LocalVariable localVar : localVariables ) {
if( localVar.name().equals( "$that$" ) ) {
// Use the enhanced object for the 'this' ref in enhancements
return frame.getValue( localVar );
}
}
return context.getThisObject();
}
private ArrayReference makeExternalsSymbolsForLocals( DebugProcessImpl debugProcess, EvaluationContext context ) throws EvaluateException, AbsentInformationException, InvalidTypeException, ClassNotLoadedException {
List<Value> values = new ArrayList<>();
StackFrameProxyImpl frame = (StackFrameProxyImpl)context.getFrameProxy();
try {
for( LocalVariableProxyImpl localVar : frame.visibleVariables() ) {
values.add( debugProcess.getVirtualMachineProxy().mirrorOf( localVar.name() ) );
Value value = frame.getValue( localVar );
values.add( boxIfPrimitive( context, value ) );
}
}
catch( EvaluateException e ) {
// ignore
}
ArrayType objectArrayClass = (ArrayType)debugProcess.findClass(
context,
"java.lang.Object[]",
context.getClassLoader() );
if( objectArrayClass == null ) {
throw new IllegalStateException();
}
ArrayReference argArray = debugProcess.newInstance( objectArrayClass, values.size() );
((SuspendContextImpl)context.getSuspendContext()).keep( argArray ); // to avoid ObjectCollectedException
argArray.setValues( values );
return argArray;
}
public Value boxIfPrimitive( EvaluationContext context, Value value ) throws EvaluateException {
if( value == null || value instanceof ObjectReference ) {
return value;
}
if( value instanceof BooleanValue ) {
return convertToWrapper( context, (BooleanValue)value, "java.lang.Boolean" );
}
if( value instanceof ByteValue ) {
return convertToWrapper( context, (ByteValue)value, "java.lang.Byte" );
}
if( value instanceof CharValue ) {
return convertToWrapper( context, (CharValue)value, "java.lang.Character" );
}
if( value instanceof ShortValue ) {
return convertToWrapper( context, (ShortValue)value, "java.lang.Short" );
}
if( value instanceof IntegerValue ) {
return convertToWrapper( context, (IntegerValue)value, "java.lang.Integer" );
}
if( value instanceof LongValue ) {
return convertToWrapper( context, (LongValue)value, "java.lang.Long" );
}
if( value instanceof FloatValue ) {
return convertToWrapper( context, (FloatValue)value, "java.lang.Float" );
}
if( value instanceof DoubleValue ) {
return convertToWrapper( context, (DoubleValue)value, "java.lang.Double" );
}
throw new EvaluateException( "Cannot perform boxing conversion for a value of type " + value.type().name() );
}
public Value unboxIfBoxed( EvaluationContext context, Value value ) throws EvaluateException {
if( value == null || !(value instanceof ObjectReference) ) {
return value;
}
ObjectReference valueRef = (ObjectReference)value;
ReferenceType type = valueRef.referenceType();
//System.out.println( "TYPE IS: " + type.name() );
if( type.name().equals( Boolean.class.getName() ) ) {
return unbox( context, valueRef, Boolean.class.getName(), "booleanValue" );
}
if( type.name().equals( Byte.class.getName() ) ) {
return unbox( context, valueRef, Byte.class.getName(), "byteValue" );
}
if( type.name().equals( Character.class.getName() ) ) {
return unbox( context, valueRef, Character.class.getName(), "charValue" );
}
if( type.name().equals( Short.class.getName() ) ) {
return unbox( context, valueRef, Short.class.getName(), "shortValue" );
}
if( type.name().equals( Integer.class.getName() ) ) {
return unbox( context, valueRef, Integer.class.getName(), "intValue" );
}
if( type.name().equals( Long.class.getName() ) ) {
return unbox( context, valueRef, Long.class.getName(), "longValue" );
}
if( type.name().equals( Float.class.getName() ) ) {
return unbox( context, valueRef, Float.class.getName(), "floatValue" );
}
if( type.name().equals( Double.class.getName() ) ) {
return unbox( context, valueRef, Double.class.getName(), "doubleValue" );
}
return value;
}
private Value convertToWrapper( EvaluationContext context, PrimitiveValue value, String wrapperTypeName ) throws EvaluateException {
DebugProcessImpl process = (DebugProcessImpl)context.getDebugProcess();
ClassType wrapperClass = (ClassType)process.findClass( context, wrapperTypeName, context.getClassLoader() );
String methodSignature = "(" + JVMNameUtil.getPrimitiveSignature( value.type().name() ) + ")L" + wrapperTypeName.replace( '.', '/' ) + ";";
List<Method> methods = wrapperClass.methodsByName( "valueOf", methodSignature );
if( methods.size() == 0 ) { // older JDK version
methods = wrapperClass.methodsByName( "<init>", methodSignature );
}
if( methods.size() == 0 ) {
throw new EvaluateException( "Cannot construct wrapper object for value of type " + value.type() + ": Unable to find either valueOf() or constructor method" );
}
final Method factoryMethod = methods.get( 0 );
final ArrayList<Value> args = new ArrayList<>();
args.add( value );
return process.invokeMethod( context, wrapperClass, factoryMethod, args );
}
private Value unbox( EvaluationContext context, ObjectReference value, String wrapperTypeName, String strMethod ) throws EvaluateException {
DebugProcessImpl process = (DebugProcessImpl)context.getDebugProcess();
ClassType wrapperClass = (ClassType)process.findClass( context, wrapperTypeName, context.getClassLoader() );
List<Method> methods = wrapperClass.methodsByName( strMethod );
if( methods.size() == 0 ) {
throw new EvaluateException( "Could not find method " + strMethod + "()" );
}
Method factoryMethod = methods.get( 0 );
return process.invokeMethod( context, value, factoryMethod, Collections.emptyList() );
}
}