public RemoteInvocationHandler(Connection connection, final int objectID) { super(); this.connection = connection; this.objectID = objectID; responseListener = new Listener() { @Override public void received(Connection connection, Object object) { if (!(object instanceof InvokeMethodResult)) { return; } InvokeMethodResult invokeMethodResult = (InvokeMethodResult) object; if (invokeMethodResult.objectID != objectID) { return; } responseTable.put(invokeMethodResult.responseID, invokeMethodResult); lock.lock(); try { responseCondition.signalAll(); } finally { lock.unlock(); } } @Override public void disconnected(Connection connection) { close(); } }; connection.addListener(responseListener); }
private Object waitForResponse(byte responseID) { if (connection.getEndPoint().getUpdateThread() == Thread.currentThread()) { throw new IllegalStateException( "Cannot wait for an RMI response on the connection's update thread."); } long endTime = System.currentTimeMillis() + timeoutMillis; while (true) { long remaining = endTime - System.currentTimeMillis(); if (responseTable.containsKey(responseID)) { InvokeMethodResult invokeMethodResult = responseTable.get(responseID); responseTable.remove(responseID); lastResponseID = null; return invokeMethodResult.result; } else { if (remaining <= 0) { throw new TimeoutException("Response timed out."); } lock.lock(); try { responseCondition.await(remaining, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } finally { lock.unlock(); } } } }
/** * Causes this ObjectSpace to stop listening to the connections for method invocation messages. */ public void close() { Connection[] connections = this.connections; for (Connection connection : connections) { connection.removeListener(invokeListener); } synchronized (instancesLock) { ArrayList<Connection> temp = new ArrayList(Arrays.asList(instances)); temp.remove(this); instances = temp.toArray(new ObjectSpace[temp.size()]); } if (TRACE) { trace("kryonet", "Closed ObjectSpace."); } }
/** * Removes the specified connection, it will no longer be able to access objects registered in * this ObjectSpace. */ public void removeConnection(Connection connection) { if (connection == null) { throw new IllegalArgumentException("connection cannot be null."); } connection.removeListener(invokeListener); synchronized (connectionsLock) { ArrayList<Connection> temp = new ArrayList(Arrays.asList(connections)); temp.remove(connection); connections = temp.toArray(new Connection[temp.size()]); } if (TRACE) { trace("kryonet", "Removed connection from ObjectSpace: " + connection); } }
/** * Allows the remote end of the specified connection to access objects registered in this * ObjectSpace. */ public void addConnection(Connection connection) { if (connection == null) { throw new IllegalArgumentException("connection cannot be null."); } synchronized (connectionsLock) { Connection[] newConnections = new Connection[connections.length + 1]; newConnections[0] = connection; System.arraycopy(connections, 0, newConnections, 1, connections.length); connections = newConnections; } connection.addListener(invokeListener); if (TRACE) { trace("kryonet", "Added connection to ObjectSpace: " + connection); } }
void close() { connection.removeListener(responseListener); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception { if (method.getDeclaringClass() == RemoteObject.class) { String name = method.getName(); if (name.equals("close")) { close(); return null; } else if (name.equals("setResponseTimeout")) { timeoutMillis = (Integer) args[0]; return null; } else if (name.equals("setNonBlocking")) { nonBlocking = (Boolean) args[0]; return null; } else if (name.equals("setTransmitReturnValue")) { transmitReturnValue = (Boolean) args[0]; return null; } else if (name.equals("setTransmitExceptions")) { transmitExceptions = (Boolean) args[0]; return null; } else if (name.equals("waitForLastResponse")) { if (lastResponseID == null) { throw new IllegalStateException("There is no last response to wait for."); } return waitForResponse(lastResponseID); } else if (name.equals("getLastResponseID")) { if (lastResponseID == null) { throw new IllegalStateException("There is no last response ID."); } return lastResponseID; } else if (name.equals("waitForResponse")) { if (!transmitReturnValue && !transmitExceptions && nonBlocking) { throw new IllegalStateException( "This RemoteObject is currently set to ignore all responses."); } return waitForResponse((Byte) args[0]); } else if (name.equals("getConnection")) { return connection; } else { // Should never happen, for debugging purposes only throw new RuntimeException( "Invocation handler could not find RemoteObject method. Check ObjectSpace.java"); } } else if (method.getDeclaringClass() == Object.class) { if (method.getName().equals("toString")) { return "<proxy>"; } try { return method.invoke(proxy, args); } catch (Exception ex) { throw new RuntimeException(ex); } } InvokeMethod invokeMethod = new InvokeMethod(); invokeMethod.objectID = objectID; invokeMethod.method = method; invokeMethod.args = args; // The only time a invocation doesn't need a response is if it's async // and no return values or exceptions are wanted back. boolean needsResponse = transmitReturnValue || transmitExceptions || !nonBlocking; if (needsResponse) { byte responseID; synchronized (this) { // Increment the response counter and put it into the first six bits of the // responseID byte responseID = nextResponseNum++; if (nextResponseNum == 64) { nextResponseNum = 1; // Keep number under 2^6, avoid 0 (see else statement // below) } } // Pack return value and exception info into the top two bits if (transmitReturnValue) { responseID |= kReturnValMask; } if (transmitExceptions) { responseID |= kReturnExMask; } invokeMethod.responseID = responseID; } else { invokeMethod.responseID = 0; // A response info of 0 means to not respond } int length = connection.sendTCP(invokeMethod); if (DEBUG) { String argString = ""; if (args != null) { argString = Arrays.deepToString(args); argString = argString.substring(1, argString.length() - 1); } debug( "kryonet", connection + " sent: " + method.getDeclaringClass().getSimpleName() + "#" + method.getName() + "(" + argString + ") (" + length + ")"); } if (invokeMethod.responseID != 0) { lastResponseID = invokeMethod.responseID; } if (nonBlocking) { Class returnType = method.getReturnType(); if (returnType.isPrimitive()) { if (returnType == int.class) { return 0; } if (returnType == boolean.class) { return Boolean.FALSE; } if (returnType == float.class) { return 0f; } if (returnType == char.class) { return (char) 0; } if (returnType == long.class) { return 0l; } if (returnType == short.class) { return (short) 0; } if (returnType == byte.class) { return (byte) 0; } if (returnType == double.class) { return 0d; } } return null; } try { Object result = waitForResponse(invokeMethod.responseID); if ((result != null) && (result instanceof Exception)) { throw (Exception) result; } else { return result; } } catch (TimeoutException ex) { throw new TimeoutException( "Response timed out: " + method.getDeclaringClass().getName() + "." + method.getName()); } }
/** * Invokes the method on the object and, if necessary, sends the result back to the connection * that made the invocation request. This method is invoked on the update thread of the {@link * EndPoint} for this ObjectSpace and unless an {@link #setExecutor(Executor) executor} has been * set. * * @param connection The remote side of this connection requested the invocation. */ protected void invoke(Connection connection, Object target, InvokeMethod invokeMethod) { if (DEBUG) { String argString = ""; if (invokeMethod.args != null) { argString = Arrays.deepToString(invokeMethod.args); argString = argString.substring(1, argString.length() - 1); } debug( "kryonet", connection + " received: " + target.getClass().getSimpleName() + "#" + invokeMethod.method.getName() + "(" + argString + ")"); } byte responseID = invokeMethod.responseID; boolean transmitReturnVal = (responseID & kReturnValMask) == kReturnValMask; boolean transmitExceptions = (responseID & kReturnExMask) == kReturnExMask; Object result = null; Method method = invokeMethod.method; try { result = method.invoke(target, invokeMethod.args); // Catch exceptions caused by the Method#invoke } catch (InvocationTargetException ex) { if (transmitExceptions) { result = ex.getCause(); } else { throw new RuntimeException( "Error invoking method: " + method.getDeclaringClass().getName() + "." + method.getName(), ex); } } catch (Exception ex) { throw new RuntimeException( "Error invoking method: " + method.getDeclaringClass().getName() + "." + method.getName(), ex); } if (responseID == 0) { return; } InvokeMethodResult invokeMethodResult = new InvokeMethodResult(); invokeMethodResult.objectID = invokeMethod.objectID; invokeMethodResult.responseID = responseID; // Do not return non-primitives if transmitReturnVal is false if (!transmitReturnVal && !invokeMethod.method.getReturnType().isPrimitive()) { invokeMethodResult.result = null; } else { invokeMethodResult.result = result; } int length = connection.sendTCP(invokeMethodResult); if (DEBUG) { debug("kryonet", connection + " sent: " + result + " (" + length + ")"); } }