/******************************************************************************* * 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.common.remote; /* */ import java.io.*; import org.eclipse.jem.internal.proxy.common.CommandException; /** * The commands that can be passed back and forth between * client and server. And other constants. * * - Contains helper methods for reading/writing commands. */ public class Commands { // The commands will be written in writeByte format . public final static byte GET_CLASS = 1, // Get the class object, VALUE = 2, // Returning a value QUIT_CONNECTION = 4, // Close this connection TERMINATE_SERVER = 5, // Terminate the entire server. ERROR = 6, // Returning an error RELEASE_OBJECT = 7, // An object is no longer needed on the client side, so // it can be removed from the server id table and released. GET_CLASS_RETURN = 8, // The return command from GET_CLASS // Obsolete, not used anymore GET_METHOD = 9, // Return the id for a method // Obsolete, not used anymore GET_CTOR = 10, // Return the id for a constructor NEW_INIT_STRING = 11, // Create a new bean using the init string GET_CLASS_FROM_ID = 12, // We have an ID, return the class info for this id. GET_CLASS_ID_RETURN = 13, // The return command from GET_CLASS_FROM_ID GET_OBJECT_DATA = 14, // We have an ID, but we don't have the info, return it. This is a // corrective command only. This would happen if for some strange // reason the proxy has been removed but has not been released. This // really shouldn't happen except as a possible race condition between // GC and returning id from the server. INVOKE = 15, // Invoke a method. // These commands are to the Master Server thread in the IDE. ALIVE = 16, // Are you alive? REMOTE_STARTED = 17, // Remote VM has started. ATTACH_CALLBACK = 18, // Attach to a callback thread on the IDE side. The remote vm will use its socket as the callback socket. // it will return boolean <code>true</code> if attach worked or <code>false</code> if it failed. // These are more regular commands. They were historically added after the master server thread commands, so // they are shown here after them and with numbers greater than them. EXPRESSION_TREE_COMMAND = 19, // An expression tree subcommand has come in. INVOKE_WITH_METHOD_PASSED = 20, // Invoke where the description of the method is passed in with the command. GET_ARRAY_CONTENTS = 21; // Get the first dimension contents as an array of ids and send them back. // Callback commands public final static byte CALLBACK = (byte) 255, // A callback has come in. CALLBACK_DONE = (byte) 254, // A callback done command, sent to the remote vm upon callback completion. CALLBACK_STREAM = (byte) 253, // A callback for a byte stream has come in. // This is a special callback. When this comes in a special // input stream will be created that will take over control of // the connection until the stream is terminated on the remote // side. At this time the connection will be returned. CALLBACK_STREAM_TERMINATE = (byte) 252; // A callback stream is asked to terminate early. // The error values from the command on the server. public final static int NO_ERROR = 0, // No error status. UNKNOWN_COMMAND_SENT = 1, // An unknown command was sent to the server. Value is void. GET_CLASS_NOT_FOUND = 2, // The class was not found in GetClass. Value is void. CANNOT_EVALUATE_STRING = 3, // Evaluator couldn't evaluate the init string. Too complicated. Value is a throwable of the wrappered Init string error. CLASS_CAST_EXCEPTION = 4, // The result is not assignable to the expected type. Value is void. GET_METHOD_NOT_FOUND = 5, // Method requested wasn't found. Value is void. THROWABLE_SENT = 6, // A Throwable is being sent back as the error, not as just data for the error. Value is the Throwable. CALLBACK_RUNTIME_EXCEPTION = 7, // A runtime exception occurred during a callback. The data is the message. CALLBACK_NOT_REGISTERED = 8, MAX_ERROR_CODE = CALLBACK_NOT_REGISTERED; // This is just the max code. Not actually sent. Used as a flag. // Predefined standard id's for standard classes/objects. Both sides will assume these id's have been assigned // to these classes/types/objects public final static int NOT_AN_ID = -1, // This value means it is not an id. It is never a valid id. VOID_TYPE = 0, BOOLEAN_TYPE = 1, BOOLEAN_CLASS = 2, INTEGER_TYPE = 3, INTEGER_CLASS = 4, BYTE_TYPE = 5, BYTE_CLASS = 6, CHARACTER_TYPE = 7, CHARACTER_CLASS = 8, DOUBLE_TYPE = 9, DOUBLE_CLASS = 10, FLOAT_TYPE = 11, FLOAT_CLASS = 12, SHORT_TYPE = 13, SHORT_CLASS = 14, LONG_TYPE = 15, LONG_CLASS = 16, STRING_CLASS = 17, BIG_DECIMAL_CLASS = 18, BIG_INTEGER_CLASS = 19, NUMBER_CLASS = 20, THROWABLE_CLASS = 21, CLASS_CLASS = 22, OBJECT_CLASS = 23, ACCESSIBLEOBJECT_CLASS = 24, METHOD_CLASS = 25, FIELD_CLASS = 26, CONSTRUCTOR_CLASS = 27, GET_METHOD_ID = 28, // Class.getMethod(...) predefined id. IVMSERVER_CLASS = 29, // IVMServer.class ICALLBACK_CLASS = 30, // ICallback.class REMOTESERVER_ID = 31, // id of RemoteVMServerThread instance. REMOTEVMSERVER_CLASS = 32, // RemoteVMServer.class INITIALIZECALLBACK_METHOD_ID = 33, // ICallback.initializeCallback method. THREAD_CLASS = 34, EXPRESSIONPROCESSERCONTROLLER_CLASS = 35, FIRST_FREE_ID = 36; // The type flags written in writeByte format public final static byte VOID = VOID_TYPE, // null - nothing follows BYTE = BYTE_TYPE, // byte - writeByte L_BYTE = BYTE_CLASS, // java.lang.Byte - writeByte CHAR = CHARACTER_TYPE, // char - writeChar L_CHAR = CHARACTER_CLASS, // java.lang.Character - writeChar DOUBLE = DOUBLE_TYPE, // double - writeDouble L_DOUBLE = DOUBLE_CLASS, // java.lang.Double - writeDouble FLOAT = FLOAT_TYPE, // float - writeFloat L_FLOAT = FLOAT_CLASS, // java.lang.Float - writeFloat INT = INTEGER_TYPE, // int - writeInt L_INT = INTEGER_CLASS, // java.lang.Integer - writeInt LONG = LONG_TYPE, // long - writeLong L_LONG = LONG_CLASS, // java.lang.Long - writeLong SHORT = SHORT_TYPE, // short - writeShort L_SHORT = SHORT_CLASS, // java.lang.Short - writeShort BOOL = BOOLEAN_TYPE, // boolean - writeBoolean L_BOOL = BOOLEAN_CLASS, // java.lang.Boolean - writeBoolean STRING = STRING_CLASS, // java.lang.String - writeUTF OBJECT = OBJECT_CLASS, // Object - special, see below (Object can be used to return an array (except if the array contains any object_ids, that has a special type) OBJECT_ID = 50, // Object identity key - writeInt NEW_OBJECT_ID = 51, // New Object identity (this is a new object that didn't exist before) THROW = 52, // An exception occured. The value is a throwable, it is of the same format as NEW_OBJECT_ID. ARRAY_IDS = 53, // An array of values, where there are at least one ID in the array. If there were no // ID's (i.e. all just values), then use OBJECT type intead and have it written as // writeObject. FLAG = 54; // The value is a flag int. If this is allowed on a read, the anInt field will contain the flag value. // Unless specified below, the commands are one byte long. // Also, unless specified below, the commands do not return a confirmation response. // // NOTE: VERY IMPORTANT, after every command, flush() should be used so that the // the data is immediately sent to the server. // // n means int (e.g. 1) // nb means byte (e.g. 1b) // 'x' means Unicode char (i.e. writeChar()) // "xxx" means UTF8 string (i.e. writeUTF) // bool means a one byte boolean value // // The commas aren't actually written, they are used as separaters in the comments below // // GET_CLASS: 1b, "classname" // Will return on the output stream GET_CLASS_RETURN command: // 8b, n1, bool1, bool2, "superclassname" // The "n1" is the class id. // The bool1 is whether this class is an interface (true if it is). // The bool2 is whether this class is abstract (true if it is). // The "superclassname" is the class name of the super class (0 length if no superclass) // If the class is not found, then it will return an error with a value for the error. // // GET_CLASS_FROM_ID: 12b, n // Where "n" is the class id. // Will return on the output stream GET_CLASS_ID_RETURN command: // 13b, "classname", bool1, bool2, "superclassname" // The bool1 is whether this class is an interface (true if it is). // The bool2 is whether this class is abstract (true if it is). // // VALUE: 2b, tb, value // Where tb is the type in byte, and value is the appropriate value shown in // table above. // OBJECT_ID: 50b, n // Where "n" is the object id. // NEW_OBJECT_ID: 51b, n1, n2 // Where "n1" is class ObjectID of the object that the object_id ("n2") is made of. // OBJECT: 19b, n, writeObject // Where "n" is the classObjectID of the class of the type of the object. // NOTE: Object should be used only very rarely. Identity is lost, i.e. // a copy is made each time and it can't be referenced back on the remote // VM. // ARRAY_IDS: 52b, id, n, [tb, value, ...] // This is a very special array. It contains at least one ID. Therefor all of the // First level entries are value objects. // "id" is the id of the component type of the array(e.g. id for Object, or if multi-dimensional String[] (this will produce String[][]). // "n" is the number of entries in the array. Followed by the values, one of the // values could be an ARRAY_IDS too. The reading/writing of these are special because // there is a callback mechanism to process the individual entries. This is so that // temp arrays of ValueObjects won't need to be created to handle this, so it can // go directly from the array to/from the stream. // // RELEASE_OBJECT: 7b, n // Where the n is the object id to release. There is no confirmation to read back. // // ERROR: 6b, n, tb, ... // n is the error code for this error. // tb is a type flag, followed by the value. The value is dependent upon // the command that this is error is from. If a THROW, then the THROW is ALWAYS a new // ID, it can never be an existing id. // // // TO_BEAN_STRING: 9b, n // Where n is the object id to produce the bean string for. // It will return a VALUE command where the type is String. // // NEW_INSTANCE: 10b, n // Where n is the class object id of the class to create a new instance of using the default ctor. // It will return either a VALUE command containing the new value (of type OBJECT_ID/NEW_OBJECT_ID if not // one of the constant types with the true classID in it) or an ERROR command. (The ERROR could // be a THROW type). If the object created is not assignable to the type passed in, then // an ERROR is returned with CLASS_CAST_EXCEPTION flag. // // NEW_INIT_STRING: 11b, n, "initstring" // Where n is the class object id of the class this initstring is supposed to create for. // It will return either a VALUE command containing the new value (of type OBJECT_ID/NEW_OBJECT_ID if not // one of the constant types with the true classID in it) or an ERROR command. (The ERROR could // be a THROW type). The error could also be CANNOT_EVALUATE_STRING. This means that the string was too // complicated for the evaluator and needs to be compiled and tried again. (TBD) // If the object created is not assignable to the type passed in, then // an ERROR is returned with CLASS_CAST_EXCEPTION flag. // // GET_OBJECT_DATA: 14b, n // Where n is the id of the object being requested. It will return a NEW_OBJECT_ID value with the info. // // GET_METHOD: 9b, classId, "methodName", n1, [n2]... // Where classID is the id of the class the method should be found in. // Where n1 is the number of parm types following, and n2 is replicated that many times, // each entry is the id of class for the parm type. (0 is valid which means there are no parms). // The return will be a VALUE command containing the OBJECT_ID of the method. // // GET_CTOR: 10b, classId, n1, [n2]... // Where classID is the id of the class the method should be found in. // Where n1 is the number of parm types following, and n2 is replicated that many times, // each entry is the id of class for the parm type. (0 is valid which means there are no parms). // The return will be a VALUE command containing the OBJECT_ID of the method. // // GET_FIELD: // // GET_CTOR: // // INVOKE: 15b, n1, tb, value1, value2 // Where "n1" is the id of the method to invoke. // tb, value1 is the value of who to invoke against (it is usually an OBJECT_ID for tb) // value2 is an ARRAY_IDS type or an OBJECT array of values if all constants. // What is returned is a VALUE command containing the return value, (the value will be null (VOID) if // there is no return type (i.e. the method was void). So null can be returned either if the value // was null or if the return type was void. // // EXPRESSION_TREE_COMMAND: 20b, n, b // Receiving an expression tree subcommand. Where "n" is a unique id number of the // expression being processed. Where "b" is byte code, defined in ExpressionCommands, that // determines the type of expression tree commands. // There can be more data following, but it is read by the // ExpressionProcesserController, not by the connection. See the controller for the subcommands. // // The id number is the id of the expression being processed. This allows more than one expression // to be processed at a time from this connection. // // @see ExpressionCommands // @see ExpressionProcessController // // INVOKE_WITH_METHOD_PASSED: 20b, classId, "methodName", value0, tb, value1, value2 // Where classID is the id of the class the method should be found in. // value0 is an ARRAY_IDS type for the type of the parms, or null type for no parms. // tb, value1 is the value of who to invoke against (it is usually an OBJECT_ID for tb) // value2 is an ARRAY_IDS type or an OBJECT array of values if all constants. // What is returned is a VALUE command containing the return value, (the value will be null (VOID) if // there is no return type (i.e. the method was void). So null can be returned either if the value // was null or if the return type was void. // // GET_ARRAY_CONTENTS: 21b, arrayId // Where arrayID is the id of the array to get the contents of. What is returned is a value command // containing an array of ids of the first dimension contents. // // Callback commands: // // CALLBACK: 255b, n1, n2, value1 // Where // "n1" is the id of callback type (these are registered with the callback server) // "n2" is the msgId for the callback (These are entirely callback dependent and are maintained by the callback developer) // value1 is an ARRAY_IDS type or an OBJECT array of values if all constants. These are // parms to send to the callback msg. // It will return a CALLBACK_DONE. // // CALLBACK_DONE: 254b, value command. // What comes back is a value command (i.e. Commands.VALUE followed by value). This allows // ERRORS to be sent back too. // // CALLBACK_STREAM: 253b, n1, n2 // Where // "n1" is the id of callback type (these are registered with the callback server) // "n2" is the msgId for the callback (These are entirely callback dependent and are maintained by the callback developer) // It will create a CallbackInputStream and notify the registered callback that the // stream is available. It will send a callback_done when it has accepted the request // but before it notifies the registered callback with the stream. This lets the remote // vm know that it can start sending data. // To the MasterServer socket: // The MasterServer socket will expect input in DataInputStream format, and DataOutputStream for return. // The socket will be short-lived. It will be for one transaction only. Each request will return a new socket. // // ALIVE: 16b, n1 // Where // "n1" is the id of the registry this is asking to test for aliveness // Will return bool, where false if registry is not alive, true if it is alive. // REMOTE_STARTED: 17b, n1, n2 // Where // "n1" is the id of the registry this is telling that it is started // "n2" is the serversocket port number of the server socket in this remote vm. // Will return bool, where false if registry is not alive, true if it is alive. If false, then terminate the server because nothing to talk to. // GET_CALLBACK_PORT: 18b, n1 // Where // "n1" is the id of the registry this is asking for the callback server port. // Will return int, where the value is the callback server port number. -1 if there is no callback server port. /** * This class is the return from a read value. It contains the * type of the value and the value itself. Since primitives can be * returned also, there is a slot for each one and the type should * be checked to see which one is set. * * Also, if the type is OBJECT, then the anObject has the object in it, AND * the classID field has the object_id of the class of the object so that the * appropriate beantypeproxy can be found to use that object. Also, only * IREMConstantBeanTypeProxies can be of type OBJECT. That is because those * are the only ones that know how to take the value object and interpret it. * * If the type is OBJECT_ID or NEW_OBJECT_ID, then the objectID field will be set with * the id. * If the type is NEW_OBJECT_ID, then the classID field will * have the class objectID of the class of the object for which object_id proxies. * * THROW is treated like NEW_OBJECT_ID in what fields are set since it is a new object. * * Note: so as not to create unnecessary objects, if the Object type of the primitive is being * sent, then the primitive field will be set instead, though the type * will still be the Object type (i.e. if type = L_BYTE, the aByte will * have the value in it). * * Note: Also flags can be send back. The type will be FLAG and the anInt field will be the * flag value. This is used to indicate special things that aren't values. Most useful in * arrays where one of the entries is not a value. This can only be used if readValue * is passed a flag indicating flags are valid, otherwise it will be treated as not valie. */ public static class ValueObject implements Cloneable { public byte type; // Same as the types above public byte aByte; public char aChar; public double aDouble; public float aFloat; public int anInt; public short aShort; public long aLong; public boolean aBool; public int objectID; // The object id for either OBJECT_ID or NEW_OBJECT_ID. public int classID; // The class object id of the value in Object if the type is Object public Object anObject; // String also will be in here public ValueObject() { type = VOID; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } /** * Return whether the value stored here is a primitive. * * @return <code>true</code> if value is a primitive type. * * @since 1.0.0 */ public boolean isPrimitive() { return getPrimitiveType().isPrimitive(); } /** * Get the primitive type of the value. * @return The primitive type, or if not primitive, it returns simply <code>Object.class</code>. * * @since 1.0.0 */ public Class getPrimitiveType() { switch (type) { case BYTE: return Byte.TYPE; case CHAR: return Character.TYPE; case DOUBLE: return Double.TYPE; case FLOAT: return Float.TYPE; case INT: return Integer.TYPE; case SHORT: return Short.TYPE; case LONG: return Long.TYPE; case BOOL: return Boolean.TYPE; default: return Object.class; } } /** * Get the type as one of the valid Commands.Types. VOID, BYTE, L_BYTE, etc. * @return * * @since 1.1.0 */ public int getType() { return type; } /** * Special getter to get the type as an Object, this is used by invoke for example. */ public Object getAsObject() { switch (type) { case VOID: return null; case BYTE: case L_BYTE: return new Byte(aByte); case CHAR: case L_CHAR: return new Character(aChar); case DOUBLE: case L_DOUBLE: return new Double(aDouble); case FLOAT: case L_FLOAT: return new Float(aFloat); case INT: case L_INT: return new Integer(anInt); case SHORT: case L_SHORT: return new Short(aShort); case LONG: case L_LONG: return new Long(aLong); case BOOL: case L_BOOL: return aBool ? Boolean.TRUE : Boolean.FALSE; case STRING: return anObject; case OBJECT: return anObject; default: return null; // Can't handle others. Those need to be checked before calling. } } /** * Special setter to set the value depending upon the type. */ public void setAsObject(Object value, int valueClassID) { switch (valueClassID) { case VOID: set(); break; case BYTE_CLASS: set((Byte) value); break; case CHARACTER_CLASS: set((Character) value); break; case DOUBLE_CLASS: set((Double) value); break; case FLOAT_CLASS: set((Float) value); break; case INTEGER_CLASS: set((Integer) value); break; case SHORT_CLASS: set((Short) value); break; case LONG_CLASS: set((Long) value); break; case BOOLEAN_CLASS: set((Boolean) value); break; case STRING_CLASS: set((String) value); break; default: set(value, valueClassID); break; } } public void set() { type = VOID; anObject = null; } public void setFlag(int flag) { type = FLAG; anInt = flag; } public void set(byte value) { type = BYTE; aByte = value; anObject = null; } public void set(Byte value) { if (value != null) { type = L_BYTE; aByte = value.byteValue(); anObject = null; } else set(); } public void set(char value) { type = CHAR; aChar = value; anObject = null; } public void set(Character value) { if (value != null) { type = L_CHAR; aChar = value.charValue(); anObject = null; } else set(); } public void set(double value) { type = DOUBLE; aDouble = value; anObject = null; } public void set(Double value) { if (value != null) { type = L_DOUBLE; aDouble = value.doubleValue(); anObject = null; } else set(); } public void set(float value) { type = FLOAT; aFloat = value; anObject = null; } public void set(Float value) { if (value != null) { type = L_FLOAT; aFloat = value.floatValue(); anObject = null; } else set(); } public void set(int value) { type = INT; anInt = value; anObject = null; } public void set(Integer value) { if (value != null) { type = L_INT; anInt = value.intValue(); anObject = null; } else set(); } public void set(short value) { type = SHORT; aShort = value; anObject = null; } public void set(Short value) { if (value != null) { type = L_SHORT; aShort = value.shortValue(); anObject = null; } else set(); } public void set(long value) { type = LONG; aLong = value; anObject = null; } public void set(Long value) { type = L_LONG; aLong = value.longValue(); anObject = null; } public void set(boolean value) { type = BOOL; aBool = value; anObject = null; } public void set(Boolean value) { if (value != null) { type = L_BOOL; aBool = value.booleanValue(); anObject = null; } else set(); } public void set(String value) { if (value != null) { type = STRING; anObject = value; } else set(); } public void set(Object value, int classObjectID) { if (value != null) { type = OBJECT; classID = classObjectID; anObject = value; } else set(); } public void setObjectID(int value) { type = OBJECT_ID; objectID = value; anObject = null; } // Use this if the object is an array containing IDs. The retriever // will be used to get the next value to write to the stream. public void setArrayIDS(ValueRetrieve retriever, int arraySize, int componentType) { type = ARRAY_IDS; classID = componentType; anInt = arraySize; anObject = retriever; } // Use this if this is a new object so that we can get the correct class type. public void setObjectID(int value, int classObjectID) { type = NEW_OBJECT_ID; objectID = value; classID = classObjectID; anObject = null; } // Use this to indicate an exception occured. public void setException(int throwID, int throwClassID) { type = THROW; objectID = throwID; classID = throwClassID; anObject = null; } } /************************ * Helpful commands. * - If a command throws any exception except CommandErrorException, or * UnexpectedCommandException with recoverable true, then the connection is in a bad state * and needs to be closed. ************************/ /** * Use this to read a value (inputstream should be pointing to the type byte as the next byte to read). * The primitive fields of "value" will not be changed if they are not the * type of the value being read. However, anObject will be set to null. */ /** * Error flags for UnexpectedCommandExceptions that can be thrown. */ public static final Object UNKNOWN_READ_TYPE = "UNKNOWN_READ_TYPE"; // The read type byte was not a valid type //$NON-NLS-1$ public static final Object UNKNOWN_WRITE_TYPE = "UNKNOWN_WRITE_TYPE"; // The write type byte was not a valid type //$NON-NLS-1$ public static final Object TYPE_INVALID_FOR_COMMAND = "TYPE_INVALID_FOR_COMMAND"; // The data type read is not valid for this command //$NON-NLS-1$ public static final Object UNKNOWN_COMMAND = "UNKNOWN_COMMAND"; // The command flag is unknown //$NON-NLS-1$ public static final Object SOME_UNEXPECTED_EXCEPTION = "SOME_UNEXPECTED_EXCEPTION"; // There was some kind of exception that wasn't expected. The data will be the exception. //$NON-NLS-1$ public static final Object TOO_MANY_BYTES = "TOO_MANY_BYTES"; // Too many bytes were sent on a writeBytes. It was //$NON-NLS-1$ // more than could be read into the buffer. The data will be the size sent. /** * Read a value from the stream into the value object. It will not allow values of type FLAG. * * @param is * @param value * @param allowFlag * @return the value object sent in. This allows <code>value = Commands.readValue(is, new Commands.ValueObject());</code> * @throws CommandException * * * @since 1.0.0 */ public static ValueObject readValue(DataInputStream is, ValueObject value) throws CommandException { readValue(is, value, false); return value; } /** * Read a value from the stream into the value object. It will allow values of type FLAG if allowFlag is true. * @param is * @param value * @param allowFlag <code>true</code> if values of type flag are allow. * @throws CommandException * * @since 1.1.0 */ public static void readValue(DataInputStream is, ValueObject value, boolean allowFlag) throws CommandException { try { value.anObject = null; value.type = is.readByte(); switch (value.type) { case BYTE: case L_BYTE: value.aByte = is.readByte(); break; case CHAR: case L_CHAR: value.aChar = is.readChar(); break; case DOUBLE: case L_DOUBLE: value.aDouble = is.readDouble(); break; case FLOAT: case L_FLOAT: value.aFloat = is.readFloat(); break; case INT: case L_INT: value.anInt = is.readInt(); break; case OBJECT_ID: value.objectID = is.readInt(); break; case NEW_OBJECT_ID: value.classID = is.readInt(); value.objectID = is.readInt(); break; case THROW: value.classID = is.readInt(); value.objectID = is.readInt(); break; case SHORT: case L_SHORT: value.aShort = is.readShort(); break; case LONG: case L_LONG: value.aLong = is.readLong(); break; case BOOL: case L_BOOL: value.aBool = is.readBoolean(); break; case STRING: value.anObject = readStringData(is); break; case OBJECT: value.classID = is.readInt(); // Read the class id ObjectInputStream oi = new ObjectInputStream(is); value.anObject = oi.readObject(); // Read the object itself oi = null; // Don't close it, that would close the stream itself. break; case ARRAY_IDS: // The header for an array of ids. value.classID = is.readInt(); // The component type of the array value.anInt = is.readInt(); // The size of the array. // At this point, it is the responsibility of the caller to use readArray to read in the array. break; case VOID: break; case FLAG: if (allowFlag) { value.anInt = is.readInt(); break; } // Flags not allowed, so drop into default. default: throw new UnexpectedCommandException(UNKNOWN_READ_TYPE, false, new Byte(value.type)); } } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * Special interface used to read back arrays. It will be called when */ public static interface ValueSender { /** * This is called for each entry from the array. It is assumed that the ValueSender has * the array that is being built. * @param value * * @since 1.1.0 */ public void sendValue(ValueObject value); /** * This is called when an ARRAY_IDS is found within the reading of the array (i.e. nested arrays) * It is asking for a new ValueSender to use while this nested array. The arrayValue contains * the valueobject for the array header (i.e. the class id of the array and the number of elements). * It is the responsibility of the ValueSender to store this array in the array that is being built. * @param arrayValue * @return * * @since 1.1.0 */ public ValueSender nestedArray(ValueObject arrayValue); /** * Called to initialize the sender with the given array header. This is not always called, each usage * knows whether it can be called or not. For example the implementation of nestedArray may not need to call initialize. * @param arrayHeader * * @since 1.1.0 */ public void initialize(ValueObject arrayHeader); } /* * NOTE: It is important that on the IDE side that this is called within a transaction. * If not, there could be some corruption if proxy cleanup occurs in the middle. */ public static void readArray(DataInputStream is, int arraySize, ValueSender valueSender, ValueObject value, boolean allowFlag) throws CommandException { // Anything exception other than a CommandException, we will try to flush the input so that // it can continue with the next command and not close the connection. RuntimeException exception = null; for (int i=0; i<arraySize; i++) { readValue(is, value, allowFlag); if (exception == null) try { if (value.type != ARRAY_IDS) valueSender.sendValue(value); else { // We have a nested array. ValueSender nestedSender = null; try { nestedSender = valueSender.nestedArray(value); } catch (RuntimeException e) { // We still need to read in the array to flush. Create // a dummy sender that accepts everything sent to it. exception = e; nestedSender = new ValueSender() { public void sendValue(ValueObject value) { } public ValueSender nestedArray(ValueObject arrayValue) { return this; } public void initialize(ValueObject arrayHeader) { } }; } readArray(is, value.anInt, nestedSender, value, allowFlag); if (exception != null) throw exception; // An exception ocurred in new sender request. } } catch (RuntimeException e) { // We want to flush the queue, so save the exception for later. exception = e; } } if (exception != null) throw exception; } /** * Special interface to handle writing the ARRAY_IDS type. * An instance of this object will be in the valueObject sent to writeValue when the type of the value * is ARRAY_IDS. Then write value will know to call this interface to write out the values. * * @since 1.1.0 */ public static interface ValueRetrieve { /** * Returns the next value object to send. It will be called the number of times that the size of * the array was set to be send. * @return The value object to send. * @throws EOFException * * @since 1.1.0 */ public ValueObject nextValue() throws EOFException; } public static void writeValue(DataOutputStream os, ValueObject value, boolean asValueCommand) throws CommandException { writeValue(os, value, asValueCommand, asValueCommand ? true : false); } public static void writeValue(DataOutputStream os, ValueObject value, boolean asValueCommand, boolean flush) throws CommandException { try { if (asValueCommand) os.writeByte(VALUE); switch (value.type) { case BYTE: case L_BYTE: os.writeByte(value.type); os.writeByte(value.aByte); break; case CHAR: case L_CHAR: os.writeByte(value.type); os.writeChar(value.aChar); break; case DOUBLE: case L_DOUBLE: os.writeByte(value.type); os.writeDouble(value.aDouble); break; case FLOAT: case L_FLOAT: os.writeByte(value.type); os.writeFloat(value.aFloat); break; case INT: case L_INT: os.writeByte(value.type); os.writeInt(value.anInt); break; case OBJECT_ID: os.writeByte(value.type); os.writeInt(value.objectID); break; case NEW_OBJECT_ID: os.writeByte(value.type); os.writeInt(value.classID); os.writeInt(value.objectID); break; case THROW: os.writeByte(value.type); os.writeInt(value.classID); os.writeInt(value.objectID); break; case SHORT: case L_SHORT: os.writeByte(value.type); os.writeShort(value.aShort); break; case LONG: case L_LONG: os.writeByte(value.type); os.writeLong(value.aLong); break; case BOOL: case L_BOOL: os.writeByte(value.type); os.writeBoolean(value.aBool); break; case STRING: os.writeByte(value.type); sendStringData(os, (String) value.anObject); break; case OBJECT: os.writeByte(value.type); os.writeInt(value.classID); // Write the class ID. ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(value.anObject); oos.flush(); oos = null; // Don't close it, that would close the stream itself. break; case ARRAY_IDS: // We are writing out an array with ID's in it. The fields of the vale object will be: // classID: The class id of the component type of the array. // anObject: Contains the ValueRetriever to get the next value. os.writeByte(ARRAY_IDS); os.writeInt(value.classID); os.writeInt(value.anInt); // The size of the array. // Now comes the kludgy part, writing the values. ValueRetrieve retriever = (ValueRetrieve) value.anObject; int len = value.anInt; while (len-- > 0) writeValue(os, retriever.nextValue(), false); break; case VOID: os.writeByte(value.type); break; case FLAG: os.writeByte(FLAG); os.writeInt(value.anInt); break; default: os.writeByte(VOID); throw new UnexpectedCommandException(UNKNOWN_WRITE_TYPE, true, value); } if (flush) os.flush(); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * For reading a large number of bytes. This is a value type, not a command. The command * needs to be handled separately. It returns the number of bytes read. -1 if there * is no more data to send and the stream should closed. If read something but not all, * then just what it could read will be returned. The next read will return -1 for EOF. * * It will read from the format: * int - number of bytes to read (retrieved from the stream). * bytes - the actual bytes. * * Note: A command exception will be thrown if the number of bytes to read * is larger than the size of the byte array. */ public static int readBytes(DataInputStream is, byte[] bytes) throws CommandException { try { int bytesToRead = -1; try { bytesToRead = is.readInt(); } catch (EOFException e) { } if (bytesToRead == -1) return -1; if (bytesToRead > bytes.length) throw new UnexpectedCommandException(TOO_MANY_BYTES, false, new Integer(bytesToRead)); int start = 0; int toRead = bytesToRead; while (toRead > 0) { int bytesRead = is.read(bytes, start, toRead); if (bytesRead == -1) return bytesToRead != toRead ? bytesToRead-toRead : -1; // Actual number read, or if none read, then EOF start+=bytesRead; toRead-=bytesRead; } return bytesToRead; } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * For writing a large number of bytes. This is a value type, not a command. The command * needs to be handled separately. * * It will write it in the format: * int - number of bytes * bytes - the actual bytes. * */ public static void writeBytes(DataOutputStream os, byte[] bytes, int bytesToWrite) throws CommandException { try { os.writeInt(bytesToWrite); os.write(bytes, 0, bytesToWrite); } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /************************ * Send command helpers ************************/ public static void sendQuitCommand(DataOutputStream os) throws IOException { os.writeByte(QUIT_CONNECTION); os.flush(); } public static void sendTerminateCommand(DataOutputStream os) throws IOException { os.writeByte(TERMINATE_SERVER); os.flush(); } public static void releaseObjectCommand(DataOutputStream os, int id) throws IOException { os.writeByte(Commands.RELEASE_OBJECT); os.writeInt(id); os.flush(); } /** * Send a callback request. The value is to be send separately. */ public static void sendCallbackCommand(DataOutputStream os, int callbackID, int msgID) throws CommandException { try { os.writeByte(CALLBACK); os.writeInt(callbackID); os.writeInt(msgID); } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendCallbackDoneCommand(DataOutputStream os, ValueObject value, int errorCode) throws CommandException { try { os.writeByte(CALLBACK_DONE); if (errorCode == NO_ERROR) { writeValue(os, value, true); os.flush(); } else sendErrorCommand(os, errorCode, value); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * Send a start callback stream request. The data will be written separately. * There will not be a callback done command. It will return as soon as the command * is sent. */ public static void sendCallbackStreamCommand(DataOutputStream os, int callbackID, int msgID) throws CommandException { try { os.writeByte(CALLBACK_STREAM); os.writeInt(callbackID); os.writeInt(msgID); } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } protected static final byte STRING_NOT_CHUNKED = 0; protected static final byte STRING_CHUNKED = 1; protected static final byte MORE_CHUNKS = 2; protected static final byte LAST_CHUNK = 3; protected static final int CHUNK_SIZE = 65000/3; /** * Send string data. This is for general string data. It makes sure that if the string is too big (there is a UTF-8 limit) * that it will send it in chunks. Use the corresponding <code>readStringData</code> to read such data in. * @param os * @param string * @throws IOException * * @since 1.0.0 */ public static void sendStringData(DataOutputStream os, String string) throws IOException { // UTF-8 can take up to three bytes for each char. To be on safe side we will // not send a string larger than 65K/3 as one chunk. if (string.length() <= CHUNK_SIZE) { // Less than the limit, send byte to indicate not chunked. os.writeByte(STRING_NOT_CHUNKED); os.writeUTF(string); } else { // Over limit, need to chunk it. // send byte to indicate chunked, then send true length so that other side knows how big to create. os.writeByte(STRING_CHUNKED); os.writeInt(string.length()); // Now send first chunk for(int i=0; i<string.length(); i+=CHUNK_SIZE) { int endIndex = i+CHUNK_SIZE; if (i == 0) { // The first chunk is just written as is. We know endIndex will be ok because we wouldn't get here unless LARGER than chunksize. os.writeUTF(string.substring(i, endIndex)); } else { // Following chunks have either byte MORE_CHUNKS or LAST_CHUNK if (endIndex >= string.length()) { // This is last chunk. os.writeByte(LAST_CHUNK); os.writeUTF(string.substring(i)); } else { // This is an intermediate chunk. os.writeByte(MORE_CHUNKS); os.writeUTF(string.substring(i, endIndex)); } } } } } /** * Read a string that was sent using the sendStringData command. * @param in * @return * @throws IOException * * @since 1.0.0 */ public static String readStringData(DataInputStream is) throws IOException { byte chunked = is.readByte(); if (chunked == STRING_NOT_CHUNKED) return is.readUTF(); // Not chunk, just read it. else { // It is chunked. int totalLength = is.readInt(); // Get the total length. StringBuffer sbr = new StringBuffer(totalLength); while(true) { sbr.append(is.readUTF()); if (chunked != LAST_CHUNK) chunked = is.readByte(); else break; } return sbr.toString(); } } /** * Read back command, expecting either a VALUE or an ERROR. You can request that * it be of a specific type (if any type can be accepted then enter -1 for the type). */ public static final byte NO_TYPE_CHECK = -1; public static void readBackValue(DataInputStream is, ValueObject value, byte expectedType) throws CommandException { try { byte v = is.readByte(); switch (v) { case VALUE: readValue(is, value); if (expectedType != NO_TYPE_CHECK && !(expectedType == value.type || (expectedType == Commands.OBJECT_ID && value.type == NEW_OBJECT_ID))) throw new UnexpectedCommandException(TYPE_INVALID_FOR_COMMAND, true, value); break; case ERROR: int code = is.readInt(); readValue(is, value); throw new CommandErrorException(code, value); default: throw new UnexpectedCommandException(UNKNOWN_COMMAND, false, new Byte(v)); } } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * GetClass command returns a GetClassReturn object. */ public static class GetClassReturn { public int classID; public boolean isInterface; public boolean isAbstract; public String superClassname; } public static GetClassReturn sendGetClassCommand(DataOutputStream os, DataInputStream is, String className) throws CommandException { try { os.writeByte(GET_CLASS); os.writeUTF(className); os.flush(); GetClassReturn ret = new GetClassReturn(); byte v = is.readByte(); switch (v) { case GET_CLASS_RETURN: ret.classID = is.readInt(); // Get the new class id. ret.isInterface = is.readBoolean(); // Get the isInterface flag ret.isAbstract = is.readBoolean(); // Get the isAbstract flag. ret.superClassname = is.readUTF(); // Get the super class name. return ret; case ERROR: int code = is.readInt(); ValueObject value = new ValueObject(); readValue(is, value); throw new CommandErrorException(code, value); default: throw new UnexpectedCommandException(UNKNOWN_COMMAND, false, new Byte(v)); } } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } /** * GetClassFromID command returns a GetClassIDReturn object. */ public static class GetClassIDReturn { public String className; public boolean isInterface; public boolean isAbstract; public String superClassname; } public static GetClassIDReturn sendGetClassFromIDCommand(DataOutputStream os, DataInputStream is, int classID) throws CommandException { try { os.writeByte(GET_CLASS_FROM_ID); os.writeInt(classID); os.flush(); GetClassIDReturn ret = new GetClassIDReturn(); byte v = is.readByte(); switch (v) { case GET_CLASS_ID_RETURN: ret.className = is.readUTF(); // Get the new class name. ret.isInterface = is.readBoolean(); // Get the isInterface flag ret.isAbstract = is.readBoolean(); // Get the isAbstract flag. ret.superClassname = is.readUTF(); // Get the super class name. return ret; case ERROR: int code = is.readInt(); ValueObject value = new ValueObject(); readValue(is, value); throw new CommandErrorException(code, value); default: throw new UnexpectedCommandException(UNKNOWN_COMMAND, false, new Byte(v)); } } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendGetObjectData(DataOutputStream os, DataInputStream is, int objectID, ValueObject valueReturn) throws CommandException { try { os.writeByte(GET_OBJECT_DATA); os.writeInt(objectID); os.flush(); readBackValue(is, valueReturn, NEW_OBJECT_ID); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendErrorCommand(DataOutputStream os, int code, ValueObject errorValue) throws CommandException { try { os.writeByte(ERROR); os.writeInt(code); writeValue(os, errorValue, false); os.flush(); } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendNewInstance(DataOutputStream os, DataInputStream is, int classId, String initializationString, ValueObject newValueReturn) throws CommandException { try { os.writeByte(NEW_INIT_STRING); os.writeInt(classId); os.writeUTF(initializationString); os.flush(); readBackValue(is, newValueReturn, NO_TYPE_CHECK); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendInvokeMethodCommand(DataOutputStream os, DataInputStream is, int methodID, ValueObject invokeOn, ValueObject parms, ValueObject valueReturn) throws CommandException { try { os.writeByte(INVOKE); os.writeInt(methodID); writeValue(os, invokeOn, false); writeValue(os, parms, false); os.flush(); readBackValue(is, valueReturn, NO_TYPE_CHECK); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendInvokeMethodCommand(DataOutputStream os, DataInputStream is, ValueObject classType, String methodName, ValueObject parmTypes, ValueObject invokeOn, ValueObject parms, ValueObject valueReturn) throws CommandException { try { os.writeByte(INVOKE_WITH_METHOD_PASSED); writeValue(os, classType, false); os.writeUTF(methodName); writeValue(os, parmTypes, false); writeValue(os, invokeOn, false); writeValue(os, parms, false); os.flush(); readBackValue(is, valueReturn, NO_TYPE_CHECK); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } public static void sendGetArrayContentsCommand(DataOutputStream os, DataInputStream is, int arrayID, ValueObject valueReturn) throws CommandException { try { os.writeByte(GET_ARRAY_CONTENTS); os.writeInt(arrayID); os.flush(); readBackValue(is, valueReturn, NO_TYPE_CHECK); } catch (CommandException e) { // rethrow this exception since we want these to go on out. throw e; } catch (Exception e) { // Wrapper this one. throw new UnexpectedExceptionCommandException(false, e); } } private Commands() { // Never intended to be instantiated. } }