diff --git a/engine/src/desktop/com/jme3/app/AppletHarness.java b/engine/src/desktop/com/jme3/app/AppletHarness.java index c595647da..7368ff373 100644 --- a/engine/src/desktop/com/jme3/app/AppletHarness.java +++ b/engine/src/desktop/com/jme3/app/AppletHarness.java @@ -43,10 +43,13 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; /** - * @author Kirill + * @author Kirill Vainer */ public class AppletHarness extends Applet { @@ -70,12 +73,26 @@ public class AppletHarness extends Applet { // load app cfg if (appCfg != null){ + InputStream in = null; try { - InputStream in = appCfg.openStream(); + in = appCfg.openStream(); settings.load(in); in.close(); } catch (IOException ex){ + // Called before application has been created .... + // Display error message through AWT + JOptionPane.showMessageDialog(this, "An error has occured while " + + "loading applet configuration" + + ex.getMessage(), + "jME3 Applet", + JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); + } finally { + if (in != null) + try { + in.close(); + } catch (IOException ex) { + } } } diff --git a/engine/src/networking/com/jme3/network/rmi/MethodDef.java b/engine/src/networking/com/jme3/network/rmi/MethodDef.java index 4bcd8bc77..9164e06ba 100644 --- a/engine/src/networking/com/jme3/network/rmi/MethodDef.java +++ b/engine/src/networking/com/jme3/network/rmi/MethodDef.java @@ -39,20 +39,20 @@ package com.jme3.network.rmi; * * @author Kirill Vainer */ -class MethodDef { +public class MethodDef { /** * Method name */ - String name; + public String name; /** * Return type */ - Class retType; + public Class retType; /** * Parameter types */ - Class[] paramTypes; + public Class[] paramTypes; } diff --git a/engine/src/networking/com/jme3/network/rmi/ObjectDef.java b/engine/src/networking/com/jme3/network/rmi/ObjectDef.java index 2e3e18cd2..6a338aefa 100644 --- a/engine/src/networking/com/jme3/network/rmi/ObjectDef.java +++ b/engine/src/networking/com/jme3/network/rmi/ObjectDef.java @@ -37,33 +37,33 @@ import com.jme3.network.serializing.Serializable; import java.lang.reflect.Method; @Serializable -class ObjectDef { +public class ObjectDef { /** * The object name, can be null if undefined. */ - String objectName; + public String objectName; /** * Object ID */ - int objectId; + public int objectId; /** * Methods of the implementation on the local client. Set to null * on remote clients. */ - Method[] methods; + public Method[] methods; /** * Method definitions of the implementation. Set to null on * the local client. */ - MethodDef[] methodDefs; + public MethodDef[] methodDefs; @Override public String toString(){ - return "ObjectDef[name=" + objectName + ", ID=" + objectId+"]"; + return "ObjectDef[name=" + objectName + ", objectId=" + objectId+"]"; } } diff --git a/engine/src/networking/com/jme3/network/rmi/ObjectStore.java b/engine/src/networking/com/jme3/network/rmi/ObjectStore.java index 82b351c7d..431a56609 100644 --- a/engine/src/networking/com/jme3/network/rmi/ObjectStore.java +++ b/engine/src/networking/com/jme3/network/rmi/ObjectStore.java @@ -42,25 +42,36 @@ import com.jme3.network.serializing.Serializer; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; +import java.util.logging.Level; +import java.util.logging.Logger; public class ObjectStore implements MessageListener, ConnectionListener { + private static final Logger logger = Logger.getLogger(ObjectStore.class.getName()); + private static final class Invocation { + Object retVal; boolean available = false; + + @Override + public String toString(){ + return "Invocation[" + retVal + "]"; + } } private Client client; private Server server; // Local object ID counter - private short objectIdCounter = 0; + private volatile short objectIdCounter = 0; // Local invocation ID counter - private short invocationIdCounter = 0; + private volatile short invocationIdCounter = 0; // Invocations waiting .. private IntMap pendingInvocations = new IntMap(); @@ -120,10 +131,13 @@ public class ObjectStore implements MessageListener, ConnectionListener { RemoteObjectDefMessage defMsg = new RemoteObjectDefMessage(); defMsg.objects = new ObjectDef[]{ makeObjectDef(localObj) }; - if (client != null) + if (client != null) { client.send(defMsg); - else + logger.log(Level.INFO, "Client: Sending {0}", defMsg); + } else { server.broadcast(defMsg); + logger.log(Level.INFO, "Server: Sending {0}", defMsg); + } } public T getExposedObject(String name, Class type, boolean waitFor) throws InterruptedException{ @@ -140,7 +154,6 @@ public class ObjectStore implements MessageListener, ConnectionListener { } } - Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ type }, ro); ro.loadMethods(type); return (T) proxy; @@ -163,14 +176,17 @@ public class ObjectStore implements MessageListener, ConnectionListener { if (needReturn){ call.invocationId = invocationIdCounter++; invoke = new Invocation(); + // Note: could cause threading issues if used from multiple threads pendingInvocations.put(call.invocationId, invoke); } try{ if (server != null){ remoteObj.client.send(call); + logger.log(Level.INFO, "Server: Sending {0}", call); }else{ client.send(call); + logger.log(Level.INFO, "Client: Sending {0}", call); } } catch (IOException ex){ ex.printStackTrace(); @@ -186,6 +202,7 @@ public class ObjectStore implements MessageListener, ConnectionListener { } } } + // Note: could cause threading issues if used from multiple threads pendingInvocations.remove(call.invocationId); return invoke.retVal; }else{ @@ -194,6 +211,9 @@ public class ObjectStore implements MessageListener, ConnectionListener { } public void messageReceived(Message message) { + // Might want to do more strict validation of the data + // in the message to prevent crashes + if (message instanceof RemoteObjectDefMessage){ RemoteObjectDefMessage defMsg = (RemoteObjectDefMessage) message; @@ -212,27 +232,38 @@ public class ObjectStore implements MessageListener, ConnectionListener { }else if (message instanceof RemoteMethodCallMessage){ RemoteMethodCallMessage call = (RemoteMethodCallMessage) message; LocalObject localObj = localObjects.get(call.objectId); + if (localObj == null) + return; + + if (call.methodId < 0 || call.methodId >= localObj.methods.length) + return; Object obj = localObj.theObject; Method method = localObj.methods[call.methodId]; Object[] args = call.args; - Object ret; + Object ret = null; try { ret = method.invoke(obj, args); - } catch (Exception ex){ - throw new RuntimeException(ex); + } catch (IllegalAccessException ex){ + logger.log(Level.WARNING, "RMI: Error accessing method", ex); + } catch (IllegalArgumentException ex){ + logger.log(Level.WARNING, "RMI: Invalid arguments", ex); + } catch (InvocationTargetException ex){ + logger.log(Level.WARNING, "RMI: Invocation exception", ex); } if (method.getReturnType() != void.class){ // send return value back RemoteMethodReturnMessage retMsg = new RemoteMethodReturnMessage(); - retMsg.invocationID = invocationIdCounter++; + retMsg.invocationID = call.invocationId; retMsg.retVal = ret; try { if (server != null){ call.getClient().send(retMsg); + logger.log(Level.INFO, "Server: Sending {0}", retMsg); } else{ client.send(retMsg); + logger.log(Level.INFO, "Client: Sending {0}", retMsg); } } catch (IOException ex){ ex.printStackTrace(); @@ -242,7 +273,8 @@ public class ObjectStore implements MessageListener, ConnectionListener { RemoteMethodReturnMessage retMsg = (RemoteMethodReturnMessage) message; Invocation invoke = pendingInvocations.get(retMsg.invocationID); if (invoke == null){ - throw new RuntimeException("Cannot find invocation ID: " + retMsg.invocationID); + logger.log(Level.WARNING, "Cannot find invocation ID: {0}", retMsg.invocationID); + return; } synchronized (invoke){ @@ -268,8 +300,10 @@ public class ObjectStore implements MessageListener, ConnectionListener { try { if (this.client != null){ this.client.send(defMsg); + logger.log(Level.INFO, "Client: Sending {0}", defMsg); } else{ client.send(defMsg); + logger.log(Level.INFO, "Server: Sending {0}", defMsg); } } catch (IOException ex){ ex.printStackTrace(); diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java index 9feaafc30..6f487834f 100644 --- a/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java +++ b/engine/src/networking/com/jme3/network/rmi/RemoteMethodCallMessage.java @@ -41,7 +41,7 @@ import com.jme3.network.serializing.Serializable; * @author Kirill Vainer */ @Serializable -class RemoteMethodCallMessage extends Message { +public class RemoteMethodCallMessage extends Message { public RemoteMethodCallMessage(){ super(true); @@ -50,30 +50,30 @@ class RemoteMethodCallMessage extends Message { /** * The object ID on which the call is being made. */ - int objectId; + public int objectId; /** * The method ID used for look-up in the LocalObject.methods array. */ - short methodId; + public short methodId; /** * Invocation ID is used to identify a particular call if the calling * client needs the return value of the called RMI method. * This is set to zero if the method does not return a value. */ - short invocationId; + public short invocationId; /** * Arguments of the remote method invocation. */ - Object[] args; + public Object[] args; @Override public String toString(){ StringBuilder sb = new StringBuilder(); - sb.append("MethodCall[objectID=").append(objectId).append(", methodID=") + sb.append("RemoteMethodCallMessage[objectID=").append(objectId).append(", methodID=") .append(methodId); if (args != null && args.length > 0){ sb.append(", args={"); diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java index 1890bb527..3aabfe1fa 100644 --- a/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java +++ b/engine/src/networking/com/jme3/network/rmi/RemoteMethodReturnMessage.java @@ -43,7 +43,7 @@ import com.jme3.network.serializing.Serializable; * @author Kirill Vainer. */ @Serializable -class RemoteMethodReturnMessage extends Message { +public class RemoteMethodReturnMessage extends Message { public RemoteMethodReturnMessage(){ super(true); @@ -52,16 +52,16 @@ class RemoteMethodReturnMessage extends Message { /** * Invocation ID that was set in the {@link RemoteMethodCallMessage}. */ - short invocationID; + public short invocationID; /** * The return value, could be null. */ - Object retVal; + public Object retVal; @Override public String toString(){ - return "MethodReturn[ID="+invocationID+", Value="+retVal.toString()+"]"; + return "RemoteMethodReturnMessage[ID="+invocationID+", Value="+retVal.toString()+"]"; } } diff --git a/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java b/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java index 55127f6ea..d39c7d4ba 100644 --- a/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java +++ b/engine/src/networking/com/jme3/network/rmi/RemoteObjectDefMessage.java @@ -42,12 +42,23 @@ import com.jme3.network.serializing.Serializable; * @author Kirill Vainer */ @Serializable -class RemoteObjectDefMessage extends Message { +public class RemoteObjectDefMessage extends Message { - ObjectDef[] objects; + public ObjectDef[] objects; public RemoteObjectDefMessage(){ super(true); } + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append("RemoteObjectDefMessage[\n"); + for (ObjectDef def : objects){ + sb.append("\t").append(def).append("\n"); + } + sb.append("]"); + return sb.toString(); + } + } diff --git a/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java b/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java index 81a612925..76709dcd1 100644 --- a/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java +++ b/engine/src/networking/com/jme3/network/rmi/RmiSerializer.java @@ -37,6 +37,8 @@ import com.jme3.network.serializing.SerializerRegistration; import java.io.IOException; import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; /** * {@link RmiSerializer} is responsible for serializing RMI messages @@ -46,12 +48,17 @@ import java.nio.ByteBuffer; */ public class RmiSerializer extends Serializer { + private static final Logger logger = Logger.getLogger(RmiSerializer.class.getName()); + + // not good for multithread applications private char[] chrBuf = new char[256]; private void writeString(ByteBuffer buffer, String string) throws IOException{ int length = string.length(); if (length > 255){ - throw new IOException("Cannot serialize: "+ string + "\nToo long!"); + logger.log(Level.WARNING, "The string length exceeds the limit! {0} > 255", length); + buffer.put( (byte) 0 ); + return; } buffer.put( (byte) length ); @@ -73,9 +80,10 @@ public class RmiSerializer extends Serializer { buffer.putShort((short)0); } else { SerializerRegistration reg = Serializer.getSerializerRegistration(clazz); - if (reg == null) - throw new IOException("Unknown class: "+clazz); - + if (reg == null){ + logger.log(Level.WARNING, "Unknown class: {0}", clazz); + throw new IOException(); // prevents message from being serialized + } buffer.putShort(reg.getId()); } } @@ -85,10 +93,12 @@ public class RmiSerializer extends Serializer { if (reg == null){ // either "void" or unknown val short id = buffer.getShort(buffer.position()-2); - if (id == 0) + if (id == 0){ return void.class; - else - throw new IOException("Undefined class ID: " + id); + } else{ + logger.log(Level.WARNING, "Undefined class ID: {0}", id); + throw new IOException(); // prevents message from being serialized + } } return reg.getType(); } @@ -173,6 +183,10 @@ public class RmiSerializer extends Serializer { buffer.put((byte)0); }else{ buffer.put((byte)call.args.length); + + // Right now it writes 0 for every null argument + // and 1 for every non-null argument followed by the serialized + // argument. For the future, using a bit set should be considered. for (Object obj : call.args){ if (obj != null){ buffer.put((byte)0x01);