new xt shell based on JS transparency layer
authorcrawshaw <crawshaw@ibex.org>
Sun, 5 Dec 2004 14:28:47 +0000 (14:28 +0000)
committercrawshaw <crawshaw@ibex.org>
Sun, 5 Dec 2004 14:28:47 +0000 (14:28 +0000)
darcs-hash:20041205142847-2eb37-384d49182b364866cf8046df220644309c8cc6ee.gz

src/java/org/ibex/xt/shell/Command.java
src/java/org/ibex/xt/shell/Env.java [deleted file]
src/java/org/ibex/xt/shell/JSRemote.java [new file with mode: 0644]
src/java/org/ibex/xt/shell/Request.java [deleted file]
src/java/org/ibex/xt/shell/Servlet.java
src/java/org/ibex/xt/shell/Shell.java

index 7edc5ac..48fb329 100644 (file)
@@ -1,19 +1,24 @@
 package org.ibex.xt.shell;
 
-import java.io.StringReader;
 import java.io.Writer;
 import java.io.IOException;
 
 import java.util.*;
+import java.util.regex.*;
 
 import org.ibex.js.*;
+import org.ibex.util.*;
 
 public abstract class Command {
+    protected Shell shell;
+
+    protected Command(Shell s) { shell = s; }
+
     /** Returns the command name. */
     public abstract String name();
 
-    /** Returns single-line of usage information, eg. <tt>pattern]</tt> */
-    public abstract String usage();
+    /** Returns a single-line of parameter information, eg. <tt>[pattern]</tt> */
+    public abstract String params();
 
     /** Returns single-line description of command. */
     public abstract String summary();
@@ -22,38 +27,72 @@ public abstract class Command {
     public abstract String help();
 
     /** Writes result of execution, even if result is an error. */
-    public abstract void execute(Writer w, String[] args, Env env) throws IOException;
+    public abstract void execute(Writer w, String[] args) throws IOException;
+
+
+    /** Returns single-line of usage information, eg. <tt>usage: ls [pattern]</tt> */
+    public void usage(Writer w) throws IOException {
+        w.write("usage: ");
+        w.write(name());
+        w.write(" ");
+        w.write(params());
+        w.write("\n");
+    }
+
+    public List fromShellPath(String s) throws Shell.NoSuchPathException {
+        return fromShellPath(s, null);
+    }
+
+    /** Converts a shell path "/foo/etc/../bar" to its component form,
+     *  { "foo", "bar" } and loads it into the returned list.
+     *  Handles relative positioning to shell.getPath(). */
+    public List fromShellPath(String s, List l) throws Shell.NoSuchPathException {
+        if (s == null) return null;
+        if (l == null) l = new ArrayList();
+
+        s = s.trim().replace("/+", "/");
+        if (s.charAt(0) != '/') l.addAll(Arrays.asList(shell.getPath()));
+
+        StringTokenizer st = new StringTokenizer(s, "/");
+        while (st.hasMoreTokens()) {
+            String part = st.nextToken();
+            if (part == null || part.equals("") || part.equals(".")) continue;
+            else if (part.equals("..")) { if (l.size() > 0) l.remove(l.size() - 1); }
+            else l.add(part);
+        }
+
+        return l;
+    }
 
     public static class Help extends Command {
+        public Help(Shell s) { super(s); }
         public String name() { return "help"; }
-        public String usage() { return "[command name]"; }
+        public String params() { return "[command name]"; }
         public String summary() { return "Lists available commands."; }
         public String help() { return ""; }
 
-        public void execute(Writer w, String[] c, Env env) throws IOException {
+        public void execute(Writer w, String[] c) throws IOException {
             if (c.length > 1) {
-                Command cmd = env.command(c[1]);
+                Command cmd = shell.getCommand(c[1]);
                 if (c == null) {
                     w.write("help: ");
                     w.write(c[1]);
                     w.write(": command not found\n");
                 } else {
-                    w.write("usage: ");
-                    w.write(cmd.name());
-                    w.write(" ");
-                    w.write(cmd.usage());
-                    w.write("\n\n");
+                    cmd.usage(w);
+                    w.write("\n");
                     w.write(cmd.help());
                     w.write("\n");
                 }
             } else {
                 int len = 3;
-                for (int i=0; i < env.commands.length; i++)
-                    len = Math.max(env.commands[i].name().length(), len);
+                Command[] cmds = shell.getCommands();
+                for (int i=0; i < cmds.length; i++)
+                    len = Math.max(cmds[i].name().length(), len);
 
                 w.write("Available commands:\n");
-                for (int i=0; i < env.commands.length; i++) {
-                    Command cmd = env.commands[i];
+                for (int i=0; i < cmds.length; i++) {
+                    Command cmd = cmds[i];
                     w.write("  ");
                     w.write(cmd.name());
                     for (int j=len - cmd.name().length(); j >= 0; j--) w.write(" ");
@@ -67,47 +106,81 @@ public abstract class Command {
     }
 
     public static class Ls extends Command {
+        public Ls(Shell s) { super(s); }
         public String name() { return "ls"; }
-        public String usage() { return "[path]"; }
+        public String params() { return "[path]"; }
         public String summary() { return "List object entries."; }
         public String help() { return
             "Lists the keys in an object. Modelled after the UNIX ls command.";
         }
 
-        public void execute(Writer w, String[] c, Env env) throws IOException {
-            if (c.length > 2) { w.write(usage()); return; }
-            String p = env.path(c.length == 1 ? "*" : c[1]);
-
-            Request.Response ret = env.send(new Request.Key(p));
-            if (!(ret instanceof Request.Key.Res)) {
-                w.write("error: ");
-                w.write(ret.error().getMessage());
-                w.write("\n");
-            } else {
-                List l = ((Request.Key.Res)ret).keys();
-                Iterator i = l.iterator(); while (i.hasNext()) {
-                    w.write(i.next().toString());
+        public void execute(Writer w, String[] c) throws IOException {
+            if (c.length > 2) { usage(w); return; }
+            String p = c.length == 1 ? "*" : c[1];
+            if (p.endsWith("/")) p += "*";
+
+            try {
+                List path = fromShellPath(p);
+                Object key = path.remove(path.size() - 1);
+                Object po = shell.getFromPath(path.toArray());
+                if (po == null || !(po instanceof JS))
+                    throw new Shell.NoSuchPathException();
+                JS cur = (JS)po;
+
+                if (key instanceof String &&
+                        ((String)key).indexOf('*') >= 0) {
+                    String last = (String)key;
+                    last = last.replaceAll("\\.", "\\.");
+                    last = last.replaceAll("\\*", ".*");
+                    Pattern pat = Pattern.compile(last);
+                    Iterator i = cur.keys().iterator(); while (i.hasNext()) {
+                        Object o = i.next();
+                        if (o == null || !(o instanceof String)) continue;
+                        String s = (String)o;
+                        if (!pat.matcher(s).matches()) continue;
+                        w.write(s);
+                        w.write("\n");
+                    }
+                } else if (cur.containsKey(key)) {
+                    w.write(key.toString());
                     w.write("\n");
                 }
+            } catch (Shell.NoSuchPathException e) {
+                w.write("error: no such path: ");
+                w.write(p);
+                w.write("\n");
+            } catch (JSExn e) {
+                w.write("error: no such path: ");
+                w.write(p);
+                w.write(" (");
+                w.write(e.getMessage());
+                w.write(")\n");
             }
         }
     }
 
     public static class Pwd extends Command {
+        public Pwd(Shell s) { super(s); }
         public String name() { return "pwd"; }
-        public String usage() { return ""; }
+        public String params() { return ""; }
         public String summary() { return "Path to current object."; }
         public String help() { return "Print the path to the current object."; }
-        public void execute(Writer w, String[] c, Env env) throws IOException {
-            if (c.length != 1) { w.write(usage()); return; }
-            w.write(env.path.equals("") ? "." : env.path);
+        public void execute(Writer w, String[] c) throws IOException {
+            if (c.length != 1) { usage(w); return; }
+            Object[] path = shell.getPath();
+            for (int i=0; i < path.length; i++) {
+                w.write("/");
+                w.write(path[i].toString());
+            }
+            if (path.length == 0) w.write("/");
             w.write("\n");
         }
     }
 
     public static class Cd extends Command {
+        public Cd(Shell s) { super(s); }
         public String name() { return "cd"; }
-        public String usage() { return "[path]"; }
+        public String params() { return "[path]"; }
         public String summary() { return "Change current object."; }
         public String help() { return
             "Chnages the current object that all other commands use "+
@@ -116,97 +189,28 @@ public abstract class Command {
             "or an absolute path (e.g. cd .prevalent.myob).\n\n" +
             "To go up one level, cd .. can be used.";
         }
-        public void execute(Writer w, String[] c, Env env) throws IOException {
-            if (c.length > 2) w.write(usage());
-            else if (c.length == 1 || c[1].equals("") || c[1].equals(".")) env.path = "";
-            else if (c[1].equals("..")) {
-                String n = env.path;
-                n = n.substring(0, n.lastIndexOf('.'));
-                env.path = n;
-            } else {
-                String n = env.path(c[1]);
-
-                Request.Response ret = env.send(new Request.IsKey(n));
-                if (!(ret instanceof Request.IsKey.Res)) {
-                    w.write("error: ");
-                    w.write(ret.error().getMessage());
-                    w.write("\n");
-                } else {
-                    if (((Request.IsKey.Res)ret).exists()) {
-                        w.write("error: ");
-                        w.write(c[1]);
-                        w.write(": no such path\n");
-                    } else {
-                        env.path = n;
-                    }
-                }
+        public void execute(Writer w, String[] c) throws IOException {
+            if (c.length > 2) { usage(w); return; }
+            String path = c.length == 1 ? "/" : c[1];
+
+            try { shell.setPath(fromShellPath(path).toArray()); }
+            catch (Shell.NoSuchPathException e) {
+                w.write("error: no such path: ");
+                w.write(path);
+                w.write("\n");
             }
         }
     }
 
     public static class Rm extends Command {
+        public Rm(Shell s) { super(s); }
         public String name() { return "rm"; }
-        public String usage() { return "[options] [path]"; }
+        public String params() { return "[options] [path]"; }
         public String summary() { return "Removes objects."; }
         public String help() { return "Removes objects."; } // FIXME
-        public void execute(Writer w, String[] c, Env env) throws IOException {
-            if (c.length == 1) { w.write(usage()); }
-
-            Request.Key[] r = new Request.Key[c.length - 1];
-            for (int i=0; i < r.length; i++) r[i] = new Request.RemoveKey(env.path(c[i + 1]));
+        public void execute(Writer w, String[] c) throws IOException {
+            if (c.length == 1) { usage(w); return; }
 
-            Request.Response ret = env.send(new Request.Composite(r, false));
-            if (!(ret instanceof Request.Composite.Res)) {
-                w.write("error: ");
-                w.write(ret.error().getMessage());
-                w.write("\n");
-            } else {
-                Request.Response[] res = ((Request.Composite.Res)ret).responses();
-                for (int i=0; i < res.length; i++) {
-                    if (res[i] instanceof Request.RemoveKey.Res) {
-                        boolean rm = ((Request.RemoveKey.Res)res[i]).removed();
-                        if (!rm) {
-                            w.write("error: cannot remove '");
-                            w.write(c[i + 1]);
-                            w.write("': no such key\n");
-                        }
-                    } else {
-                        w.write("error: cannot remove '");
-                        w.write(c[i + 1]);
-                        w.write("': ");
-                        w.write(res[i].error().getMessage());
-                        w.write("\n");
-                    }
-                }
-            }
         }
     }
-
-    public static class Set extends Command {
-        public String name() { return "set"; }
-        public String usage() { return "[name] [object]"; }
-        public String summary() { return "Sets ."; }
-        public String help() { return "Removes objects."; } // FIXME
-
-        public void execute(Writer w, String[] c, Env env) throws IOException {
-            if (c.length < 3) { w.write(usage()); }
-
-            String m = c[1];
-            String s = "return (";
-            for (int i=2; i < c.length; i++) s += c[i];
-            s += ");";
-
-            JS js;
-            try { js = JS.fromReader("input", 0, new StringReader(s)); }
-            catch (IOException e) {
-                w.write("error: ");
-                w.write(e.getMessage());
-                w.write("\n");
-                return;
-            }
-
-            Request.Response ret = env.send(new Request.SetKey(env.path, m, js));
-        }
-    }
-
 }
diff --git a/src/java/org/ibex/xt/shell/Env.java b/src/java/org/ibex/xt/shell/Env.java
deleted file mode 100644 (file)
index f09cc95..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.ibex.xt.shell;
-
-import java.io.IOException;
-
-public abstract class Env {
-    /** Current JS path using '.' as a seperator. */
-    public String path = "";
-
-    public Command[] commands = new Command[0];
-
-    /** Returns the command matching the given name. */
-    public Command command(String name) {
-        for (int i=0; i < commands.length; i++)
-            if (commands[i].name().equals(name)) return commands[i];
-        return null;
-    }
-
-    /** Returns a path, based on console-style representation. */
-    public String path(String c) {
-        if (c.equals("") || c.equals(".")) {
-            c = ".";
-        } else if (c.equals("..")) {
-            c = path;
-            c = c.substring(0, c.lastIndexOf('.'));
-            if (c.equals("")) c = ".";
-        } else {
-            if (c.charAt(0) != '.') c = path + "." + c;
-            if (c.length() > 1 && c.charAt(c.length() - 1) == '.')
-                c = c.substring(0, c.length() - 1);
-        }
-        return c;
-    }
-
-    public abstract Request.Response send(Request request) throws IOException;
-}
diff --git a/src/java/org/ibex/xt/shell/JSRemote.java b/src/java/org/ibex/xt/shell/JSRemote.java
new file mode 100644 (file)
index 0000000..f7886d8
--- /dev/null
@@ -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 <tt>in</tt>
+     *  and writes a response on <tt>out</tt> */
+    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
+        }
+    }
+}
diff --git a/src/java/org/ibex/xt/shell/Request.java b/src/java/org/ibex/xt/shell/Request.java
deleted file mode 100644 (file)
index 8cb2be6..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-package org.ibex.xt.shell;
-
-import java.io.*;
-import java.util.*;
-import java.util.regex.*;
-
-import org.ibex.js.*;
-
-public abstract class Request implements Serializable {
-
-    public abstract Response process(JSScope root) throws JSExn;
-
-    public static class Response implements Serializable {
-        protected Exception ex;
-        public Response() { ex = null; }
-        public Response(Exception e) { ex = e; }
-        public Exception error() { return ex; }
-    }
-
-    public static class Key extends Request {
-        protected String path, matcher;
-        public Key() {}
-
-        /** Expects a shell-path that uses '.' as a seperator and * for wildcard. */
-        public Key(String c) {
-            int pos = c.lastIndexOf('.');
-            path = c.substring(0, pos);
-            matcher = c.substring(pos + 1).replaceAll("\\*+", ".*");
-        }
-
-        public Key(String path, String matcher) {
-            this.path = path; this.matcher = matcher;
-        }
-
-        /** Returns the object referenced by path. */
-        protected Object path(JSScope root) throws JSExn {
-            String p = path == null ? "" : path.replaceAll("\\.+", "\\.");
-            if (p.length() > 0 && p.charAt(0) == '.')
-                p = p.substring(1);
-            if (p.length() > 0 && p.charAt(p.length() - 1) == '.')
-                p = p.substring(0, p.length() - 1);
-
-            return p.equals("") ? root : root.get(p);
-        }
-
-        /** Returns the keys in <tt>js</tt> that match <tt>matcher</tt>. */
-        protected List matches(JS js) throws JSExn {
-            String m = matcher;
-
-            Pattern pat = Pattern.compile(m);
-            List keys = new ArrayList();
-
-            Iterator i = js.keys().iterator(); while(i.hasNext()) {
-                String k = i.next().toString();
-                if (pat.matcher(k).matches()) keys.add(k);
-            }
-
-            return keys;
-        }
-
-        /** Returns <tt>o</tt> cast as a JS if it is such and has keys,
-         *  otherwise returns null. */
-        protected JS keyed(Object o) {
-            // FIXME: replace this with a canHaveKeys() function in org.ibex.js.JS
-            return o == null || !(o instanceof JS) ||
-                       o instanceof JSDate || o instanceof Directory ||
-                       o instanceof Grammar || o instanceof JSMath ||
-                       o instanceof JSReflection || o instanceof JSRegexp ?
-                   null : (JS)o;
-        }
-
-        public Response process(JSScope root) throws JSExn {
-            JS js = keyed(path(root));
-
-            // if matcher is exact and a keyed object, return its keys
-            JS o = keyed(js.get(matcher));
-            if (o != null) { js = o; matcher = ".*"; }
-
-            return js == null ? new Res() : new Res(matches(js));
-        }
-
-        public static class Res extends Response {
-            private List keys;
-            public Res() { keys = null; }
-            public Res(List l) { keys = l; }
-            public List keys() { return keys; }
-            public boolean isPath() { return keys != null; }
-        }
-    }
-
-    public static class IsKey extends Key {
-        public IsKey() {}
-        public IsKey(String c) { super(c); }
-        public Response process(JSScope root) throws JSExn {
-            JS js = keyed(path(root));
-            return js == null ? new Res(false) : new Res(js.get(matcher) != null);
-        }
-
-        public static class Res extends Response {
-            private boolean exists;
-            public Res() {}
-            public Res(boolean e) { exists = e; }
-            public boolean exists() { return exists; }
-        }
-    }
-
-    public static class RemoveKey extends Key {
-        public RemoveKey(String c) { super(c); }
-        public RemoveKey(String p, String m) { super(p, m); }
-        public Response process(JSScope root) throws JSExn {
-            JS js = keyed(path(root));
-            if (js == null) throw new JSExn("no such path");
-            boolean rm = js.containsKey(matcher);
-            if (rm) js.remove(matcher);
-            return new Res(rm);
-        }
-
-        public static class Res extends Response {
-            private boolean removed;
-            public Res() { }
-            public Res(boolean rm) { removed = rm; }
-            public boolean removed() { return removed; }
-        }
-    }
-
-    public static class SetKey extends Key {
-        protected JS value;
-        public SetKey() {}
-        public SetKey(String p, String m, JS val) { super(p, m); this.value = val; }
-
-        public Response process(JSScope root) throws JSExn {
-            JS js = keyed(path(root));
-            if (js == null) throw new JSExn("no such path");
-
-            js.put(matcher, JS.eval(JS.cloneWithNewParentScope(
-                                    value, new JSResolve(root, js))));
-            return new Res(); // TODO: return put() value when it has one
-        }
-
-        public class Res extends Response {
-        }
-
-        public class JSResolve extends JSScope {
-            private JS path;
-            public JSResolve(JSScope parent, JS path) { super(parent); this.path = path; }
-            public Object get(Object key) throws JSExn {
-                if (key != null && key instanceof String) {
-                    String k = (String)key;
-                    if (k.startsWith(".")) k = k.substring(1);
-                    else return path.get(k);
-                }
-                return super.get(key);
-            }
-            public void put(Object key, Object val) throws JSExn {
-                if (key != null && key instanceof String) {
-                    String k = (String)key;
-                    if (k.startsWith(".")) k = k.substring(1);
-                    else { path.put(key, val); return; }
-                }
-                super.put(key, val);
-            }
-        }
-    }
-
-    /** Runs a series of requests. */
-    public static class Composite extends Request {
-        private boolean breakOnError = false;
-        private Request[] requests;
-        public Composite() {}
-        public Composite(Request[] r, boolean breakOnError) {
-            requests = r;
-            this.breakOnError = breakOnError;
-        }
-        public Composite(List r, boolean breakOnError) {
-            Request[] req = new Request[r.size()];
-            r.toArray(req);
-            requests = req;
-            this.breakOnError = breakOnError;
-        }
-
-        public Response process(JSScope root) {
-            Response[] res = new Response[requests.length];
-
-            for (int i=0; i < requests.length; i++) {
-                try { res[i] = requests[i].process(root); }
-                catch (JSExn e) {
-                    res[i] = new Response(e);
-
-                    if (breakOnError) {
-                        Response[] newres = new Response[i + 1];
-                        System.arraycopy(res, 0, newres, 0,  newres.length);
-                        res = newres;
-                        break;
-                    }
-                }
-            }
-
-            return new Res(res);
-        }
-
-        public static class Res extends Response {
-            private Response[] responses;
-            public Res() {}
-            public Res(Response[] r) { responses = r; }
-            public Response get(int i) { return responses[i]; }
-            public int size() { return responses.length; }
-            public Response[] responses() { return responses; }
-        }
-    }
-}
index 44cc346..70b89d9 100644 (file)
@@ -22,33 +22,9 @@ public class Servlet extends HttpServlet {
     }
 
     public void doPost(HttpServletRequest rq, HttpServletResponse rs) throws IOException {
-        Request r;
-        try { r = (Request)new ObjectInputStream(rq.getInputStream()).readObject(); }
-        catch (ClassNotFoundException e) {
-            e.printStackTrace();
-            throw new IOException("exception receiving request, class not found");
-        }
-
-        JSScope scope = (JSScope)rq.getSession().getAttribute("scope");
-        if (scope == null) {
-            try {
-                scope = new JSScope(null) {
-                    { super.put("prevalent", prevalent); }
-
-                    public void put(String k, String v) throws JSExn {
-                        if (k != null && k.equals("prevalent")) throw new JSExn(
-                            "can not replace key prevalent");
-                        super.put(k, v);
-                    }
-                };
-            } catch (JSExn e) {
-                e.printStackTrace();
-                throw new IOException("unexpected JSExn");
-            }
-            rq.getSession().setAttribute("scope", scope);
-        }
-        Request.Response ret;
-        try { ret = r.process(scope); } catch (JSExn e) { ret = new Request.Response(e); }
-        new ObjectOutputStream(rs.getOutputStream()).writeObject(ret);
+        ObjectInputStream in = new ObjectInputStream(rq.getInputStream());
+        ObjectOutputStream out = new ObjectOutputStream(rs.getOutputStream());
+        JSRemote.receive(prevalent, in, out);
+        out.flush();
     }
 }
index 17a2f57..f677f28 100644 (file)
@@ -3,7 +3,10 @@ package org.ibex.xt.shell;
 import java.io.*;
 import java.net.*;
 
-public class Shell extends Env {
+import org.ibex.js.*;
+import org.ibex.util.*;
+
+public class Shell {
 
     public static void main(String[] args) throws Exception {
         if (args.length == 0 || args.length > 2||  !args[0].startsWith("http://")) {
@@ -22,22 +25,95 @@ public class Shell extends Env {
         System.out.println("Usage: xish url [command]");
     }
 
-    /** URL of server. */
-    protected URL server;
 
-    /** Server cookie. Reduces server load. */
-    private String cookie = null;
+    /** Supported commands. */
+    private Command[] commands;
+
+    /** Root context. */
+    private final JS root;
+
+    /** Current JS context for the shell. */
+    private JS scope;
+
+    /** Current path to <tt>scope</tt> in <tt>root</tt>. */
+    private Object[] path;
+
+    /** Returns the object represented by the given path,
+     *  ignoring the current shell path context.*/
+    public Object getFromPath(Object[] path) throws NoSuchPathException, JSExn {
+        if (path.length == 0) return root;
+
+        if (root instanceof JSRemote) {
+            // JSRemote will automatically process its keys for '.' seperators
+            StringBuffer sb = new StringBuffer(path[0].toString());
+            for (int i=1; i < path.length; i++) {
+                sb.append('.'); sb.append(path[i].toString());
+            }
+            return root.get(sb.toString());
+        } else {
+            JS cur = root;
+            for (int i=0; i < path.length - 1; i++) {
+                Object o =  cur.get(path[i]);
+                if (o == null || !(o instanceof JS)) throw new Shell.NoSuchPathException();
+                cur = (JS)o;
+            }
+            return cur.get(path[path.length - 1]);
+        }
+    }
+
+    /** Set the current path of the shell, modifiying the result of getScope(). */
+    public void setPath(Object[] s) throws NoSuchPathException {
+        JS cur = root;
+        if (s == null) s = new Object[0];
+        for (int i=0; i < s.length; i++) {
+            Object o;
+            try { o = cur.get(s[i]); } catch (JSExn e) { throw new NoSuchPathException(); }
+            if (o == null || !(o instanceof JS)) throw new NoSuchPathException();
+            cur = (JS)o;
+        }
+        scope = cur;
+        path = s;
+    }
+
+    /** Returns the current path of the shell. */
+    public Object[] getPath() { return path; }
+
+    /** Returns String represntation of path, using '/' as a seperator. */
+    public String getPathAsString() {
+        if (path.length == 0) return "/";
+        StringBuffer sb = new StringBuffer();
+        for (int i=0; i < path.length; i++) {
+            sb.append('/'); sb.append(path[i].toString());
+        }
+        return sb.toString();
+    }
+
+    /** Returns the context matching the current path. */
+    public JS getScope() { return scope; }
+
+    /** Returns the context matching the current path. */
+    public JS getRootScope() { return root; }
+
+    /** Returns all supported commands. */
+    public Command[] getCommands() { return commands; }
+
+    /** Returns the command matching the given name. */
+    public Command getCommand(String name) {
+        for (int i=0; i < commands.length; i++)
+            if (commands[i].name().equals(name)) return commands[i];
+        return null;
+    }
 
     /** Create a new Shell using the given url for the server. */
     public Shell(URL url) {
-        server = url;
+        root = scope = new JSRemote(url);
+        path = new String[0];
         commands = new Command[] {
-            new Command.Ls(),
-            new Command.Pwd(),
-            new Command.Cd(),
-            new Command.Rm(),
-            new Command.Set(),
-            new Command.Help()
+            new Command.Ls(this),
+            new Command.Pwd(this),
+            new Command.Cd(this),
+            new Command.Rm(this),
+            new Command.Help(this)
         };
     }
 
@@ -46,7 +122,7 @@ public class Shell extends Env {
         PrintWriter out = new PrintWriter(w);
 
         out.println("ibex xt shell: type help or exit");
-        out.print("xt: ");
+        out.print("xt:"); out.print(getPathAsString()); out.print("# ");
         out.flush();
 
         String line;
@@ -84,47 +160,19 @@ public class Shell extends Env {
 
             if (buffer.length() > 0) {
                 String[] c = buffer.split(" ");
-                Command cmd = command(c[0]);
+                Command cmd = getCommand(c[0]);
 
                 if (cmd == null) {
                     out.write(c[0]);
                     w.write(": command not found\n");
-                } else cmd.execute(out, c, this);
+                } else cmd.execute(out, c);
 
                 buffer = "";
             }
-            out.print("xt: ");
+            out.print("xt:"); out.print(getPathAsString()); out.print("# ");
             out.flush();
         }
     }
 
-    public Request.Response 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.writeObject(request);
-        out.close();
-
-        String cook = c.getHeaderField("Set-Cookie");
-        if (cook != null && !cook.equals("")) cookie = cook.substring(0, cook.indexOf(';'));
-
-        try {
-            Object o = new ObjectInputStream(c.getInputStream()).readObject();
-            if (o == null) {
-                throw new IOException("unexpected null object returned");
-            } else if (!(o instanceof Request.Response)) {
-                throw new IOException("unexpected object returned: "+o.getClass().getName());
-            } else {
-                return (Request.Response)o;
-            }
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-            throw new IOException("unexpected ClassNotFoundException");
-        }
-    }
+    public static class NoSuchPathException extends Exception {}
 }