Adding early version of IoT RMI system; for now: 1) Connects Java to Java; 2) Transla...
[iot2.git] / iotjava / iotrmi / Java / IoTRMIObject.java
diff --git a/iotjava/iotrmi/Java/IoTRMIObject.java b/iotjava/iotrmi/Java/IoTRMIObject.java
new file mode 100644 (file)
index 0000000..9f40b82
--- /dev/null
@@ -0,0 +1,231 @@
+package iotrmi.Java;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.lang.reflect.*;
+
+
+/** Class IoTRMIObject is a class that stores info of an object.
+ *  <p>
+ *  It stores object ID, methods, method ID, method's signature 
+ *  and parameters.
+ *  This class also receive calls from different objects as they
+ *  ask to execute certain methods remotely. This will have the 
+ *  execution result (return value) sent back to 
+ *
+ * @author      Rahmadi Trimananda <rtrimana @ uci.edu>
+ * @version     1.0
+ * @since       2016-10-03
+ */
+public class IoTRMIObject {
+
+       /**
+        * Class Properties
+        */
+       private Map<String,Method> mapSign2Method;      // Map from signature to method
+       private Map<Integer,String> mapHash2Sign;       // Map from hashcode(method ID) to signature
+       private IoTRMIUtil rmiUtil;
+       private IoTSocketServer rmiServer;
+       private Object obj;
+       private Class<?> cls;
+       private Method[] methods;
+
+
+       /**
+        * Constructors
+        */
+       public IoTRMIObject(String _clsName, int _port) throws 
+                       ClassNotFoundException, InstantiationException, 
+                               IllegalAccessException, IOException {
+
+               rmiUtil = new IoTRMIUtil();
+               cls = Class.forName(_clsName);
+               obj = cls.newInstance();
+               methods = cls.getDeclaredMethods();
+               mapSign2Method = new HashMap<String,Method>();
+               mapHash2Sign = new HashMap<Integer,String>();
+               getMethodSignatures();  // Initialize the signature map
+               getMethodIds();                 // Initialize the method ID map
+               rmiServer = new IoTSocketServer(_port);
+               rmiServer.connect();
+       }
+
+
+       /**
+        * getName() gets class name
+        */
+       public String getName() {
+
+               return cls.getName();
+       }
+
+
+       /**
+        * getSignatures() gets method signatures
+        */
+       public Set<String> getSignatures() {
+
+               return mapSign2Method.keySet();
+       }
+
+       
+       /**
+        * getMethodParamTypes() gets method parameter types
+        */
+       public Class<?>[] getMethodParamTypes(String signature) {
+
+               Method method = mapSign2Method.get(signature);
+               return method.getParameterTypes();
+       }
+
+
+       /**
+        * getMethodRetType() gets method return type
+        */
+       public Class<?> getMethodRetType(String signature) {
+
+               Method method = mapSign2Method.get(signature);
+               return method.getReturnType();
+       }
+       
+
+       /**
+        * invokeMethod() invokes a method based on signature and params
+        */
+       public Object invokeMethod(String signature, Object[] params) {
+
+               Method method = mapSign2Method.get(signature);
+               Object retVal = null;
+               try {
+                       retVal = method.invoke(obj, params);
+               } catch (IllegalAccessException |
+                                InvocationTargetException ex) {
+                       ex.printStackTrace();
+                       throw new Error("IoTRMICall: Error invoking method: " + signature);
+               }
+
+               return retVal;
+       }
+
+
+       /**
+        * recvAndInvoke() waits for method transmission and invoke the method
+        */
+       private void recvAndInvoke() throws IOException {
+
+               // Receive method info and invoke
+               byte[] method = null;
+               method = rmiServer.receiveBytes(method);
+               Object retObj = invokeMethod(method);
+               // Send back return value
+               byte[] retObjBytes = IoTRMIUtil.getObjectBytes(retObj);
+               rmiServer.sendBytes(retObjBytes);
+       }
+       
+
+       /**
+        * invokeMethod() invokes a method based on byte array received
+        * <p>
+        * Basically this is the format of a method in bytes:
+        * 1) 32-bit value of method ID (hash code)
+        * 2) m parameters with n-bit value each (m x n-bit)
+        * For the parameters that don't have definite length,
+        * we need to extract the length from a preceding 32-bit
+        * field in front of it.
+        *
+        * For primitive objects:
+        * | 32-bit method ID | m-bit actual data (fixed length)  |
+        * 
+        * For string, arrays, and non-primitive objects:
+        * | 32-bit method ID | 32-bit length | n-bit actual data | ...
+        * 
+        */
+       public Object invokeMethod(byte[] methodBytes) {
+
+               // Byte scanning position
+               int pos = 0;
+               // Get method ID
+               byte[] methodIdBytes = new byte[IoTRMIUtil.METHOD_ID_LEN];
+               System.arraycopy(methodBytes, pos, methodIdBytes, 0, IoTRMIUtil.METHOD_ID_LEN);
+               pos = pos + IoTRMIUtil.METHOD_ID_LEN;
+               // Get Method object to handle method
+               int methodId = IoTRMIUtil.byteArrayToInt(methodIdBytes);
+               String signature = mapHash2Sign.get(methodId);
+               Method method = mapSign2Method.get(signature);
+               // Get class name and then param length
+               Class<?>[] arrCls = method.getParameterTypes();
+               Object[] paramObj = new Object[arrCls.length];
+               for (int i=0; i < arrCls.length; i++) {
+
+                       String paramType = arrCls[i].getSimpleName();
+                       int paramSize = rmiUtil.getTypeSize(paramType);
+                       // Get the 32-bit field in the byte array to get the actual
+                       //              length (this is a param with indefinite length)
+                       if (paramSize == -1) {
+                               byte[] bytPrmLen = new byte[IoTRMIUtil.PARAM_LEN];
+                               System.arraycopy(methodBytes, pos, bytPrmLen, 0, IoTRMIUtil.PARAM_LEN);
+                               pos = pos + IoTRMIUtil.PARAM_LEN;
+                               paramSize = IoTRMIUtil.byteArrayToInt(bytPrmLen);
+                       }
+                       byte[] paramBytes = new byte[paramSize];
+                       System.arraycopy(methodBytes, pos, paramBytes, 0, paramSize);
+                       pos = pos + paramSize;
+                       paramObj[i] = IoTRMIUtil.getParamObject(paramType, paramBytes);
+               }
+               Object retObj = null;
+               try {
+                       retObj = method.invoke(obj, paramObj);
+               } catch (IllegalAccessException |
+                                InvocationTargetException ex) {
+                       ex.printStackTrace();
+                       throw new Error("IoTRMICall: Error invoking method: " + signature);
+               }
+
+               return retObj;
+       }
+
+
+       /**================
+        * Helper methods
+        **================
+        */
+       /**
+        * getMethodSignatures() gets methods signatures and store them in the Map
+        */
+       private void getMethodSignatures() {
+
+               for (Method m : methods) {
+                       String sign = rmiUtil.getSignature(m);
+                       //System.out.println("Signature: " + sign);
+                       mapSign2Method.put(sign, m);
+               }
+       }
+       
+       
+       /**
+        * getMethodIds() gets methods identifiers (hash code) and store them in the Map
+        */
+       private void getMethodIds() {
+
+               Set<String> setSignatures = getSignatures();
+               for (String sign : setSignatures) {
+                       byte[] hashCode = IoTRMIUtil.getHashCodeBytes(sign);
+                       int methodId = IoTRMIUtil.byteArrayToInt(hashCode);
+                       mapHash2Sign.put(methodId, sign);
+               }
+       }
+
+
+       public static void main(String[] args) throws Exception {
+
+               int port = 5010;
+               IoTRMIObject rmiObj = new IoTRMIObject("TestClass", port);
+               rmiObj.recvAndInvoke();
+       }
+}