| /******************************************************************************* |
| * Copyright (c) 2001, 2005 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 |
| *******************************************************************************/ |
| package org.eclipse.jem.internal.proxy.vm.remote; |
| /* |
| |
| |
| */ |
| |
| |
| import java.io.*; |
| import java.lang.reflect.Array; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.util.*; |
| |
| import org.eclipse.jem.internal.proxy.common.CommandException; |
| import org.eclipse.jem.internal.proxy.common.remote.*; |
| import org.eclipse.jem.internal.proxy.initParser.*; |
| import org.eclipse.jem.internal.proxy.initParser.tree.NoExpressionValueException; |
| |
| /** |
| * This handles one connection. |
| */ |
| |
| public class ConnectionHandler { |
| |
| protected Socket socket; |
| final protected RemoteVMServerThread server; |
| final protected Thread connectionThread; |
| protected DataInputStream in; |
| protected DataOutputStream out; |
| |
| // Kludge: Bug in Linux 1.3.xxx of JVM. Closing a socket while the socket is being read/accept will not interrupt the |
| // wait. Need to timeout to the socket read/accept before the socket close will be noticed. This has been fixed |
| // in Linux 1.4. So on Linux 1.3 need to put timeouts in on those sockets that can be separately closed while reading/accepting. |
| static boolean LINUX_1_3 = "linux".equalsIgnoreCase(System.getProperty("os.name")) && System.getProperty("java.version","").startsWith("1.3"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ |
| |
| /** |
| * This may be running as a callback, in that case there is no connectionThread. |
| */ |
| public ConnectionHandler(Socket sc, RemoteVMServerThread svr, Thread connectionThread) { |
| socket = sc; |
| server = svr; |
| this.connectionThread = connectionThread; |
| |
| Integer bufSize = Integer.getInteger("proxyvm.bufsize"); //$NON-NLS-1$ |
| if (bufSize == null) |
| bufSize = new Integer(5000); |
| |
| try { |
| out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream(), bufSize.intValue())); |
| in = new DataInputStream(new BufferedInputStream(socket.getInputStream(), bufSize.intValue())); |
| } catch (IOException e) { |
| } |
| |
| } |
| |
| /** |
| * Did this construct correctly. |
| * This is needed because an exception could be thrown in the ctor and |
| * that's not a good thing to do. |
| */ |
| public boolean isConnected() { |
| return socket != null; |
| } |
| |
| /** |
| * Request the thread to close down. |
| */ |
| public void close() { |
| if (socket != null) { |
| try { |
| Socket s = socket; |
| socket = null; |
| s.close(); |
| } catch (Exception e) { |
| } |
| } |
| } |
| |
| // Map of ID to expression processers that are currently being processed. |
| private Map expressionProcessors = new HashMap(); |
| |
| /** |
| * Process and loop until told to stop. A callback_done will stop |
| * the loop and will return a result. Otherwise null is returned. |
| */ |
| public Object run() throws CommandException { |
| Object result = null; |
| boolean shutdown = false; |
| boolean closeWhenDone = true; |
| Commands.ValueObject valueObject = new Commands.ValueObject(); // Working value object so not continually recreated. |
| InvokableValueSender valueSender = new InvokableValueSender(); // Working valuesender so not continually recreated. |
| try { |
| boolean doLoop = true; |
| |
| /** |
| * Note: In the cases below you will see a lot of finally clauses that null variables out. |
| * This is because this is a long running loop, and variables declared within blocks are not |
| * garbage collected until the method is terminated, so these variables once set would never |
| * be GC'd. The nulling at the end of the case makes sure that any of those objects set are |
| * now available for garbage collection when necessary. |
| */ |
| while(doLoop && isConnected()) { |
| byte cmd = 0; |
| try { |
| if (LINUX_1_3) |
| socket.setSoTimeout(1000); // Linux 1.3 bug, see comment on LINUX_1_3 |
| cmd = in.readByte(); |
| if (LINUX_1_3 && isConnected()) |
| socket.setSoTimeout(0); // Linux 1.3 bug, see comment on LINUX_1_3 |
| } catch (InterruptedIOException e) { |
| continue; // Timeout, try again |
| } |
| switch (cmd) { |
| case Commands.QUIT_CONNECTION: |
| doLoop = false; |
| break; // Close this connection |
| |
| case Commands.TERMINATE_SERVER: |
| doLoop = false; |
| shutdown = true; // Shutdown everything |
| break; |
| |
| case Commands.GET_CLASS: |
| String className = in.readUTF(); |
| Class aClass = null; |
| Class superClass = null; |
| String superClassName = null; |
| boolean added = false; |
| try { |
| aClass = Class.forName(className); // Turns out using JNI format for array type will work fine. |
| |
| added = server.getIdentityID(aClass, valueObject); |
| boolean isInterface = aClass.isInterface(); |
| boolean isAbstract = java.lang.reflect.Modifier.isAbstract(aClass.getModifiers()); |
| superClass = aClass.getSuperclass(); |
| superClassName = (superClass != null) ? superClass.getName() : ""; //$NON-NLS-1$ |
| out.writeByte(Commands.GET_CLASS_RETURN); |
| out.writeInt(valueObject.objectID); |
| out.writeBoolean(isInterface); |
| out.writeBoolean(isAbstract); |
| out.writeUTF(superClassName); |
| out.flush(); |
| } catch (ClassNotFoundException e) { |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.GET_CLASS_NOT_FOUND, valueObject); |
| } catch (ExceptionInInitializerError e) { |
| sendException(e.getException(), valueObject, out); |
| } catch (LinkageError e) { |
| sendException(e, valueObject, out); |
| } catch (Throwable e) { |
| // Something bad, did we add a class? If we did remove it from the table. |
| if (added) |
| server.removeObject(server.getObject(valueObject.objectID)); |
| throw e; |
| } finally { |
| // clear out for GC to work. |
| className = null; |
| aClass = null; |
| superClass = null; |
| superClassName = null; |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.GET_CLASS_FROM_ID: |
| int classID = in.readInt(); |
| try { |
| aClass = (Class) server.getObject(classID); |
| boolean isInterface = aClass.isInterface(); |
| boolean isAbstract = java.lang.reflect.Modifier.isAbstract(aClass.getModifiers()); |
| superClass = aClass.getSuperclass(); |
| superClassName = (superClass != null) ? superClass.getName() : ""; //$NON-NLS-1$ |
| out.writeByte(Commands.GET_CLASS_ID_RETURN); |
| out.writeUTF(aClass.getName()); |
| out.writeBoolean(isInterface); |
| out.writeBoolean(isAbstract); |
| out.writeUTF(superClassName); |
| out.flush(); |
| } catch (ClassCastException e) { |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.CLASS_CAST_EXCEPTION, valueObject); |
| } finally { |
| // clear out for GC to work. |
| aClass = null; |
| superClass = null; |
| superClassName = null; |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.GET_OBJECT_DATA: |
| int objectID = in.readInt(); |
| Object anObject = null; |
| try { |
| anObject = server.getObject(objectID); |
| valueObject.setObjectID(objectID, server.getIdentityID(anObject.getClass())); |
| Commands.writeValue(out, valueObject, true); |
| } catch (ClassCastException e) { |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.CLASS_CAST_EXCEPTION, valueObject); |
| } finally { |
| anObject = null; // Clear out for GC to work |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.RELEASE_OBJECT: |
| int id = in.readInt(); |
| server.removeObject(server.getObject(id)); |
| break; |
| |
| case Commands.NEW_INIT_STRING: |
| classID = in.readInt(); // ID Of class to do new upon. |
| String initString = in.readUTF(); // The init string. |
| Object newValue = null; |
| Class theClass = null; |
| try { |
| theClass = (Class) server.getObject(classID); |
| if (theClass == null) { |
| // The class wasn't found. So imply ClassNotFound exception. |
| throw new ClassNotFoundException(); |
| } |
| |
| InitializationStringParser parser = null; |
| try { |
| parser = InitializationStringParser.createParser(initString); |
| newValue = parser.evaluate(); |
| boolean primitive = parser.isPrimitive(); |
| // If expected class is Void.TYPE, that means don't test the type of the result |
| // to verify if correct type, just return what it really is. |
| if (theClass != Void.TYPE && primitive != theClass.isPrimitive()) { |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.CLASS_CAST_EXCEPTION, valueObject); |
| continue; // Goto next command. |
| } |
| if (primitive) { |
| try { |
| // Need to do special tests for compatibility and assignment. |
| sendObject(newValue, classID != Commands.VOID_TYPE ? classID : server.getIdentityID(parser.getExpectedType()), valueObject, out, true); // This will make sure it goes out as the correct primitive type |
| } catch (ClassCastException e) { |
| // The returned type is not of the correct type for what is expected. |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.CLASS_CAST_EXCEPTION, valueObject); |
| continue; // Goto next command |
| } |
| } else { |
| if (newValue != null) { |
| // Test to see if they are compatible. (Null can be assigned to any object, |
| // so that is why it was tested out above). |
| // If expected class is Void.TYPE, that means don't test the type of the result |
| // to verify if correct type, just return what it really is. |
| if (theClass != Void.TYPE && !theClass.isInstance(newValue)) { |
| // The returned type is not of the correct type for what is expected. |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.CLASS_CAST_EXCEPTION, valueObject); |
| continue; // Goto next command |
| } |
| } |
| sendObject(newValue, NOT_A_PRIMITIVE, valueObject, out, true); // Send out as an object. |
| } |
| } catch (InitializationStringEvaluationException e) { |
| if (e instanceof EvaluationException) { |
| // Want to return the real exception. |
| sendException(e.getOriginalException(), valueObject, out); |
| } else { |
| // Couldn't be evaluated, return an error for this. |
| setExceptionIntoValue(e.getOriginalException(), valueObject); |
| Commands.sendErrorCommand(out, Commands.CANNOT_EVALUATE_STRING, valueObject); |
| } |
| } finally { |
| parser = null; // Clear out for GC to work |
| } |
| } catch (Throwable e) { |
| sendException(e, valueObject, out); |
| } finally { |
| // Clear out for GC to work |
| initString = null; |
| theClass = null; |
| newValue = null; |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.INVOKE: |
| Object target = null; |
| Object[] parms = null; |
| Class returnType = null; |
| java.lang.reflect.Method aMethod = null; |
| try { |
| int methodID = in.readInt(); // ID of method to invoke |
| aMethod = (java.lang.reflect.Method) server.getObject(methodID); // Method to invoke |
| Commands.readValue(in, valueObject); |
| target = getInvokableObject(valueObject); |
| Commands.readValue(in, valueObject); |
| if (valueObject.type == Commands.ARRAY_IDS) { |
| // It is an array containing IDs, as it normally would be. |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| parms = (Object[]) valueSender.getArray(); |
| } else { |
| // It is all objects or null, so it should be an Object[] or null. If not, then this is an error. |
| parms = (Object[]) valueObject.anObject; |
| } |
| |
| if (!aMethod.isAccessible()) |
| aMethod.setAccessible(true); // We will allow all to occur. Let access control be handled by IDE and compiler. |
| newValue = aMethod.invoke(target, parms); |
| returnType = aMethod.getReturnType(); |
| if (returnType.isPrimitive()) { |
| int returnTypeID = server.getIdentityID(returnType); |
| // Need to tell sendObject the correct primitive type. |
| sendObject(newValue, returnTypeID, valueObject, out, true); |
| |
| } else { |
| sendObject(newValue, NOT_A_PRIMITIVE, valueObject, out, true); // Just send the object back. sendObject knows how to iterpret the type |
| } |
| } catch (CommandException e) { |
| throw e; // Throw it again. These we don't want to come up as an exception proxy. These should end the thread. |
| } catch (java.lang.reflect.InvocationTargetException e) { |
| // This is a wrappered exception. Return the wrappered one so it looks like |
| // it was the real one. (Sometimes the method being invoked is on a java.lang.reflect.Constructor.newInstance, |
| // which in turn is an InvocationTargetException, so we will go until we don't have an InvocationTargetException. |
| Throwable t = e; |
| do { |
| t = ((java.lang.reflect.InvocationTargetException) t).getTargetException(); |
| } while (t instanceof java.lang.reflect.InvocationTargetException); |
| sendException(t, valueObject, out); |
| } catch (Throwable e) { |
| sendException(e, valueObject, out); // Turn it into a exception proxy on the client. |
| } finally { |
| // Clear out for GC to work |
| valueObject.set(); |
| parms = null; |
| target = null; |
| aMethod = null; |
| returnType = null; |
| newValue = null; |
| valueSender.clear(); |
| } |
| break; |
| |
| case Commands.INVOKE_WITH_METHOD_PASSED: |
| aClass = null; |
| String methodName = null; |
| Class[] parmTypes = null; |
| target = null; |
| parms = null; |
| returnType = null; |
| aMethod = null; |
| |
| try { |
| Commands.readValue(in, valueObject); |
| aClass = (Class) getInvokableObject(valueObject); // The class that has the method. |
| methodName = in.readUTF(); |
| Commands.readValue(in, valueObject); |
| if (valueObject.type == Commands.ARRAY_IDS) { |
| // It is an array containing IDs, as it normally would be. |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| parmTypes = (Class[]) valueSender.getArray(); |
| } else { |
| // It null, so it should be an null. If not, then this is an error. |
| parmTypes = null; |
| } |
| aMethod = aClass.getMethod(methodName, parmTypes); |
| |
| // Now we get the info for the invocation of the method and execute it. |
| Commands.readValue(in, valueObject); |
| target = getInvokableObject(valueObject); |
| Commands.readValue(in, valueObject); |
| if (valueObject.type == Commands.ARRAY_IDS) { |
| // It is an array containing IDs, as it normally would be. |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| parms = (Object[]) valueSender.getArray(); |
| } else { |
| // It is all objects or null, so it should be an Object[] or null. If not, then this is an error. |
| parms = (Object[]) valueObject.anObject; |
| } |
| |
| if (!aMethod.isAccessible()) |
| aMethod.setAccessible(true); // We will allow all to occur. Let access control be handled by IDE and compiler. |
| newValue = aMethod.invoke(target, parms); |
| returnType = aMethod.getReturnType(); |
| if (returnType.isPrimitive()) { |
| int returnTypeID = server.getIdentityID(returnType); |
| // Need to tell sendObject the correct primitive type. |
| sendObject(newValue, returnTypeID, valueObject, out, true); |
| |
| } else { |
| sendObject(newValue, NOT_A_PRIMITIVE, valueObject, out, true); // Just send the object back. sendObject knows how to iterpret the type |
| } |
| } catch (CommandException e) { |
| throw e; // Throw it again. These we don't want to come up as an exception proxy. These should end the thread. |
| } catch (java.lang.reflect.InvocationTargetException e) { |
| // This is a wrappered exception. Return the wrappered one so it looks like |
| // it was the real one. (Sometimes the method being invoked is on a java.lang.reflect.Constructor.newInstance, |
| // which in turn is an InvocationTargetException, so we will go until we don't have an InvocationTargetException. |
| Throwable t = e; |
| do { |
| t = ((java.lang.reflect.InvocationTargetException) t).getTargetException(); |
| } while (t instanceof java.lang.reflect.InvocationTargetException); |
| sendException(t, valueObject, out); |
| |
| } catch (Throwable e) { |
| sendException(e, valueObject, out); // Turn it into a exception proxy on the client. |
| } finally { |
| aClass = null; |
| methodName = null; |
| parmTypes = null; |
| // Clear out for GC to work |
| valueObject.set(); |
| parms = null; |
| target = null; |
| aMethod = null; |
| returnType = null; |
| newValue = null; |
| valueSender.clear(); |
| } |
| break; |
| |
| case Commands.GET_ARRAY_CONTENTS: |
| try { |
| target = server.getObject(in.readInt()); // Array to get the ids for. |
| valueObject.setArrayIDS(new ArrayContentsRetriever(target), Array.getLength(target), Commands.OBJECT_CLASS); |
| Commands.writeValue(out, valueObject, true); // Write it back as a value command. |
| } catch (CommandException e) { |
| throw e; // Throw it again. These we don't want to come up as an exception proxy. These should end the thread. |
| } catch (Throwable e) { |
| sendException(e, valueObject, out); // Turn it into a exception proxy on the client. |
| } finally { |
| target = null; |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.CALLBACK_DONE: |
| try { |
| if (connectionThread != null) { |
| valueObject.set(); |
| Commands.sendErrorCommand(out, Commands.UNKNOWN_COMMAND_SENT, valueObject); |
| } else { |
| try { |
| Commands.readBackValue(in, valueObject, Commands.NO_TYPE_CHECK); |
| if (valueObject.type == Commands.ARRAY_IDS) { |
| // It is an array containing IDs, as it normally would be. |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| result = valueSender.getArray(); |
| } else { |
| result = getInvokableObject(valueObject); |
| } |
| doLoop = false; // We need to terminate and return result |
| closeWhenDone = false; // Don't close, we will continue. |
| } catch (CommandErrorException e) { |
| // There was an command error on the other side. This means |
| // connection still good, but don't continue the callback processing. |
| doLoop = false; // We need to terminate and return result |
| closeWhenDone = false; // Don't close, we will continue. |
| throw e; // Let it go on out. |
| } |
| } |
| } finally { |
| valueObject.set(); |
| valueSender.clear(); |
| } |
| break; |
| |
| case Commands.ERROR: |
| try { |
| // Got an error command. Don't know what to do but read the |
| // value and simply print it out. |
| Commands.readValue(in, valueObject); |
| result = getInvokableObject(valueObject); |
| System.out.println("Error sent to server: Result=" + result); //$NON-NLS-1$ |
| } finally { |
| valueObject.set(); |
| } |
| break; |
| |
| case Commands.EXPRESSION_TREE_COMMAND: |
| try { |
| processExpressionCommand(valueObject, valueSender); |
| } finally { |
| valueObject.set(); |
| valueSender.clear(); |
| } |
| break; |
| |
| default: |
| // Unknown command. We don't know how long it is, so we need to shut the connection down. |
| System.err.println("Error: Invalid cmd send to server: Cmd=" + cmd); //$NON-NLS-1$ |
| doLoop = false; |
| closeWhenDone = true; |
| break; |
| } |
| } |
| } catch (EOFException e) { |
| // This is ok. It means that the connection on the other side was terminated. |
| // So just accept this and go down. |
| } catch (CommandException e) { |
| throw e; |
| } catch (SocketException e) { |
| if (socket != null) |
| throw new UnexpectedExceptionCommandException(false, e); // socket null means a valid close request |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| } finally { |
| if (closeWhenDone) { |
| try { |
| for (Iterator itr = expressionProcessors.values().iterator(); itr.hasNext();) { |
| ExpressionProcesserController exp = (ExpressionProcesserController) itr.next(); |
| exp.close(); |
| } |
| } finally { |
| expressionProcessors.clear(); |
| } |
| |
| if (in != null) |
| try { |
| in.close(); |
| } catch (Exception e) { |
| } |
| in = null; |
| if (out != null) |
| try { |
| out.close(); |
| } catch (Exception e) { |
| } |
| out = null; |
| close(); |
| } |
| } |
| |
| if (closeWhenDone && connectionThread != null) |
| server.removeConnectionThread(connectionThread); |
| if (shutdown) |
| server.requestShutdown(); |
| |
| return result; |
| } |
| |
| // A retriever is what handles the array get contents. |
| private class ArrayContentsRetriever implements Commands.ValueRetrieve { |
| int index=0; |
| Object array; |
| int length; |
| int componentType; |
| Commands.ValueObject worker = new Commands.ValueObject(); |
| |
| |
| public ArrayContentsRetriever(Object anArray) { |
| array = anArray; |
| length = Array.getLength(anArray); |
| if (anArray.getClass().getComponentType().isPrimitive()) { |
| componentType = server.getIdentityID(anArray.getClass().getComponentType()); |
| } else |
| componentType = NOT_A_PRIMITIVE; |
| } |
| |
| public Commands.ValueObject nextValue() { |
| fillInValue(Array.get(array, index++), componentType, worker); |
| return worker; |
| } |
| }; |
| |
| |
| private void processExpressionCommand(Commands.ValueObject valueObject, InvokableValueSender valueSender) throws IOException, CommandException { |
| Integer expressionID = new Integer(in.readInt()); |
| byte cmdType = in.readByte(); |
| ExpressionProcesserController exp = null; |
| switch (cmdType) { |
| case ExpressionCommands.START_EXPRESSION_TREE_PROCESSING: |
| byte trace = in.readByte(); |
| if (trace == ExpressionCommands.TRACE_DEFAULT) |
| exp = new ExpressionProcesserController(server, this); |
| else |
| exp = new ExpressionProcesserController(server, this, trace == ExpressionCommands.TRACE_ON); |
| expressionProcessors.put(expressionID, exp); |
| break; |
| case ExpressionCommands.TRANSFER_EXPRESSION_REQUEST: |
| exp = (ExpressionProcesserController) expressionProcessors.remove(expressionID); |
| sendObject(exp, NOT_A_PRIMITIVE, valueObject, out, true); |
| break; |
| case ExpressionCommands.RESUME_EXPRESSION_REQUEST: |
| Commands.readValue(in, valueObject); |
| exp = (ExpressionProcesserController) getInvokableObject(valueObject); |
| expressionProcessors.put(expressionID, exp); |
| break; |
| case ExpressionCommands.PUSH_EXPRESSION: |
| exp = (ExpressionProcesserController) expressionProcessors.get(expressionID); |
| exp.process(in); |
| break; |
| case ExpressionCommands.SYNC_REQUEST: |
| boolean haveProxies = in.readBoolean(); // See if we expression proxy ids that need to be resolved |
| if (haveProxies) { |
| Commands.readValue(in, valueObject); |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| } |
| |
| exp = (ExpressionProcesserController) expressionProcessors.get(expressionID); |
| if (haveProxies) |
| sendExpressionProxyResolutions(valueObject, (int[]) valueSender.getArray(), exp); |
| if (exp.noErrors()) { |
| valueObject.set(true); // Mark that all is good. |
| Commands.writeValue(out, valueObject, true, true); |
| } else { |
| processExpressionError(exp, valueObject); |
| } |
| break; |
| case ExpressionCommands.PULL_VALUE_REQUEST: |
| haveProxies = in.readBoolean(); // See if we expression proxy ids that need to be resolved |
| if (haveProxies) { |
| Commands.readValue(in, valueObject); |
| valueSender.initialize(valueObject); |
| Commands.readArray(in, valueObject.anInt, valueSender, valueObject, false); |
| } |
| |
| exp = (ExpressionProcesserController) expressionProcessors.get(expressionID); |
| if (haveProxies) |
| sendExpressionProxyResolutions(valueObject, (int[]) valueSender.getArray(), exp); |
| if (exp.noErrors()) { |
| try { |
| Object[] pulledValue = exp.pullValue(); |
| // Send back the command code for pull value. Don't flush. We will flush when all done. |
| if (((Class) pulledValue[1]).isPrimitive()) { |
| int returnTypeID = server.getIdentityID(pulledValue[1]); |
| // Need to tell sendObject the correct primitive type. |
| sendObject(pulledValue[0], returnTypeID, valueObject, out, true, true); |
| |
| } else { |
| sendObject(pulledValue[0], NOT_A_PRIMITIVE, valueObject, out, true, true); // Just send the object back. sendObject knows how to iterpret the type |
| } |
| } catch (NoExpressionValueException e) { |
| sendNoValueErrorCommand(exp, valueObject); |
| } |
| } else |
| processExpressionError(exp, valueObject); |
| break; |
| case ExpressionCommands.END_EXPRESSION_TREE_PROCESSING: |
| exp = (ExpressionProcesserController) expressionProcessors.remove(expressionID); |
| exp.close(); |
| break; |
| } |
| } |
| |
| /* |
| * @param is |
| * @param exp |
| * |
| * @since 1.1.0 |
| */ |
| private void sendExpressionProxyResolutions(Commands.ValueObject valueObject, final int[] proxyIDs, final ExpressionProcesserController exp) throws CommandException { |
| class ExpressionProxyRetriever implements Commands.ValueRetrieve { |
| int index = 0; |
| Object[] proxyResolution = new Object[2]; |
| Commands.ValueObject worker = new Commands.ValueObject(); |
| |
| public Commands.ValueObject nextValue() { |
| int proxyID = proxyIDs[index++]; |
| if (exp.pullExpressionProxyValue(proxyID, proxyResolution)) { |
| if (proxyResolution[1] != Void.TYPE) { |
| if (((Class) proxyResolution[1]).isPrimitive()) { |
| int returnTypeID = server.getIdentityID(proxyResolution[1]); |
| // Need to tell worker the correct primitive type. |
| fillInValue(proxyResolution[0], returnTypeID, worker); |
| } else { |
| fillInValue(proxyResolution[0], NOT_A_PRIMITIVE, worker); |
| } |
| } else |
| worker.setFlag(ExpressionCommands.EXPRESSIONPROXY_VOIDTYPE); // It was resolved, but to not set due to void type of expression. |
| } else |
| worker.setFlag(ExpressionCommands.EXPRESSIONPROXY_NOTRESOLVED); // It wasn't resolved. |
| |
| return worker; |
| } |
| }; |
| |
| valueObject.setArrayIDS(new ExpressionProxyRetriever(), proxyIDs.length, Commands.OBJECT_CLASS); |
| Commands.writeValue(out, valueObject, true, false); // Write it back as a value command. |
| |
| } |
| |
| private void processExpressionError(ExpressionProcesserController exp, Commands.ValueObject valueObject) throws CommandException { |
| if (exp.isNoExpressionValue()) { |
| sendNoValueErrorCommand(exp, valueObject); |
| } else |
| sendException(exp.getErrorThrowable(), valueObject, out); |
| } |
| |
| /* |
| * @param exp |
| * @param valueObject |
| * @throws CommandException |
| * |
| * @since 1.1.0 |
| */ |
| private void sendNoValueErrorCommand(ExpressionProcesserController exp, Commands.ValueObject valueObject) throws CommandException { |
| setExceptionIntoValue(exp.getErrorThrowable(), valueObject); |
| Commands.sendErrorCommand(out, ExpressionCommands.EXPRESSION_NOEXPRESSIONVALUE_EXCEPTION, valueObject); |
| } |
| |
| protected static final int NOT_A_PRIMITIVE = Commands.NOT_AN_ID; |
| protected static final int SEND_AS_IS = -2; // This means sends as an object not as an id. |
| |
| public static final int CLASS_ADDED = 1; |
| public static final int OBJECT_ADDED = 2; |
| public int fillInValue(Object object, int primitiveTypeID, Commands.ValueObject valueObject) { |
| int added = 0; |
| if (object == null) { |
| valueObject.set(); |
| } else { |
| int classID = 0; |
| if (primitiveTypeID != NOT_A_PRIMITIVE && primitiveTypeID != SEND_AS_IS) { |
| classID = primitiveTypeID; // The object is really supposed to be a primitive of this type |
| switch (classID) { |
| case Commands.BOOLEAN_TYPE: |
| valueObject.set(((Boolean) object).booleanValue()); |
| break; |
| case Commands.INTEGER_TYPE: |
| if (object instanceof Character) |
| valueObject.set((int) ((Character) object).charValue()); // Because char can be widened to an int |
| else |
| valueObject.set(((Number) object).intValue()); |
| break; |
| case Commands.BYTE_TYPE: |
| valueObject.set(((Number) object).byteValue()); |
| break; |
| case Commands.CHARACTER_TYPE: |
| valueObject.set(((Character) object).charValue()); |
| break; |
| case Commands.DOUBLE_TYPE: |
| if (object instanceof Character) |
| valueObject.set((double) ((Character) object).charValue()); // Because char can be widened to a double |
| else |
| valueObject.set(((Number) object).doubleValue()); |
| break; |
| case Commands.FLOAT_TYPE: |
| if (object instanceof Character) |
| valueObject.set((float) ((Character) object).charValue()); // Because char can be widened to a float |
| else |
| valueObject.set(((Number) object).floatValue()); |
| break; |
| case Commands.SHORT_TYPE: |
| valueObject.set(((Number) object).shortValue()); |
| break; |
| case Commands.LONG_TYPE: |
| if (object instanceof Character) |
| valueObject.set((long) ((Character) object).charValue()); // Because char can be widened to a long |
| else |
| valueObject.set(((Number) object).longValue()); |
| break; |
| } |
| } else { |
| // It's not a primitive. |
| boolean addObject = false, addClass = false; |
| Class objClass = object.getClass(); |
| classID = server.getIdentityID(objClass); |
| if (classID == RemoteVMServerThread.ID_NOT_FOUND) { |
| Commands.ValueObject classValue = new Commands.ValueObject(); |
| addClass = server.getIdentityID(objClass, classValue); |
| if (addClass) |
| added |= CLASS_ADDED; |
| classID = classValue.objectID; |
| } |
| |
| switch (classID) { |
| case Commands.BYTE_CLASS: |
| valueObject.set((Byte) object); |
| break; |
| case Commands.CHARACTER_CLASS: |
| valueObject.set((Character) object); |
| break; |
| case Commands.DOUBLE_CLASS: |
| valueObject.set((Double) object); |
| break; |
| case Commands.FLOAT_CLASS: |
| valueObject.set((Float) object); |
| break; |
| case Commands.INTEGER_CLASS: |
| valueObject.set((Integer) object); |
| break; |
| case Commands.LONG_CLASS: |
| valueObject.set((Long) object); |
| break; |
| case Commands.SHORT_CLASS: |
| valueObject.set((Short) object); |
| break; |
| case Commands.BOOLEAN_CLASS: |
| valueObject.set((Boolean) object); |
| break; |
| case Commands.STRING_CLASS: |
| valueObject.set((String) object); |
| break; |
| case Commands.BIG_DECIMAL_CLASS: |
| valueObject.set(object, Commands.BIG_DECIMAL_CLASS); |
| break; |
| case Commands.BIG_INTEGER_CLASS: |
| valueObject.set(object, Commands.BIG_INTEGER_CLASS); |
| break; |
| default: |
| if (primitiveTypeID != SEND_AS_IS) { |
| addObject = server.getIdentityID(object, valueObject); |
| if (addObject) { |
| added |= OBJECT_ADDED; |
| valueObject.setObjectID(valueObject.objectID, classID); |
| } else |
| valueObject.setObjectID(valueObject.objectID); |
| } else |
| valueObject.set(object, classID); |
| break; |
| } |
| } |
| } |
| |
| return added; |
| } |
| |
| public void sendObject(Object object, int primitiveTypeID, Commands.ValueObject valueObject, DataOutputStream out, boolean writeAsCommand) throws CommandException { |
| sendObject(object, primitiveTypeID, valueObject, out, writeAsCommand, writeAsCommand); |
| } |
| public void sendObject(Object object, int primitiveTypeID, Commands.ValueObject valueObject, DataOutputStream out, boolean writeAsCommand, boolean flush) throws CommandException { |
| int added = fillInValue(object, primitiveTypeID, valueObject); |
| boolean sent = false; |
| try { |
| Commands.writeValue(out, valueObject, writeAsCommand, flush); // Write it back as a value command. |
| sent = true; |
| } finally { |
| if (!sent) { |
| // Ending due to some problem, so clean up any added objects from the id table. |
| if ((added & OBJECT_ADDED) != 0) |
| server.removeObject(valueObject.objectID); |
| if ((added & CLASS_ADDED) != 0) |
| server.removeObject(valueObject.classID); |
| } |
| } |
| } |
| |
| |
| public void sendException(Throwable e, Commands.ValueObject value, DataOutputStream out) throws CommandException { |
| // Exception ocurred, return a value command with the exception within it. |
| setExceptionIntoValue(e, value); |
| Commands.sendErrorCommand(out, Commands.THROWABLE_SENT, value); |
| } |
| |
| public void setExceptionIntoValue(Throwable e, Commands.ValueObject value) { |
| server.getIdentityID(e, value); // It should always create it so we don't need to know. |
| int eID = value.objectID; |
| Class eClass = e.getClass(); |
| server.getIdentityID(eClass, value); |
| int eClassID = value.objectID; |
| value.setException(eID, eClassID); |
| } |
| |
| /** |
| * From the valueObject, get an Object that is invokable (i.e. can be the target of an invoke, or one of its parms). |
| * If it is an array type, the a ValueSender is returned. The invoker needs to then cast this |
| * to a ValueSender and call the readArray routine. |
| */ |
| public Object getInvokableObject(final Commands.ValueObject value) { |
| switch (value.type) { |
| case Commands.NEW_OBJECT_ID: |
| case Commands.OBJECT_ID: |
| // These need to have access to the server to get the real object |
| return server.getObject(value.objectID); |
| default: |
| // These have all the info needed within the value object itself, so ask it. |
| return value.getAsObject(); |
| } |
| } |
| |
| // Helper class for getting an array. |
| private class InvokableValueSender implements Commands.ValueSender { |
| int index = 0; |
| Object array; |
| |
| public InvokableValueSender() { |
| } |
| |
| public InvokableValueSender(Commands.ValueObject arrayHeader) { |
| initialize(arrayHeader); |
| } |
| |
| public void initialize(Commands.ValueObject arrayHeader) { |
| index = 0; |
| Class arrayType = (Class) server.getObject(arrayHeader.classID); |
| array = java.lang.reflect.Array.newInstance(arrayType, arrayHeader.anInt); |
| } |
| |
| public void clear() { |
| array = null; |
| index = 0; |
| } |
| |
| public Object getArray() { |
| return array; |
| } |
| |
| // A new value is being sent to the array |
| public void sendValue(Commands.ValueObject value) { |
| java.lang.reflect.Array.set(array, index++, getInvokableObject(value)); // add it to the array |
| } |
| |
| // The next entry is an array too! |
| public Commands.ValueSender nestedArray(Commands.ValueObject arrayHeader) { |
| InvokableValueSender sender = new InvokableValueSender(arrayHeader); |
| // Take the newly created array and put it into the current array. |
| java.lang.reflect.Array.set(array, index++, sender.getArray()); |
| return sender; |
| } |
| } |
| |
| } |
| |
| |