2003/11/13 05:04:22
[org.ibex.core.git] / src / org / xwt / Res.java
index 3bf88ee..b2ae73b 100644 (file)
@@ -8,56 +8,38 @@ import org.xwt.js.*;
 import org.xwt.util.*;
 import org.bouncycastle.util.encoders.Base64;
 
-/** base class for XWT resources */
+
+/** Base class for XWT resources */
 public abstract class Res extends JS {
 
-    public abstract String getDescriptiveName();
+
+    // Public Static //////////////////////////////////////////////////////////////////////
+
+    // FIXME: move to XWT.load()?
+    public static Res fromString(String url) {
+        if (url.startsWith("http://")) return new HTTP(url);
+        else if (url.startsWith("https://")) return new HTTP(url);
+        else if (url.startsWith("data:")) return new ByteArray(Base64.decode(url.substring(5)));
+        else if (url.startsWith("utf8:")) return new ByteArray(url.substring(5).getBytes());
+        throw new JS.Exn("invalid resource specifier " + url);
+    }
+
+
+    // Base Class //////////////////////////////////////////////////////////////////////
+
     public String typeName() { return "resource"; }
 
-    /** cache of subresources so that the equality operator works on them */
+    /** so that we get the same subresource each time */
     private Hash refCache = null;
 
+    /** FIXME: needed? good idea? */
     public Template t = null;
 
-    public Res getParent() { return null; }
-
-    /** an InputStream that makes sure it is not in the MessageQueue when blocked on a read */
-    // FIXME
-    private static class BackgroundInputStream extends FilterInputStream {
-        BackgroundInputStream(InputStream i) { super(i); }
-    /*
-        private void suspend() throws IOException {
-            if (!ThreadMessage.suspendThread())
-                throw new IOException("attempt to perform background-only operation in a foreground thread");
-        }
-        private void resume() {
-            ThreadMessage.resumeThread();
-        }
-        public int read() throws IOException {
-            suspend();
-            try { return super.read(); }
-            finally { resume(); }
-        }
-        public int read(byte[] b, int off, int len) throws IOException {
-            suspend();
-            try { return super.read(b, off, len); }
-            finally { resume(); }
-        }
-    */
-    }
-
-    /** returns an InputStream containing the Resource's contents */
-    public final InputStream getInputStream() throws IOException { return new BackgroundInputStream(getInputStream("")); }
+    public final InputStream getInputStream() throws IOException { return getInputStream(""); }
     public abstract InputStream getInputStream(String path) throws IOException;
 
-    /** graft newResource in place of this resource on its parent */
-    public Res graft(Object newResource) { throw new JS.Exn("cannot graft onto this resource"); }
-
-    /** if the path of this resource does not end with extension, return a new one wit it appended */
     public Res addExtension(String extension) { return new Ref(this, extension); }
 
-    public Object[] keys() { throw new JS.Exn("cannot enumerate a resource"); } 
-    public Object put(Object key, Object val) { throw new JS.Exn("cannot put to a resource"); } 
     public Object get(Object key) {
         if ("".equals(key)) {
             Template t = Template.getTemplate(addExtension(".xwt"));
@@ -71,47 +53,34 @@ public abstract class Res extends JS {
         return ret;
     }
 
-    public static Res stringToRes(String url) {
-        if (url.indexOf('!') != -1) {
-            Res ret = new Zip(stringToRes(url.substring(0, url.lastIndexOf('!'))));
-            String subpath = url.substring(url.lastIndexOf('!') + 1);
-            if (subpath.length() > 0) ret = (Res)ret.get(subpath);
-            return ret;
-        }
-        if (url.startsWith("http://")) return new HTTP(url);
-        if (url.startsWith("https://")) return new HTTP(url);
-        if (url.startsWith("cab:")) return new Cab(stringToRes(url.substring(4)));
-        if (url.startsWith("data:")) return new ByteArray(Base64.decode(url.substring(5)));
-        if (url.startsWith("utf8:")) return new ByteArray(url.substring(5).getBytes());
-        throw new JS.Exn("invalid resource specifier " + url);
-    }
 
+
+    // Caching //////////////////////////////////////////////////////////////////////
+
+    private static class NotCacheableException extends Exception { }
+    private static NotCacheableException notCacheable = new NotCacheableException();
+
+    /** if it makes sense to cache a resource, the resource must return a unique key */
+    public String getCacheKey() throws NotCacheableException { throw notCacheable; }
+
+    // FIXME: general cleanup
     /** subclass from this if you want a CachedInputStream for each path */
     public static class CachedRes extends Res {
         private Res parent;
         private boolean disk = false;
-
-        // FIXME: security concern here
-        private String subdir = null;
-
-        public String getDescriptiveName() { return parent.getDescriptiveName(); }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey(); }
         private Hash cachedInputStreams = new Hash();
-        public CachedRes(Res parent, String subdir, boolean disk) {
-            this.parent = parent; this.disk = disk; this.subdir = subdir;
-        }
+        public CachedRes(Res p, String s, boolean d) { this.parent = p; this.disk = d; }
         public InputStream getInputStream(String path) throws IOException {
             CachedInputStream cis = (CachedInputStream)cachedInputStreams.get(path);
             if (cis == null) {
                 java.io.File f = null;
                 if (disk) {
-                    // FIXME ugly
-                    // FIXME need separate hash for disk/nondisk
                     f = new java.io.File(System.getProperty("user.home") +
                                          java.io.File.separatorChar + ".xwt" +
                                          java.io.File.separatorChar + "caches" +
-                                         java.io.File.separatorChar + subdir +
                                          java.io.File.separatorChar +
-                                         new String(Base64.encode(parent.getDescriptiveName().getBytes())));
+                                         new String(Base64.encode(parent.getCacheKey().getBytes())));
                     Log.log(this, "caching resource in " + f);
                     new java.io.File(f.getParent()).mkdirs();
                     if (f.exists()) return new FileInputStream(f);
@@ -123,20 +92,23 @@ public abstract class Res extends JS {
         }
     }
 
+
+    // Useful Subclasses //////////////////////////////////////////////////////////////////////
+
     /** HTTP or HTTPS resource */
     public static class HTTP extends Res {
         private String url;
-        HTTP(String url) { this.url = url; }
-        public String getDescriptiveName() { return url; }
-        public InputStream getInputStream(String path) throws IOException {
-            return new org.xwt.HTTP(url + path).GET(); }
+        HTTP(String url) { while (url.endsWith('/')) url = url.substring(0, url.length() - 1); this.url = url; }
+        public String getCacheKey() throws NotCacheableException { return url; }
+        public InputStream getInputStream(String path) throws IOException { return new org.xwt.HTTP(url + path).GET(); }
     }
 
     /** byte arrays */
     public static class ByteArray extends Res {
         private byte[] bytes;
-        ByteArray(byte[] bytes) { this.bytes = bytes; }
-        public String getDescriptiveName() { return "byte[]"; }
+        private String cacheKey = null;
+        ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; }}
+        public String getCacheKey() throws NotCacheableException { return cacheKey; }
         public InputStream getInputStream(String path) throws IOException {
             if (!"".equals(path)) throw new JS.Exn("can't get subresources of a byte[] resource");
             return new ByteArrayInputStream(bytes);
@@ -146,8 +118,11 @@ public abstract class Res extends JS {
     /** a file */
     public static class File extends Res {
         private String path;
-        File(String path) { this.path = path; }
-        public String getDescriptiveName() { return "file://" + path; }
+        File(String path) {
+            while (path.endsWith(java.io.File.separatorChar)) path = path.substring(0, path.length() - 1);
+            this.path = path;
+        }
+        public String getCacheKey() throws NotCacheableException { throw notCacheable; }  // already on the disk!
         public InputStream getInputStream(String rest) throws IOException {
             return new FileInputStream((path + rest).replace('/', java.io.File.separatorChar)); }
     }
@@ -156,7 +131,7 @@ public abstract class Res extends JS {
     public static class Zip extends Res {
         private Res parent;
         Zip(Res parent) { this.parent = parent; }
-        public String getDescriptiveName() { return parent.getDescriptiveName() + "!"; }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!zip:"; }
         public InputStream getInputStream(String path) throws IOException {
             if (path.startsWith("/")) path = path.substring(1);
             InputStream pis = parent.getInputStream();
@@ -172,9 +147,8 @@ public abstract class Res extends JS {
     public static class Cab extends Res {
         private Res parent;
         Cab(Res parent) { this.parent = parent; }
-        public String getDescriptiveName() { return "cab[" + parent.getDescriptiveName() + "]"; }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
         public InputStream getInputStream(String path) throws IOException {
-            // FIXME: knownlength
             if (path.startsWith("/")) path = path.substring(1);
             return new org.xwt.translators.MSPack(parent.getInputStream()).getInputStream(path);
         }
@@ -183,7 +157,7 @@ public abstract class Res extends JS {
     /** the Builtin resource */
     public static class Builtin extends Res {
        public Builtin() { };
-       public String getDescriptiveName() { return "[builtin]"; }
+       public String getCacheKey() throws NotCacheableException { throw notCacheable; }    // not cacheable
        public InputStream getInputStream(String path) throws IOException {
            if (!path.equals("")) throw new IOException("the builtin resource has no subresources");
            return Platform.getBuiltinInputStream();
@@ -195,53 +169,18 @@ public abstract class Res extends JS {
         Res parent;
         Object key;
         Ref(Res parent, Object key) { this.parent = parent; this.key = key; }
-        public String getDescriptiveName() {
-            String pdn = parent.getDescriptiveName();
-           if (pdn.equals("")) return key.toString();
-           if (!pdn.endsWith("!")) pdn += ".";
-           return pdn + key.toString();
-        }
+        public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "/" + key; }
         public Res addExtension(String extension) {
-            return (key instanceof String && ((String)key).endsWith(extension)) ? this : new Ref(parent, key + extension);
-        }
-        public InputStream getInputStream(String path) throws IOException {
-            return parent.getInputStream("/" + key + path);
-        }
-        public Res getParent() { return parent; }
-        public Res graft(Object newResource) { return new Graft(parent, key, newResource); }
-    }
-
-    // FEATURE: eliminate code duplication with JS.Graft
-    /** shadow resource which replaces the graft */
-    public static class Graft extends Res {
-        Res graftee;
-        Object replaced_key;
-        Object replaced_val;
-        Graft(Res graftee, Object key, Object val) { this.graftee = graftee; replaced_key = key; replaced_val = val; }
-        public boolean equals(Object o) { return (this == o || graftee.equals(o)); }
-        public int hashCode() { return graftee.hashCode(); }
-        public InputStream getInputStream(String s) throws IOException { return graftee.getInputStream(s); }
-        public Object get(Object key) { return replaced_key.equals(key) ? replaced_val : graftee.get(key); }
-        public String getDescriptiveName() { return graftee.getDescriptiveName(); }
-        public Res getParent() { return graftee.getParent(); }
-        public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-            if (!replaced_key.equals(method)) return graftee.callMethod(method, args, checkOnly);
-            if (replaced_val instanceof Callable) return checkOnly ? Boolean.TRUE : ((Callable)replaced_val).call(args);
-            if (checkOnly) return Boolean.FALSE;
-            throw new JS.Exn("attempt to call non-function");
-        }
-        public Number coerceToNumber() { return graftee.coerceToNumber(); }
-        public String coerceToString() { return graftee.coerceToString(); }
-        public boolean coerceToBoolean() { return graftee.coerceToBoolean(); }
-        public String typeName() { return graftee.typeName(); }
+            return ((String)key).endsWith(extension) ? this : new Ref(parent, key + extension); }
+        public InputStream getInputStream(String path) throws IOException { return parent.getInputStream("/" + key + path); }
     }
 
     /** shadow resource which replaces the graft */
     public static class ProgressWatcher extends Res {
         final Res watchee;
-        Function callback;
-        ProgressWatcher(Res watchee, Function callback) { this.watchee = watchee; this.callback = callback; }
-        public String getDescriptiveName() { return watchee.getDescriptiveName(); }
+        JSFunction callback;
+        ProgressWatcher(Res watchee, JSFunction callback) { this.watchee = watchee; this.callback = callback; }
+        public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
         public InputStream getInputStream(String s) throws IOException {
             final InputStream is = watchee.getInputStream(s);
             return new FilterInputStream(is) {
@@ -255,107 +194,14 @@ public abstract class Res extends JS {
                         int ret = super.read(b, off, len);
                         if (ret != 1) bytesDownloaded += ret;
                         Scheduler.add(new Scheduler.Task() { public void perform() {
-                            JS.Array args = new JS.Array();
+                            JSArray args = new JSArray();
                             args.addElement(new Integer(bytesDownloaded));
                             args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
-                            // FIXME
-                            //new JS.Context(callback, null, args).resume();
+                            callback.call(args);
                         } });
                         return ret;
                     }
                 };
         }
     }
-
-    public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
-        if (method.equals("getUTF")) {
-            if (checkOnly) return Boolean.TRUE;
-            if (args.length() != 0) return null;
-            try {
-                CharArrayWriter caw = new CharArrayWriter();
-                InputStream is = getInputStream();
-                BufferedReader r = new BufferedReader(new InputStreamReader(is));
-                char[] buf = new char[1024];
-                while(true) {
-                    int numread = r.read(buf, 0, 1024);
-                    if (numread == -1) break;
-                    caw.write(buf, 0, numread);
-                }
-                return caw.toString();
-            } catch (IOException e) {
-                if (Log.on) Log.log(Res.class, "IO Exception while reading from file");
-                if (Log.on) Log.log(Res.class, e);
-                throw new JS.Exn("error while reading from Resource");
-            }
-        } else if (method.equals("getDOM")) {
-            if (checkOnly) return Boolean.TRUE;
-            if (args.length() != 0) return null;
-            return new XMLHelper().doParse();
-        }
-        if (checkOnly) return Boolean.FALSE;
-        return null;
-    }
-
-    private class XMLHelper extends XML {
-        Vector obStack = new Vector();
-        public XMLHelper() { super(BUFFER_SIZE); }
-        public void startElement(XML.Element c) throws XML.SchemaException {
-            JS o = new JS.Obj();
-            o.put("$name", c.localName);
-            for(int i=0; i<c.len; i++) o.put(c.keys[i], c.vals[i]);
-            o.put("$numchildren", new Integer(0));
-            obStack.addElement(o);
-        }
-        public void endElement(XML.Element c) throws XML.SchemaException {
-            if (obStack.size() == 1) return;
-            JS me = (JS)obStack.lastElement();
-            obStack.setSize(obStack.size() - 1);
-            JS parent = (JS)obStack.lastElement();
-            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
-            parent.put("$numchildren", new Integer(numchildren + 1));
-            parent.put(new Integer(numchildren), me);
-        }
-        public void characters(char[] ch, int start, int length) throws XML.SchemaException {
-            String s = new String(ch, start, length);
-            JS parent = (JS)obStack.lastElement();
-            int numchildren = ((Integer)parent.get("$numchildren")).intValue();
-            Object lastChild = parent.get(new Integer(numchildren - 1));
-            if (lastChild instanceof String) {
-                parent.put(new Integer(numchildren - 1), lastChild + s);
-            } else {
-                parent.put("$numchildren", new Integer(numchildren + 1));
-                parent.put(new Integer(numchildren), s);
-            }
-        }
-        public void whitespace(char[] ch, int start, int length) {}
-        public JS doParse() throws JS.Exn {
-            try { 
-                InputStream is = getInputStream();
-                BufferedReader r = new BufferedReader(new InputStreamReader(is));
-                parse(r);
-            } catch (XML.XMLException e) {
-                throw new JS.Exn("error parsing XML: " + e.toString());
-            } catch (IOException e) {
-                if (Log.on) Log.log(this, "IO Exception while reading from file");
-                if (Log.on) Log.log(this, e);
-                throw new JS.Exn("error reading from Resource");
-            }
-            return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
-        }
-    }
-
-    public void writeTo(OutputStream os) throws IOException {
-        InputStream is = getInputStream();
-        byte[] buf = new byte[1024];
-        while(true) {
-            int numread = is.read(buf, 0, 1024);
-            if (numread == -1) break;
-            if (Log.on) Log.log(this, "wrote " + numread + " bytes");
-            os.write(buf, 0, numread);
-        }
-        os.flush();
-
-        // we have to close this because flush() doesn't work on Win32-GCJ
-        os.close();
-    }
 }