X-Git-Url: http://git.megacz.com/?p=org.ibex.xt-crawshaw.git;a=blobdiff_plain;f=src%2Fjava%2Forg%2Fibex%2Fxt%2Fshell%2FJSRemote.java;fp=src%2Fjava%2Forg%2Fibex%2Fxt%2Fshell%2FJSRemote.java;h=f7886d83a3db06e565275495f38f9edf0bba7686;hp=0000000000000000000000000000000000000000;hb=e2e46233d9db6fe8728421016a41d5bf79db86e5;hpb=a86e3334b03e5acf25fe9223d9d7634573a6c396 diff --git a/src/java/org/ibex/xt/shell/JSRemote.java b/src/java/org/ibex/xt/shell/JSRemote.java new file mode 100644 index 0000000..f7886d8 --- /dev/null +++ b/src/java/org/ibex/xt/shell/JSRemote.java @@ -0,0 +1,244 @@ +package org.ibex.xt.shell; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.ibex.js.*; +import org.ibex.util.*; +import org.ibex.util.Collections; + +public class JSRemote extends JS { + public static final int VERSION = 1; + + private final URL server; + private final String path; + + private transient String cookie = null; + private transient Keys keys = null; + + public JSRemote(URL s) { server = s; path = null; } + public JSRemote(URL s, String p) { server = s; path = p; } + + /** Receives a Request object from a client on in + * and writes a response on out */ + public static void receive(JS root, ObjectInputStream in, ObjectOutputStream out) + throws IOException { + out.writeInt(VERSION); if (in.readInt() != VERSION) return; + + Request r; + try { + Object o = in.readObject(); + if (o == null) throw new IOException("unexpected null request"); + if (!(o instanceof Request)) throw new IOException( + "unexpected request object: "+o.getClass().getName()); + r = (Request)o; + } catch (ClassNotFoundException e) { + throw new IOException("unexpected class not found: " + e.getMessage()); + } + + r.execute(root, out); + } + + /** Sends a request to the server. */ + protected ObjectInputStream send(Request request) throws IOException { + URLConnection c = server.openConnection(); + ((HttpURLConnection)c).setRequestMethod("POST"); + c.setDoOutput(true); + if (cookie != null) c.setRequestProperty("Cookie", cookie); + + c.connect(); + + ObjectOutputStream out = new ObjectOutputStream(c.getOutputStream()); + out.writeInt(VERSION); + out.writeObject(request); + out.close(); + + String cook = c.getHeaderField("Set-Cookie"); + if (cook != null && cook.length() > 0) cookie = cook.substring(0, cook.indexOf(';')); + + ObjectInputStream in = new ObjectInputStream(c.getInputStream()); + int ver = in.readInt(); + if (ver != VERSION) throw new IOException("server version "+ver+", expected "+VERSION); + return in; + } + + public Collection keys() { return keys == null ? keys = new Keys() : keys.update(); } + + public Object get(Object k) throws JSExn { + try { + ObjectInputStream in = send(new Request(path, k) { + protected void execute() throws JSExn, IOException { + Object o = scope.get(key); + if (o == null) + out.writeObject(null); + else if (o instanceof Number || o instanceof Boolean || o instanceof String) + out.writeObject(o); + else if (o instanceof JS) + out.writeObject(new JSRemote(server, (path == null ? "" : path + '.') + key)); + else throw new JSExn("unexpected class type " + o + " ["+o.getClass()+"]"); + } + }); + Object o = in.readObject(); in.close(); + + if (o == null) return null; + else if (o instanceof Exception) throw (Exception)o; + else return o; + } catch (JSExn e) { throw e; + } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + // FIXME: unroll JSRemote if it is a value + public void put(Object k, final Object value) throws JSExn { + try { send(new Request(path, k) { + protected void execute() throws JSExn, IOException { + scope.put(key, value); + } + }); } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + public Object remove(Object k) throws JSExn { + try { return send(new Request(path, k) { + protected void execute() throws JSExn, IOException { + out.writeObject(scope.remove(key)); + } + }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + public boolean containsKey(Object k) throws JSExn { + try { return send(new Request(path, k) { + protected void execute() throws JSExn, IOException { + out.writeBoolean(scope.containsKey(key)); + } + }).readBoolean(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + public Object call(final Object a0, final Object a1, final Object a2, + final Object[] rest, final int nargs) throws JSExn { + try { return send(new Request(path) { + protected void execute() throws JSExn, IOException { + out.writeObject(scope.call(a0, a1, a2, rest, nargs)); + } + }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + public Object callMethod(final Object m, final Object a0, final Object a1, final Object a2, + final Object[] rest, final int nargs) throws JSExn { + try { return send(new Request(path) { + protected void execute() throws JSExn, IOException { + out.writeObject(scope.callMethod(m, a0, a1, a2, rest, nargs)); + } + }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + } + + // FIXME: map trap functions to server + + public static abstract class Request implements Serializable { + protected ObjectOutputStream out; + protected JS scope; + protected String path; + protected Object key; + + public Request(String path) { this.path = path; key = null; } + public Request(String path, Object key) { + if (key != null && key instanceof String) { + String k = (String)key; + if (k.length() > 0) { + if (k.charAt(0) == '.') k = k.substring(1); + if (k.charAt(k.length() - 1) == '.') k = k.substring(0, k.length() - 1); + } + int pos = k.lastIndexOf('.'); + if (pos >= 0) { + path += k.substring(0, pos); + k = k.substring(pos + 1); + } + this.key = k; + } else this.key = key; + + this.path = path; + } + public void execute(JS r, ObjectOutputStream o) throws IOException { + out = o; + scope = r; + try { + if (path != null) { + StringTokenizer st = new StringTokenizer(path, "."); + while (st.hasMoreTokens()) { + String s = st.nextToken().trim(); + if (s.length() > 0 && !s.equals(".")) { + Object ob = scope.get(s); + if (ob == null || !(ob instanceof JS)) + throw new JSExn("path not found"); + scope = (JS)ob; + } + } + } + execute(); + } catch(JSExn e) { out.writeObject(e); } + out = null; + scope = null; + } + protected abstract void execute() throws JSExn, IOException; + } + + private class Keys extends AbstractCollection implements Serializable { + private transient final List items = + Collections.synchronizedList(new ArrayList()); + private int modCount = 0, size = -1; + + public Iterator iterator() { update(); return new KeyIt(); } + public int size() { if (size == -1) update(); return size; } + + private synchronized Keys update() { + modCount++; items.clear(); + try { + final ObjectInputStream in = send(new Request(path) { + protected void execute() throws JSExn, IOException { + Collection c = scope.keys(); out.writeInt(c.size()); + Iterator i; + if (c.size() > 1000) { // FIXME: better way to unload server? + i = c.iterator(); + } else { + List l = new ArrayList(c); + Collections.sort(l); + i = l.listIterator(); + } + while (i.hasNext()) out.writeObject(i.next()); + } + }); + size = in.readInt(); + new Thread() { public void run() { + try { + for (int i=0; i < size; i++) items.add(in.readObject()); + in.close(); + } catch (Exception e) { + size = -1; items.clear(); + throw new RuntimeException("JSRemote", e); + } + }}.start(); + } catch (Exception e) { throw new RuntimeException("JSRemote", e); } + + return this; + } + + private class KeyIt implements Iterator { + private final int mod; + private int pos = 0; + + KeyIt() { mod = modCount; } + public boolean hasNext() { + if (modCount != mod) throw new ConcurrentModificationException(); + return pos < size; + } + public Object next() { + if (modCount != mod) throw new ConcurrentModificationException(); + while (pos >= items.size()) { + if (pos >= size) throw new NoSuchElementException(); + try { Thread.sleep(50); } catch (InterruptedException e) {} + } + return items.get(pos++); + } + public void remove() { throw new UnsupportedOperationException(); } // FIXME + } + } +}