f7886d83a3db06e565275495f38f9edf0bba7686
[org.ibex.xt-crawshaw.git] / src / java / org / ibex / xt / shell / JSRemote.java
1 package org.ibex.xt.shell;
2
3 import java.io.*;
4 import java.net.*;
5 import java.util.*;
6
7 import org.ibex.js.*;
8 import org.ibex.util.*;
9 import org.ibex.util.Collections;
10
11 public class JSRemote extends JS {
12     public static final int VERSION = 1;
13
14     private final URL server;
15     private final String path;
16
17     private transient String cookie = null;
18     private transient Keys keys = null;
19
20     public JSRemote(URL s) { server = s; path = null; }
21     public JSRemote(URL s, String p) { server = s; path = p; }
22
23     /** Receives a Request object from a client on <tt>in</tt>
24      *  and writes a response on <tt>out</tt> */
25     public static void receive(JS root, ObjectInputStream in, ObjectOutputStream out)
26                                throws IOException {
27         out.writeInt(VERSION); if (in.readInt() != VERSION) return;
28
29         Request r;
30         try {
31             Object o = in.readObject();
32             if (o == null) throw new IOException("unexpected null request");
33             if (!(o instanceof Request)) throw new IOException(
34                 "unexpected request object: "+o.getClass().getName());
35             r = (Request)o;
36         } catch (ClassNotFoundException e) {
37             throw new IOException("unexpected class not found: " + e.getMessage());
38         }
39
40         r.execute(root, out);
41     }
42
43     /** Sends a request to the server. */
44     protected ObjectInputStream send(Request request) throws IOException {
45         URLConnection c = server.openConnection();
46         ((HttpURLConnection)c).setRequestMethod("POST");
47         c.setDoOutput(true);
48         if (cookie != null) c.setRequestProperty("Cookie", cookie);
49
50         c.connect();
51
52         ObjectOutputStream out = new ObjectOutputStream(c.getOutputStream());
53         out.writeInt(VERSION);
54         out.writeObject(request);
55         out.close();
56
57         String cook = c.getHeaderField("Set-Cookie");
58         if (cook != null && cook.length() > 0) cookie = cook.substring(0, cook.indexOf(';'));
59
60         ObjectInputStream in = new ObjectInputStream(c.getInputStream());
61         int ver = in.readInt();
62         if (ver != VERSION) throw new IOException("server version "+ver+", expected "+VERSION);
63         return in;
64     }
65
66     public Collection keys() { return keys == null ? keys = new Keys() : keys.update(); }
67
68     public Object get(Object k) throws JSExn {
69         try {
70             ObjectInputStream in = send(new Request(path, k) {
71                 protected void execute() throws JSExn, IOException {
72                     Object o = scope.get(key);
73                     if (o == null)
74                         out.writeObject(null);
75                     else if (o instanceof Number || o instanceof Boolean || o instanceof String)
76                         out.writeObject(o);
77                     else if (o instanceof JS)
78                         out.writeObject(new JSRemote(server, (path == null ? "" : path + '.') + key));
79                     else throw new JSExn("unexpected class type " + o + " ["+o.getClass()+"]");
80                 }
81             });
82             Object o = in.readObject(); in.close();
83
84             if (o == null) return null;
85             else if (o instanceof Exception) throw (Exception)o;
86             else return o;
87         } catch (JSExn e) { throw e;
88         } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
89     }
90
91     // FIXME: unroll JSRemote if it is a value
92     public void put(Object k, final Object value) throws JSExn {
93         try { send(new Request(path, k) {
94             protected void execute() throws JSExn, IOException {
95                 scope.put(key, value);
96             }
97         }); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
98     }
99
100     public Object remove(Object k) throws JSExn {
101         try { return send(new Request(path, k) {
102             protected void execute() throws JSExn, IOException {
103                 out.writeObject(scope.remove(key));
104             }
105         }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
106     }
107
108     public boolean containsKey(Object k) throws JSExn {
109         try { return send(new Request(path, k) {
110             protected void execute() throws JSExn, IOException {
111                 out.writeBoolean(scope.containsKey(key));
112             }
113         }).readBoolean(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
114     }
115
116     public Object call(final Object a0, final Object a1, final Object a2,
117                        final Object[] rest, final int nargs) throws JSExn {
118         try { return send(new Request(path) {
119             protected void execute() throws JSExn, IOException {
120                 out.writeObject(scope.call(a0, a1, a2, rest, nargs));
121             }
122         }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
123     }
124
125     public Object callMethod(final Object m, final Object a0, final Object a1, final Object a2,
126                              final Object[] rest, final int nargs) throws JSExn {
127         try { return send(new Request(path) {
128             protected void execute() throws JSExn, IOException {
129                 out.writeObject(scope.callMethod(m, a0, a1, a2, rest, nargs));
130             }
131         }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
132     }
133
134     // FIXME: map trap functions to server
135
136     public static abstract class Request implements Serializable {
137         protected ObjectOutputStream out;
138         protected JS scope;
139         protected String path;
140         protected Object key;
141
142         public Request(String path) { this.path = path; key = null; }
143         public Request(String path, Object key) {
144             if (key != null && key instanceof String) {
145                 String k = (String)key;
146                 if (k.length() > 0) {
147                     if (k.charAt(0) == '.') k = k.substring(1);
148                     if (k.charAt(k.length() - 1) == '.') k = k.substring(0, k.length() - 1);
149                 }
150                 int pos = k.lastIndexOf('.');
151                 if (pos >= 0) {
152                     path += k.substring(0, pos);
153                     k = k.substring(pos + 1);
154                 }
155                 this.key = k;
156             } else this.key = key;
157
158             this.path = path;
159         }
160         public void execute(JS r, ObjectOutputStream o) throws IOException {
161             out = o;
162             scope = r;
163             try { 
164                 if (path != null) {
165                     StringTokenizer st = new StringTokenizer(path, ".");
166                     while (st.hasMoreTokens()) {
167                         String s = st.nextToken().trim();
168                         if (s.length() > 0 && !s.equals(".")) {
169                             Object ob = scope.get(s);
170                             if (ob == null || !(ob instanceof JS))
171                                 throw new JSExn("path not found");
172                             scope = (JS)ob;
173                         }
174                     }
175                 }
176                 execute();
177             } catch(JSExn e) { out.writeObject(e); }
178             out = null;
179             scope = null;
180         }
181         protected abstract void execute() throws JSExn, IOException;
182     }
183
184     private class Keys extends AbstractCollection implements Serializable {
185         private transient final List items =
186             Collections.synchronizedList(new ArrayList());
187         private int modCount = 0, size = -1;
188
189         public Iterator iterator() { update(); return new KeyIt(); }
190         public int size() { if (size == -1) update(); return size; }
191
192         private synchronized Keys update() {
193             modCount++; items.clear();
194             try {
195                 final ObjectInputStream in = send(new Request(path) {
196                     protected void execute() throws JSExn, IOException {
197                         Collection c = scope.keys(); out.writeInt(c.size());
198                         Iterator i;
199                         if (c.size() > 1000) { // FIXME: better way to unload server?
200                             i = c.iterator();
201                         }  else {
202                             List l = new ArrayList(c);
203                             Collections.sort(l);
204                             i = l.listIterator();
205                         }
206                         while (i.hasNext()) out.writeObject(i.next());
207                     }
208                 });
209                 size = in.readInt();
210                 new Thread() { public void run() {
211                     try {
212                         for (int i=0; i < size; i++) items.add(in.readObject());
213                         in.close();
214                     } catch (Exception e) {
215                         size = -1; items.clear();
216                         throw new RuntimeException("JSRemote", e);
217                     }
218                 }}.start();
219             } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
220
221             return this;
222         }
223
224         private class KeyIt implements Iterator {
225             private final int mod;
226             private int pos = 0;
227
228             KeyIt() { mod = modCount; }
229             public boolean hasNext() {
230                 if (modCount != mod) throw new ConcurrentModificationException();
231                 return pos < size;
232             }
233             public Object next() {
234                 if (modCount != mod) throw new ConcurrentModificationException();
235                 while (pos >= items.size()) {
236                     if (pos >= size) throw new NoSuchElementException();
237                     try { Thread.sleep(50); } catch (InterruptedException e) {}
238                 }
239                 return items.get(pos++);
240             }
241             public void remove() { throw new UnsupportedOperationException(); } // FIXME
242         }
243     }
244 }