blob: d29a9f2e38cdd68fbb8b845933320df91a528d2f [file] [log] [blame]
/*******************************************************************************
* 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 v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* 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.
}
}