update to handle returning old version from put() function in JS and functional rm...
[org.ibex.xt-crawshaw.git] / src / java / org / ibex / xt / shell / JSRemote.java
index f7886d8..4b3e475 100644 (file)
@@ -8,6 +8,9 @@ import org.ibex.js.*;
 import org.ibex.util.*;
 import org.ibex.util.Collections;
 
+import org.ibex.xt.Prevalence;
+import org.prevayler.*;
+
 public class JSRemote extends JS {
     public static final int VERSION = 1;
 
@@ -22,7 +25,7 @@ public class JSRemote extends JS {
 
     /** Receives a Request object from a client on <tt>in</tt>
      *  and writes a response on <tt>out</tt> */
-    public static void receive(JS root, ObjectInputStream in, ObjectOutputStream out)
+    public static void receive(Prevayler p, JS root, ObjectInputStream in, ObjectOutputStream out)
                                throws IOException {
         out.writeInt(VERSION); if (in.readInt() != VERSION) return;
 
@@ -37,7 +40,7 @@ public class JSRemote extends JS {
             throw new IOException("unexpected class not found: " + e.getMessage());
         }
 
-        r.execute(root, out);
+        r.execute(p, root, out);
     }
 
     /** Sends a request to the server. */
@@ -63,6 +66,28 @@ public class JSRemote extends JS {
         return in;
     }
 
+    /** FEATURE: It's questionable as to whether this belongs here. JSRemote shouldn't
+     *  really know anything about prevayler, but that rquires another layer between
+     *  this class and the http layer. Maybe not a bad idea. */
+    public void transaction(final JS t) {
+        Object resp;
+        try {
+            resp = send(new Request(null) {
+                protected void execute() throws JSExn, IOException {
+                    try {
+                        prevayler.execute(new Prevalence.JSTransaction(t));
+                        out.writeObject(null);
+                    } catch (Exception e) { e.printStackTrace(); out.writeObject(e); }
+                }
+            }).readObject();
+        } catch (Exception e) {
+            throw new RuntimeException("transaction failed", e);
+        }
+
+        if (resp != null && resp instanceof Exception)
+            throw new RuntimeException("transaction failed", (Exception)resp);
+    }
+
     public Collection keys() { return keys == null ? keys = new Keys() : keys.update(); }
 
     public Object get(Object k) throws JSExn {
@@ -82,6 +107,7 @@ public class JSRemote extends JS {
             Object o = in.readObject(); in.close();
 
             if (o == null) return null;
+            else if (o instanceof JS) return new JSImmutable((JS)o);
             else if (o instanceof Exception) throw (Exception)o;
             else return o;
         } catch (JSExn e) { throw e;
@@ -89,12 +115,12 @@ public class JSRemote extends JS {
     }
 
     // FIXME: unroll JSRemote if it is a value
-    public void put(Object k, final Object value) throws JSExn {
-        try { send(new Request(path, k) {
+    public Object put(Object k, final Object value) throws JSExn {
+        try { return send(new Request(path, k) {
             protected void execute() throws JSExn, IOException {
-                scope.put(key, value);
+                out.writeObject(scope.put(key, value));
             }
-        }); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
+        }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
     }
 
     public Object remove(Object k) throws JSExn {
@@ -105,7 +131,7 @@ public class JSRemote extends JS {
         }).readObject(); } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
     }
 
-    public boolean containsKey(Object k) throws JSExn {
+    public boolean containsKey(Object k) {
         try { return send(new Request(path, k) {
             protected void execute() throws JSExn, IOException {
                 out.writeBoolean(scope.containsKey(key));
@@ -134,6 +160,7 @@ public class JSRemote extends JS {
     // FIXME: map trap functions to server
 
     public static abstract class Request implements Serializable {
+        protected Prevayler prevayler;
         protected ObjectOutputStream out;
         protected JS scope;
         protected String path;
@@ -157,7 +184,8 @@ public class JSRemote extends JS {
 
             this.path = path;
         }
-        public void execute(JS r, ObjectOutputStream o) throws IOException {
+        public void execute(Prevayler p, JS r, ObjectOutputStream o) throws IOException {
+            prevayler = p;
             out = o;
             scope = r;
             try { 
@@ -175,21 +203,66 @@ public class JSRemote extends JS {
                 }
                 execute();
             } catch(JSExn e) { out.writeObject(e); }
+            prevayler = null;
             out = null;
             scope = null;
         }
         protected abstract void execute() throws JSExn, IOException;
     }
 
+    public static class JSImmutable extends JS {
+        private final JS wrapped;
+        public JSImmutable(JS toWrap) { wrapped = toWrap; }
+
+        public Collection keys() throws JSExn {
+            return Collections.unmodifiableCollection(wrapped.keys()); }
+        public Object get(Object key) throws JSExn { return wrapped.get(key); }
+        public boolean containsKey(Object key) { return wrapped.containsKey(key); }
+        public Object call(Object a0, Object a1, Object a2, Object[] r, int n)
+            throws JSExn { return wrapped.call(a0, a1, a2, r, n); }
+
+        public Object callMethod(Object m, Object a0, Object a1, Object a2, Object[] r, int n)
+            throws JSExn { throw new JSExn("immutable JS"); }
+        public Object put(Object k, Object v) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public Object remove(Object k) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public void putAndTriggerTraps(Object k, Object v) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        public Object getAndTriggerTraps(Object k) throws JSExn {
+            throw new JSExn("immutable JS"); }
+        protected boolean isTrappable(Object n, boolean i) { return false; }
+    }
+
     private class Keys extends AbstractCollection implements Serializable {
+        private transient boolean updating = false;
         private transient final List items =
             Collections.synchronizedList(new ArrayList());
-        private int modCount = 0, size = -1;
+        private transient int modCount = 0;
+
+        private int size = -1;
 
-        public Iterator iterator() { update(); return new KeyIt(); }
         public int size() { if (size == -1) update(); return size; }
+        public Iterator iterator() { updateList(); return new KeyIt(); }
 
         private synchronized Keys update() {
+            if (updating) return this;
+            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());
+                    }
+                });
+                size = in.readInt();
+            } catch (Exception e) { throw new RuntimeException("JSRemote", e); }
+            return this;
+        }
+
+        private synchronized Keys updateList() {
+            if (updating) return this;
+            updating = true;
             modCount++; items.clear();
             try {
                 final ObjectInputStream in = send(new Request(path) {
@@ -214,6 +287,8 @@ public class JSRemote extends JS {
                     } catch (Exception e) {
                         size = -1; items.clear();
                         throw new RuntimeException("JSRemote", e);
+                    } finally {
+                        synchronized (Keys.this) { Keys.this.updating = false; }
                     }
                 }}.start();
             } catch (Exception e) { throw new RuntimeException("JSRemote", e); }