Adding early version of IoT RMI system; for now: 1) Connects Java to Java; 2) Transla...
[iot2.git] / iotjava / iotrmi / Java / IoTRMIObject.java
1 package iotrmi.Java;
2
3 import java.io.IOException;
4 import java.nio.ByteBuffer;
5 import java.util.Arrays;
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.lang.reflect.*;
12
13
14 /** Class IoTRMIObject is a class that stores info of an object.
15  *  <p>
16  *  It stores object ID, methods, method ID, method's signature 
17  *  and parameters.
18  *  This class also receive calls from different objects as they
19  *  ask to execute certain methods remotely. This will have the 
20  *  execution result (return value) sent back to 
21  *
22  * @author      Rahmadi Trimananda <rtrimana @ uci.edu>
23  * @version     1.0
24  * @since       2016-10-03
25  */
26 public class IoTRMIObject {
27
28         /**
29          * Class Properties
30          */
31         private Map<String,Method> mapSign2Method;      // Map from signature to method
32         private Map<Integer,String> mapHash2Sign;       // Map from hashcode(method ID) to signature
33         private IoTRMIUtil rmiUtil;
34         private IoTSocketServer rmiServer;
35         private Object obj;
36         private Class<?> cls;
37         private Method[] methods;
38
39
40         /**
41          * Constructors
42          */
43         public IoTRMIObject(String _clsName, int _port) throws 
44                         ClassNotFoundException, InstantiationException, 
45                                 IllegalAccessException, IOException {
46
47                 rmiUtil = new IoTRMIUtil();
48                 cls = Class.forName(_clsName);
49                 obj = cls.newInstance();
50                 methods = cls.getDeclaredMethods();
51                 mapSign2Method = new HashMap<String,Method>();
52                 mapHash2Sign = new HashMap<Integer,String>();
53                 getMethodSignatures();  // Initialize the signature map
54                 getMethodIds();                 // Initialize the method ID map
55                 rmiServer = new IoTSocketServer(_port);
56                 rmiServer.connect();
57         }
58
59
60         /**
61          * getName() gets class name
62          */
63         public String getName() {
64
65                 return cls.getName();
66         }
67
68
69         /**
70          * getSignatures() gets method signatures
71          */
72         public Set<String> getSignatures() {
73
74                 return mapSign2Method.keySet();
75         }
76
77         
78         /**
79          * getMethodParamTypes() gets method parameter types
80          */
81         public Class<?>[] getMethodParamTypes(String signature) {
82
83                 Method method = mapSign2Method.get(signature);
84                 return method.getParameterTypes();
85         }
86
87
88         /**
89          * getMethodRetType() gets method return type
90          */
91         public Class<?> getMethodRetType(String signature) {
92
93                 Method method = mapSign2Method.get(signature);
94                 return method.getReturnType();
95         }
96         
97
98         /**
99          * invokeMethod() invokes a method based on signature and params
100          */
101         public Object invokeMethod(String signature, Object[] params) {
102
103                 Method method = mapSign2Method.get(signature);
104                 Object retVal = null;
105                 try {
106                         retVal = method.invoke(obj, params);
107                 } catch (IllegalAccessException |
108                                  InvocationTargetException ex) {
109                         ex.printStackTrace();
110                         throw new Error("IoTRMICall: Error invoking method: " + signature);
111                 }
112
113                 return retVal;
114         }
115
116
117         /**
118          * recvAndInvoke() waits for method transmission and invoke the method
119          */
120         private void recvAndInvoke() throws IOException {
121
122                 // Receive method info and invoke
123                 byte[] method = null;
124                 method = rmiServer.receiveBytes(method);
125                 Object retObj = invokeMethod(method);
126                 // Send back return value
127                 byte[] retObjBytes = IoTRMIUtil.getObjectBytes(retObj);
128                 rmiServer.sendBytes(retObjBytes);
129         }
130         
131
132         /**
133          * invokeMethod() invokes a method based on byte array received
134          * <p>
135          * Basically this is the format of a method in bytes:
136          * 1) 32-bit value of method ID (hash code)
137          * 2) m parameters with n-bit value each (m x n-bit)
138          * For the parameters that don't have definite length,
139          * we need to extract the length from a preceding 32-bit
140          * field in front of it.
141          *
142          * For primitive objects:
143          * | 32-bit method ID | m-bit actual data (fixed length)  |
144          * 
145          * For string, arrays, and non-primitive objects:
146          * | 32-bit method ID | 32-bit length | n-bit actual data | ...
147          * 
148          */
149         public Object invokeMethod(byte[] methodBytes) {
150
151                 // Byte scanning position
152                 int pos = 0;
153                 // Get method ID
154                 byte[] methodIdBytes = new byte[IoTRMIUtil.METHOD_ID_LEN];
155                 System.arraycopy(methodBytes, pos, methodIdBytes, 0, IoTRMIUtil.METHOD_ID_LEN);
156                 pos = pos + IoTRMIUtil.METHOD_ID_LEN;
157                 // Get Method object to handle method
158                 int methodId = IoTRMIUtil.byteArrayToInt(methodIdBytes);
159                 String signature = mapHash2Sign.get(methodId);
160                 Method method = mapSign2Method.get(signature);
161                 // Get class name and then param length
162                 Class<?>[] arrCls = method.getParameterTypes();
163                 Object[] paramObj = new Object[arrCls.length];
164                 for (int i=0; i < arrCls.length; i++) {
165
166                         String paramType = arrCls[i].getSimpleName();
167                         int paramSize = rmiUtil.getTypeSize(paramType);
168                         // Get the 32-bit field in the byte array to get the actual
169                         //              length (this is a param with indefinite length)
170                         if (paramSize == -1) {
171                                 byte[] bytPrmLen = new byte[IoTRMIUtil.PARAM_LEN];
172                                 System.arraycopy(methodBytes, pos, bytPrmLen, 0, IoTRMIUtil.PARAM_LEN);
173                                 pos = pos + IoTRMIUtil.PARAM_LEN;
174                                 paramSize = IoTRMIUtil.byteArrayToInt(bytPrmLen);
175                         }
176                         byte[] paramBytes = new byte[paramSize];
177                         System.arraycopy(methodBytes, pos, paramBytes, 0, paramSize);
178                         pos = pos + paramSize;
179                         paramObj[i] = IoTRMIUtil.getParamObject(paramType, paramBytes);
180                 }
181                 Object retObj = null;
182                 try {
183                         retObj = method.invoke(obj, paramObj);
184                 } catch (IllegalAccessException |
185                                  InvocationTargetException ex) {
186                         ex.printStackTrace();
187                         throw new Error("IoTRMICall: Error invoking method: " + signature);
188                 }
189
190                 return retObj;
191         }
192
193
194         /**================
195          * Helper methods
196          **================
197          */
198         /**
199          * getMethodSignatures() gets methods signatures and store them in the Map
200          */
201         private void getMethodSignatures() {
202
203                 for (Method m : methods) {
204                         String sign = rmiUtil.getSignature(m);
205                         //System.out.println("Signature: " + sign);
206                         mapSign2Method.put(sign, m);
207                 }
208         }
209         
210         
211         /**
212          * getMethodIds() gets methods identifiers (hash code) and store them in the Map
213          */
214         private void getMethodIds() {
215
216                 Set<String> setSignatures = getSignatures();
217                 for (String sign : setSignatures) {
218                         byte[] hashCode = IoTRMIUtil.getHashCodeBytes(sign);
219                         int methodId = IoTRMIUtil.byteArrayToInt(hashCode);
220                         mapHash2Sign.put(methodId, sign);
221                 }
222         }
223
224
225         public static void main(String[] args) throws Exception {
226
227                 int port = 5010;
228                 IoTRMIObject rmiObj = new IoTRMIObject("TestClass", port);
229                 rmiObj.recvAndInvoke();
230         }
231 }