1 package org.ibex.xt.shell;
8 import org.ibex.util.*;
9 import org.ibex.util.Collections;
11 public class JSRemote extends JS {
12 public static final int VERSION = 1;
14 private final URL server;
15 private final String path;
17 private transient String cookie = null;
18 private transient Keys keys = null;
20 public JSRemote(URL s) { server = s; path = null; }
21 public JSRemote(URL s, String p) { server = s; path = p; }
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)
27 out.writeInt(VERSION); if (in.readInt() != VERSION) return;
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());
36 } catch (ClassNotFoundException e) {
37 throw new IOException("unexpected class not found: " + e.getMessage());
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");
48 if (cookie != null) c.setRequestProperty("Cookie", cookie);
52 ObjectOutputStream out = new ObjectOutputStream(c.getOutputStream());
53 out.writeInt(VERSION);
54 out.writeObject(request);
57 String cook = c.getHeaderField("Set-Cookie");
58 if (cook != null && cook.length() > 0) cookie = cook.substring(0, cook.indexOf(';'));
60 ObjectInputStream in = new ObjectInputStream(c.getInputStream());
61 int ver = in.readInt();
62 if (ver != VERSION) throw new IOException("server version "+ver+", expected "+VERSION);
66 public Collection keys() { return keys == null ? keys = new Keys() : keys.update(); }
68 public Object get(Object k) throws JSExn {
70 ObjectInputStream in = send(new Request(path, k) {
71 protected void execute() throws JSExn, IOException {
72 Object o = scope.get(key);
74 out.writeObject(null);
75 else if (o instanceof Number || o instanceof Boolean || o instanceof String)
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()+"]");
82 Object o = in.readObject(); in.close();
84 if (o == null) return null;
85 else if (o instanceof Exception) throw (Exception)o;
87 } catch (JSExn e) { throw e;
88 } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
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);
97 }); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
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));
105 }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
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));
113 }).readBoolean(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
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));
122 }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
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));
131 }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
134 // FIXME: map trap functions to server
136 public static abstract class Request implements Serializable {
137 protected ObjectOutputStream out;
139 protected String path;
140 protected Object key;
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);
150 int pos = k.lastIndexOf('.');
152 path += k.substring(0, pos);
153 k = k.substring(pos + 1);
156 } else this.key = key;
160 public void execute(JS r, ObjectOutputStream o) throws IOException {
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");
177 } catch(JSExn e) { out.writeObject(e); }
181 protected abstract void execute() throws JSExn, IOException;
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;
189 public Iterator iterator() { update(); return new KeyIt(); }
190 public int size() { if (size == -1) update(); return size; }
192 private synchronized Keys update() {
193 modCount++; items.clear();
195 final ObjectInputStream in = send(new Request(path) {
196 protected void execute() throws JSExn, IOException {
197 Collection c = scope.keys(); out.writeInt(c.size());
199 if (c.size() > 1000) { // FIXME: better way to unload server?
202 List l = new ArrayList(c);
204 i = l.listIterator();
206 while (i.hasNext()) out.writeObject(i.next());
210 new Thread() { public void run() {
212 for (int i=0; i < size; i++) items.add(in.readObject());
214 } catch (Exception e) {
215 size = -1; items.clear();
216 throw new RuntimeException("JSRemote", e);
219 } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
224 private class KeyIt implements Iterator {
225 private final int mod;
228 KeyIt() { mod = modCount; }
229 public boolean hasNext() {
230 if (modCount != mod) throw new ConcurrentModificationException();
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) {}
239 return items.get(pos++);
241 public void remove() { throw new UnsupportedOperationException(); } // FIXME