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
+ }
+ }
+}