blob: 347b804e9c7d35f99133f391bd5b639e1abb97ab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
* $RCSfile: ExpressionProcesserController.java,v $
* $Revision: 1.5 $ $Date: 2005/02/15 22:57:54 $
*/
package org.eclipse.jem.internal.proxy.vm.remote;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jem.internal.proxy.common.*;
import org.eclipse.jem.internal.proxy.common.CommandException;
import org.eclipse.jem.internal.proxy.common.MapTypes;
import org.eclipse.jem.internal.proxy.common.remote.Commands;
import org.eclipse.jem.internal.proxy.common.remote.ExpressionCommands;
import org.eclipse.jem.internal.proxy.initParser.EvaluationException;
import org.eclipse.jem.internal.proxy.initParser.tree.ExpressionProcesser;
import org.eclipse.jem.internal.proxy.initParser.tree.IInternalExpressionConstants;
import org.eclipse.jem.internal.proxy.initParser.tree.IExpressionConstants.NoExpressionValueException;
/**
* This processes the commands for expression processing and sends them over
* to the common expression processer.
*
* This will be instantiated on the start of an expression. And then
* each expression request from the IDE will be sent into here. The
* reason this guy doesn't hold onto the connection and process the
* entire expression is because we need to return to the connection
* handler to keep the connection live (there is timeouts and stuff
* in there that we don't want to duplicate here).
* <p>
* If there are any expression processing errors (versus hard io errors) we
* will save up the error but don't do any more processing other than to make
* sure we read the complete subcommand. This is so that the inputstream is left
* in a valid state without standed data.
* <p>
* The at the sync point (either get value or sync subcommand) we will send back
* the error.
*
* @since 1.0.0
*/
public class ExpressionProcesserController {
protected final RemoteVMServerThread server;
protected final ConnectionHandler connHandler;
protected final ExpressionProcesser exp;
protected Commands.ValueObject workerValue = new Commands.ValueObject(); // A worker value object so we don't need to keep creating them and releasing them.
private ClassLoader classLoader;
/**
* An error has occurred. At this point all subcommands will simply make sure they flush the input stream
* correctly, but they do not process it.
*
* @since 1.0.0
*/
protected boolean errorOccurred = false;
private String novalueMsg = null; // If NoExpressionValueException then this is the msg.
private Throwable exception = null; // Was there another kind of exception that was caught.
/**
* Create with a default expression processer.
* @param server
*
* @since 1.0.0
*/
public ExpressionProcesserController(RemoteVMServerThread server, ConnectionHandler connHandler) {
this(server, connHandler, new ExpressionProcesser());
}
/**
* Create from a subclass with a given expression processer.
*
* @param server
* @param exp
*
* @since 1.0.0
*/
protected ExpressionProcesserController(RemoteVMServerThread server, ConnectionHandler connHandler, ExpressionProcesser exp) {
this.server = server;
this.connHandler = connHandler;
this.exp = exp;
}
/**
* Set the class loader to use for finding classes. If never set, or if <code>null</code>, then
* <code>Class.forName</code> will be used.
*
* @param classLoader
*
* @since 1.0.0
*/
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/*
* Array of primitive type names. Used to look up primtive types in primitive types array.
*
* @since 1.0.0
*/
private static final List PRIMITIVE_NAMES = Arrays.asList(new String[] {"byte", "char", "short", "int", "long", "float", "double"});
private static final Class[] PRIMITIVE_TYPES = new Class[] {Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE};
/**
* Load the class given the name. If not found, return null.
*
* @param className
* @return
*
* @since 1.0.0
*/
protected Class loadClass(String className) throws ClassNotFoundException {
if (className == null)
return null;
else if (className.endsWith("[]")) {
// We have an array request instead. This is trickier.
return loadClass(MapTypes.getJNIFormatName(className));
} else {
int primIndex = PRIMITIVE_NAMES.indexOf(className);
if (primIndex >= 0)
return PRIMITIVE_TYPES[primIndex];
else if (classLoader == null) {
return Class.forName(className);
} else {
return classLoader.loadClass(className);
}
}
}
/**
* Now process the input stream. If either throws occurs, this is a hard error and we must terminate
* the entire connection. The input stream is in an unknown state.
*
* @param in The input stream to get the data for the current sub-command.
*
* @throws CommandException
* @throws IOException
* @since 1.0.0
*/
public void process(DataInputStream in) throws CommandException, IOException {
// In the following subcommand processing, we always read the entire subcommand from the stream.
// Then we check if an error has occurred in the past. If it has, we simply break. This is because
// once an error occurred we don't want to continue wasting time evaluating, however we need to make
// sure that the stream is read completely so that we don't have a corrupted input stream. That way
// when all is done we can return the error and still have a valid connection socket.
byte subcommand = in.readByte();
try {
switch (subcommand) {
case IInternalExpressionConstants.PUSH_TO_PROXY_EXPRESSION :
// Getting a proxy push. The value is sent as valueObject, so use that to read it in.
Commands.readValue(in, workerValue);
if (errorOccurred)
break;
Object value = connHandler.getInvokableObject(workerValue);
if (value == null)
exp.pushExpression(null, MethodHelper.NULL_TYPE);
else if (workerValue.isPrimitive())
exp.pushExpression(value, workerValue.getPrimitiveType());
else
exp.pushExpression(value, value.getClass());
break;
case IInternalExpressionConstants.CAST_EXPRESSION :
// Get a cast request. The type is sent as valueObject.
Commands.readValue(in, workerValue);
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushCast((Class) value);
} catch (NoExpressionValueException e) {
processException(e);
} catch (ClassNotFoundException e) {
processException(e);
}
break;
case IInternalExpressionConstants.INSTANCEOF_EXPRESSION :
// Get a instanceof request. The type is sent as valueObject.
Commands.readValue(in, workerValue);
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushInstanceof((Class) value);
} catch (ClassNotFoundException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
break;
case IInternalExpressionConstants.INFIX_EXPRESSION :
// Get an infix request. The operator and operand type are sent as bytes.
byte infix_operator = in.readByte();
byte infix_operandType = in.readByte();
if (errorOccurred)
break;
try {
exp.pushInfix(infix_operator, infix_operandType);
} catch (NoExpressionValueException e1) {
processException(e1);
}
break;
case IInternalExpressionConstants.PREFIX_EXPRESSION :
// Get a prefix request. The operator is sent as byte.
byte prefix_operandType = in.readByte();
if (errorOccurred)
break;
try {
exp.pushPrefix(prefix_operandType);
} catch (NoExpressionValueException e2) {
processException(e2);
}
break;
case IInternalExpressionConstants.TYPELITERAL_EXPRESSION :
// Get a type literal request. The type string is sent as valueObject.
Commands.readValue(in, workerValue);
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
value = loadClass((String) value);
exp.pushExpression((Class) value, Class.class);
} catch (ClassNotFoundException e) {
processException(e);
}
break;
case IInternalExpressionConstants.ARRAY_ACCESS_EXPRESSION :
// Get an array access request. The index cound is sent as int.
int arrayAccess_Indexcount = in.readInt();
if (errorOccurred)
break;
try {
exp.pushArrayAccess(arrayAccess_Indexcount);
} catch (NoExpressionValueException e3) {
processException(e3);
}
break;
case IInternalExpressionConstants.ARRAY_CREATION_EXPRESSION :
// Get an array creation request. The type is sent as valueObject, followed by int dimension count.
Commands.readValue(in, workerValue);
int arrayCreation_dimCount = in.readInt();
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushArrayCreation((Class) value, arrayCreation_dimCount);
} catch (ClassNotFoundException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
break;
case IInternalExpressionConstants.ARRAY_INITIALIZER_EXPRESSION :
// Get an array initializer request. The type is sent as valueObject, followed by int expression count.
Commands.readValue(in, workerValue);
int arrayInitializer_expressionCount = in.readInt();
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushArrayInitializer((Class) value, arrayInitializer_expressionCount);
} catch (ClassNotFoundException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
}
break;
case IInternalExpressionConstants.CLASS_INSTANCE_CREATION_EXPRESSION :
// Get a class instance creation request. The type is sent as valueObject, followed by int argument count.
Commands.readValue(in, workerValue);
int newInstance_argCount = in.readInt();
if (errorOccurred)
return;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushClassInstanceCreation((Class) value, newInstance_argCount);
} catch (ClassNotFoundException e) {
processException(e);
} catch (EvaluationException e) {
processException(e);
} catch (NoExpressionValueException e) {
processException(e);
} catch (InstantiationException e) {
processException(e);
} catch (IllegalAccessException e) {
processException(e);
} catch (InvocationTargetException e) {
processException(e);
} catch (LinkageError e) {
processError(e);
}
break;
case IInternalExpressionConstants.TYPERECEIVER_EXPRESSION :
// Get a type receiver request. The type is sent as valueObject.
Commands.readValue(in, workerValue);
if (errorOccurred)
break;
value = connHandler.getInvokableObject(workerValue);
try {
if (value instanceof String)
value = loadClass((String) value);
exp.pushExpression(value, (Class) value);
} catch (ClassNotFoundException e) {
processException(e);
}
break;
case IInternalExpressionConstants.FIELD_ACCESS_EXPRESSION :
// Get a field access request. The field name sent as string, followed by hasReceiver as boolean.
String fieldAccess_name = in.readUTF();
boolean fieldAccess_receiver = in.readBoolean();
if (errorOccurred)
break;
try {
exp.pushFieldAccess(fieldAccess_name, fieldAccess_receiver);
} catch (NoExpressionValueException e4) {
processException(e4);
} catch (NoSuchFieldException e4) {
processException(e4);
} catch (IllegalAccessException e4) {
processException(e4);
}
break;
case IInternalExpressionConstants.METHOD_EXPRESSION :
// Get a method invocation request. The method name sent as string, followed by hasReceiver as boolean, and argCount as int.
String method_name = in.readUTF();
boolean method_receiver = in.readBoolean();
int method_argCount = in.readInt();
if (errorOccurred)
break;
try {
exp.pushMethodInvocation(method_name, method_receiver, method_argCount);
} catch (EvaluationException e5) {
processException(e5);
} catch (NoExpressionValueException e5) {
processException(e5);
} catch (IllegalAccessException e5) {
processException(e5);
} catch (InvocationTargetException e5) {
processException(e5);
}
break;
case IInternalExpressionConstants.CONDITIONAL_EXPRESSION :
// Get a conditional expression request. The expression type (ie. condition/true/false) is sent as a byte
byte conditional_type = in.readByte();
if (errorOccurred)
break;
try {
exp.pushConditional(conditional_type);
} catch (NoExpressionValueException e6) {
processException(e6);
}
break;
}
} catch (RuntimeException e) {
processException(e);
}
workerValue.set(); // Clear it out so nothing being held onto.
}
/**
* Pull the value. If an error occurred during this, then <code>null</code> is returned, and
* caller should do normal error processing.
*
* @return r[0] is the value, r[1] is the type of the value. <code>null</code> if there was an error.
*
* @since 1.0.0
*/
public Object[] pullValue() {
Object[] result = new Object[2];
try {
exp.pullValue(result);
return result;
} catch (NoExpressionValueException e) {
processException(e);
}
return null;
}
/**
* Process all other exceptions then the NoExpressionValueException, InvocationTargetException, and EvaluationException. This is
* for exceptions that are not related to the input stream and shouldn't
* cause the input stream to be closed.
*
* @param e
*
* @since 1.0.0
*/
protected final void processException(Exception e) {
// Process all other exceptions.
errorOccurred = true;
exception = e;
}
/**
* Process certain errors. Should not be called for general errors. Should be called for specific java.lang.Errors that
* are not related to the input stream and shouldn't cause the input stream to be closed.
*
* @param e
*
* @since 1.0.2
*/
protected final void processError(Error e) {
// Process specific errors
errorOccurred = true;
exception = e;
}
/**
* Process InvocationTargetException to retrieve the actual target.
* @param e
*
* @since 1.0.0
*/
protected final void processException(InvocationTargetException e) {
errorOccurred = true;
exception = e.getTargetException();
}
/**
* Process EvaluationException to retrieve the actual target.
* @param e
*
* @since 1.0.0
*/
protected final void processException(EvaluationException e) {
errorOccurred = true;
exception = e.getOriginalException();
}
/**
* Special case for NoExpressionValueException caught.
* @param e
*
* @since 1.0.0
*/
protected final void processException(NoExpressionValueException e) {
errorOccurred = true;
novalueMsg = e.getLocalizedMessage();
if (novalueMsg == null)
novalueMsg = ""; // It was null, so just default of empty string so we know it had occurred.
}
/**
* Return whether there are any errors.
*
* @return <code>true</code> if no errors.
*
* @since 1.0.0
*/
public boolean noErrors() {
return !errorOccurred;
}
/**
* Return the error code from ExpressionCommands if non-exception error occurred.
* Return zero if no error.
*
* @return Zero if no error code to return, else error code from ExpressionCommands.
*
* @see org.eclipse.jem.internal.proxy.common.remote.ExpressionCommands#ExpressionNoExpressionValueException
*
* @since 1.0.0
*/
public int getErrorCode() {
if (novalueMsg != null)
return ExpressionCommands.ExpressionNoExpressionValueException;
else
return 0;
}
/**
* Return the error message, should only be called if <code>getErrorCode()</code> returned non-zero.
*
* @return The error message if any. <code>null</code> otherwise.
*
* @since 1.0.0
*/
public String getErrorMsg() {
if (novalueMsg != null && novalueMsg.length() > 0)
return novalueMsg;
else
return null;
}
/**
* Return the throwable if a Throwable was caught.
*
* @return The throwable, or <code>null</code> if not set.
*
* @since 1.0.0
*/
public Throwable getErrorThrowable() {
return exception;
}
/**
* Close out things.
*
* @since 1.0.0
*/
public void close() {
exp.close();
}
}