package games.strategy.engine.message;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import games.strategy.util.Tuple;
/**
* All the info neccassary to describe a method call in one handy
* serializable package.
*/
public class RemoteMethodCall implements Externalizable {
private static final long serialVersionUID = 4630825927685836207L;
private static final Logger s_logger = Logger.getLogger(RemoteMethodCall.class.getName());
private String m_remoteName;
private String m_methodName;
private Object[] m_args;
// to save space, we dont serialize method name/types
// instead we just serialize a number which can be transalted into
// the correct method.
private int m_methodNumber;
// stored as a String[] so we can be serialzed
private String[] m_argTypes;
public RemoteMethodCall() {}
public RemoteMethodCall(final String remoteName, final String methodName, final Object[] args,
final Class<?>[] argTypes, final Class<?> remoteInterface) {
if (argTypes == null) {
throw new IllegalArgumentException("ArgTypes are null");
}
if (args == null && argTypes.length != 0) {
throw new IllegalArgumentException("args but no types");
}
if (args != null && args.length != argTypes.length) {
throw new IllegalArgumentException("Arg and arg type lengths dont match");
}
m_remoteName = remoteName;
m_methodName = methodName;
m_args = args;
m_argTypes = classesToString(argTypes, args);
m_methodNumber = RemoteInterfaceHelper.getNumber(methodName, argTypes, remoteInterface);
if (s_logger.isLoggable(Level.FINE)) {
s_logger.fine("Remote Method Call:" + debugMethodText());
}
}
private String debugMethodText() {
if (m_argTypes == null) {
return "." + m_methodName + "(" + ")";
} else {
return "." + m_methodName + "(" + Arrays.asList(m_argTypes) + ")";
}
}
/**
* @return Returns the channelName.
*/
public String getRemoteName() {
return m_remoteName;
}
/**
* @return Returns the methodName.
*/
public String getMethodName() {
return m_methodName;
}
/**
* @return Returns the args.
*/
public Object[] getArgs() {
return m_args;
}
/**
* @return Returns the argTypes.
*/
public Class<?>[] getArgTypes() {
return stringsToClasses(m_argTypes, m_args);
}
private static Class<?>[] stringsToClasses(final String[] strings, final Object[] args) {
final Class<?>[] rVal = new Class<?>[strings.length];
for (int i = 0; i < strings.length; i++) {
try {
// null if we skipped writing because the arg is the expected
// class, this saves some space since generally the arg will
// be of the correct type
if (strings[i] == null) {
rVal[i] = args[i].getClass();
} else if (strings[i].equals("int")) {
rVal[i] = Integer.TYPE;
} else if (strings[i].equals("short")) {
rVal[i] = Short.TYPE;
} else if (strings[i].equals("byte")) {
rVal[i] = Byte.TYPE;
} else if (strings[i].equals("long")) {
rVal[i] = Long.TYPE;
} else if (strings[i].equals("float")) {
rVal[i] = Float.TYPE;
} else if (strings[i].equals("double")) {
rVal[i] = Double.TYPE;
} else if (strings[i].equals("boolean")) {
rVal[i] = Boolean.TYPE;
} else {
rVal[i] = Class.forName(strings[i]);
}
} catch (final ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
return rVal;
}
private static String[] classesToString(final Class<?>[] classes, final Object[] args) {
// as an optimization, if args[i].getClass == classes[i] then leave classes[i] as null
// this will reduce the amount of info we write over the network in the common
// case where the object is the same type as its arg
final String[] rVal = new String[classes.length];
for (int i = 0; i < classes.length; i++) {
if (args != null && args[i] != null && classes[i] == args[i].getClass()) {
rVal[i] = null;
} else {
rVal[i] = classes[i].getName();
}
}
return rVal;
}
@Override
public String toString() {
return "Remote method call:" + m_methodName + " on:" + m_remoteName;
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeUTF(m_remoteName);
out.writeByte(m_methodNumber);
if (m_args == null) {
out.writeByte(Byte.MAX_VALUE);
} else {
out.writeByte(m_args.length);
for (final Object m_arg : m_args) {
out.writeObject(m_arg);
}
}
}
@Override
public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
m_remoteName = in.readUTF();
m_methodNumber = in.readByte();
final byte count = in.readByte();
if (count != Byte.MAX_VALUE) {
m_args = new Object[count];
for (int i = 0; i < count; i++) {
m_args[i] = in.readObject();
}
}
}
/**
* After we have been de-serialized, we do not transmit enough
* informatin to determine the method without being told
* what class we operate on.
*/
public void resolve(final Class<?> remoteType) {
if (m_methodName != null) {
return;
}
final Tuple<String, Class<?>[]> values = RemoteInterfaceHelper.getMethodInfo(m_methodNumber, remoteType);
m_methodName = values.getFirst();
m_argTypes = classesToString(values.getSecond(), m_args);
if (s_logger.isLoggable(Level.FINE)) {
s_logger.fine("Remote Method for class:" + remoteType.getSimpleName() + " Resolved To:" + debugMethodText());
}
}
}