2003/11/03 06:32:55
[org.ibex.core.git] / src / org / xwt / Res.java
1 // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import java.io.*;
5 import java.util.*;
6 import java.util.zip.*;
7 import org.xwt.js.*;
8 import org.xwt.util.*;
9 import org.bouncycastle.util.encoders.Base64;
10
11 /** base class for XWT resources */
12 public abstract class Res extends JS {
13
14     public String getDescriptiveName() { return ""; }
15     public String typeName() { return "resource"; }
16
17     /** cache of subresources so that the equality operator works on them */
18     private Hash refCache = null;
19
20     public Template t = null;
21
22     public Res getParent() { return null; }
23
24     /** an InputStream that makes sure it is not in the MessageQueue when blocked on a read */
25     // FIXME
26     private static class BackgroundInputStream extends FilterInputStream {
27         BackgroundInputStream(InputStream i) { super(i); }
28     /*
29         private void suspend() throws IOException {
30             if (!ThreadMessage.suspendThread())
31                 throw new IOException("attempt to perform background-only operation in a foreground thread");
32         }
33         private void resume() {
34             ThreadMessage.resumeThread();
35         }
36         public int read() throws IOException {
37             suspend();
38             try { return super.read(); }
39             finally { resume(); }
40         }
41         public int read(byte[] b, int off, int len) throws IOException {
42             suspend();
43             try { return super.read(b, off, len); }
44             finally { resume(); }
45         }
46     */
47     }
48
49     /** returns an InputStream containing the Resource's contents */
50     public final InputStream getInputStream() throws IOException { return new BackgroundInputStream(getInputStream("")); }
51     public abstract InputStream getInputStream(String path) throws IOException;
52
53     /** graft newResource in place of this resource on its parent */
54     public Res graft(Object newResource) { throw new JS.Exn("cannot graft onto this resource"); }
55
56     /** if the path of this resource does not end with extension, return a new one wit it appended */
57     public Res addExtension(String extension) { return new Ref(this, extension); }
58
59     public Object[] keys() { throw new JS.Exn("cannot enumerate a resource"); } 
60     public Object put(Object key, Object val) { throw new JS.Exn("cannot put to a resource"); } 
61     public Object get(Object key) {
62         if ("".equals(key)) {
63             Template t = Template.getTemplate(addExtension(".xwt"));
64             return t == null ? null : t.getStatic();
65         }
66         Object ret = refCache == null ? null : refCache.get(key);
67         if (ret != null) return ret;
68         ret = new Ref(this, key);
69         if (refCache == null) refCache = new Hash();
70         refCache.put(key, ret);
71         return ret;
72     }
73
74     public static Res stringToRes(String url) {
75         if (url.indexOf('!') != -1) {
76             Res ret = new Zip(stringToRes(url.substring(0, url.lastIndexOf('!'))));
77             String subpath = url.substring(url.lastIndexOf('!') + 1);
78             if (subpath.length() > 0) ret = (Res)ret.get(subpath);
79             return ret;
80         }
81         if (url.startsWith("http://")) return new HTTP(url);
82         if (url.startsWith("https://")) return new HTTP(url);
83         if (url.startsWith("cab:")) return new Cab(stringToRes(url.substring(4)));
84         if (url.startsWith("data:")) return new ByteArray(Base64.decode(url.substring(5)));
85         if (url.startsWith("utf8:")) return new ByteArray(url.substring(5).getBytes());
86         throw new JS.Exn("invalid resource specifier " + url);
87     }
88
89     /** subclass from this if you want a CachedInputStream for each path */
90     public static abstract class CachedRes extends Res {
91         private Hash cachedInputStreams = new Hash();
92         abstract InputStream _getInputStream(String path) throws IOException;
93         public final InputStream getInputStream(String path) throws IOException {
94             CachedInputStream cis = (CachedInputStream)cachedInputStreams.get(path);
95             if (cis == null) {
96                 cis = new CachedInputStream(_getInputStream(path));
97                 cachedInputStreams.put(path, cis);
98             }
99             return cis.getInputStream();
100         }
101     }
102
103     /** HTTP or HTTPS resource */
104     public static class HTTP extends CachedRes {
105         private String url;
106         HTTP(String url) { this.url = url; }
107         public String getDescriptiveName() { return url; }
108         public InputStream _getInputStream(String path) throws IOException {
109             return new org.xwt.HTTP(url + path).GET(); }
110     }
111
112     /** byte arrays */
113     public static class ByteArray extends Res {
114         private byte[] bytes;
115         ByteArray(byte[] bytes) { this.bytes = bytes; }
116         public String getDescriptiveName() { return "byte[]"; }
117         public InputStream getInputStream(String path) throws IOException {
118             if (!"".equals(path)) throw new JS.Exn("can't get subresources of a byte[] resource");
119             return new ByteArrayInputStream(bytes);
120         }
121     }
122
123     /** a file */
124     public static class File extends Res {
125         private String path;
126         File(String path) { this.path = path; }
127         public String getDescriptiveName() { return "file://" + path; }
128         public InputStream getInputStream(String rest) throws IOException {
129             return new FileInputStream((path + rest).replace('/', java.io.File.separatorChar)); }
130     }
131
132     /** "unwrap" a Zip archive */
133     public static class Zip extends Res {
134         private Res parent;
135         Zip(Res parent) { this.parent = parent; }
136         public String getDescriptiveName() { return parent.getDescriptiveName() + "!"; }
137         public InputStream getInputStream(String path) throws IOException {
138             if (path.startsWith("/")) path = path.substring(1);
139             InputStream pis = parent.getInputStream();
140             ZipInputStream zis = new ZipInputStream(pis);
141             ZipEntry ze = zis.getNextEntry();
142             while(ze != null && !ze.getName().equals(path)) ze = zis.getNextEntry();
143             if (ze == null) throw new JS.Exn("requested file (" + path + ") not found in archive");
144             return new KnownLength.KnownLengthInputStream(zis, (int)ze.getSize());
145         }
146     }
147
148     /** "unwrap" a Cab archive */
149     public static class Cab extends Res {
150         private Res parent;
151         Cab(Res parent) { this.parent = parent; }
152         public String getDescriptiveName() { return "cab[" + parent.getDescriptiveName() + "]"; }
153         public InputStream getInputStream(String path) throws IOException {
154             // FIXME: knownlength
155             if (path.startsWith("/")) path = path.substring(1);
156             return new org.xwt.translators.MSPack(parent.getInputStream()).getInputStream(path);
157         }
158     }
159
160     /** the Builtin resource */
161     public static class Builtin extends Res {
162         public Builtin() { };
163         public String getDescriptiveName() { return "[builtin]"; }
164         public InputStream getInputStream(String path) throws IOException {
165             if (!path.equals("")) throw new IOException("the builtin resource has no subresources");
166             return Platform.getBuiltinInputStream();
167         }
168     }
169
170     /** what you get when you reference a subresource */
171     public static class Ref extends Res {
172         Res parent;
173         Object key;
174         Ref(Res parent, Object key) { this.parent = parent; this.key = key; }
175         public String getDescriptiveName() {
176             String pdn = parent.getDescriptiveName();
177             if (pdn.equals("")) return key.toString();
178             if (!pdn.endsWith("!")) pdn += ".";
179             return pdn + key.toString();
180         }
181         public Res addExtension(String extension) {
182             return (key instanceof String && ((String)key).endsWith(extension)) ? this : new Ref(parent, key + extension);
183         }
184         public InputStream getInputStream(String path) throws IOException {
185             return parent.getInputStream("/" + key + path);
186         }
187         public Res getParent() { return parent; }
188         public Res graft(Object newResource) { return new Graft(parent, key, newResource); }
189     }
190
191     // FEATURE: eliminate code duplication with JS.Graft
192     /** shadow resource which replaces the graft */
193     public static class Graft extends Res {
194         Res graftee;
195         Object replaced_key;
196         Object replaced_val;
197         Graft(Res graftee, Object key, Object val) { this.graftee = graftee; replaced_key = key; replaced_val = val; }
198         public boolean equals(Object o) { return (this == o || graftee.equals(o)); }
199         public int hashCode() { return graftee.hashCode(); }
200         public InputStream getInputStream(String s) throws IOException { return graftee.getInputStream(s); }
201         public Object get(Object key) { return replaced_key.equals(key) ? replaced_val : graftee.get(key); }
202         public String getDescriptiveName() { return graftee.getDescriptiveName(); }
203         public Res getParent() { return graftee.getParent(); }
204         public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
205             if (!replaced_key.equals(method)) return graftee.callMethod(method, args, checkOnly);
206             if (replaced_val instanceof Callable) return checkOnly ? Boolean.TRUE : ((Callable)replaced_val).call(args);
207             if (checkOnly) return Boolean.FALSE;
208             throw new JS.Exn("attempt to call non-function");
209         }
210         public Number coerceToNumber() { return graftee.coerceToNumber(); }
211         public String coerceToString() { return graftee.coerceToString(); }
212         public boolean coerceToBoolean() { return graftee.coerceToBoolean(); }
213         public String typeName() { return graftee.typeName(); }
214     }
215
216     /** shadow resource which replaces the graft */
217     public static class ProgressWatcher extends Res {
218         final Res watchee;
219         Function callback;
220         ProgressWatcher(Res watchee, Function callback) { this.watchee = watchee; this.callback = callback; }
221         public String getDescriptiveName() { return watchee.getDescriptiveName(); }
222         public InputStream getInputStream(String s) throws IOException {
223             final InputStream is = watchee.getInputStream(s);
224             return new FilterInputStream(is) {
225                     int bytesDownloaded = 0;
226                     public int read() throws IOException {
227                         int ret = super.read();
228                         if (ret != -1) bytesDownloaded++;
229                         return ret;
230                     }
231                     public int read(byte[] b, int off, int len) throws IOException {
232                         int ret = super.read(b, off, len);
233                         if (ret != 1) bytesDownloaded += ret;
234                         Scheduler.add(new Scheduler.Task() { public void perform() {
235                             JS.Array args = new JS.Array();
236                             args.addElement(new Integer(bytesDownloaded));
237                             args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
238                             // FIXME
239                             //new JS.Context(callback, null, args).resume();
240                         } });
241                         return ret;
242                     }
243                 };
244         }
245     }
246
247     public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
248         if (method.equals("getUTF")) {
249             if (checkOnly) return Boolean.TRUE;
250             if (args.length() != 0) return null;
251             try {
252                 CharArrayWriter caw = new CharArrayWriter();
253                 InputStream is = getInputStream();
254                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
255                 char[] buf = new char[1024];
256                 while(true) {
257                     int numread = r.read(buf, 0, 1024);
258                     if (numread == -1) break;
259                     caw.write(buf, 0, numread);
260                 }
261                 return caw.toString();
262             } catch (IOException e) {
263                 if (Log.on) Log.log(Res.class, "IO Exception while reading from file");
264                 if (Log.on) Log.log(Res.class, e);
265                 throw new JS.Exn("error while reading from Resource");
266             }
267         } else if (method.equals("getDOM")) {
268             if (checkOnly) return Boolean.TRUE;
269             if (args.length() != 0) return null;
270             return new XMLHelper().doParse();
271         }
272         if (checkOnly) return Boolean.FALSE;
273         return null;
274     }
275
276     private class XMLHelper extends XML {
277         Vector obStack = new Vector();
278         public XMLHelper() { super(BUFFER_SIZE); }
279         public void startElement(XML.Element c) throws XML.SchemaException {
280             JS o = new JS.Obj();
281             o.put("$name", c.localName);
282             for(int i=0; i<c.len; i++) o.put(c.keys[i], c.vals[i]);
283             o.put("$numchildren", new Integer(0));
284             obStack.addElement(o);
285         }
286         public void endElement(XML.Element c) throws XML.SchemaException {
287             if (obStack.size() == 1) return;
288             JS me = (JS)obStack.lastElement();
289             obStack.setSize(obStack.size() - 1);
290             JS parent = (JS)obStack.lastElement();
291             int numchildren = ((Integer)parent.get("$numchildren")).intValue();
292             parent.put("$numchildren", new Integer(numchildren + 1));
293             parent.put(new Integer(numchildren), me);
294         }
295         public void characters(char[] ch, int start, int length) throws XML.SchemaException {
296             String s = new String(ch, start, length);
297             JS parent = (JS)obStack.lastElement();
298             int numchildren = ((Integer)parent.get("$numchildren")).intValue();
299             Object lastChild = parent.get(new Integer(numchildren - 1));
300             if (lastChild instanceof String) {
301                 parent.put(new Integer(numchildren - 1), lastChild + s);
302             } else {
303                 parent.put("$numchildren", new Integer(numchildren + 1));
304                 parent.put(new Integer(numchildren), s);
305             }
306         }
307         public void whitespace(char[] ch, int start, int length) {}
308         public JS doParse() throws JS.Exn {
309             try { 
310                 InputStream is = getInputStream();
311                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
312                 parse(r);
313             } catch (XML.XMLException e) {
314                 throw new JS.Exn("error parsing XML: " + e.toString());
315             } catch (IOException e) {
316                 if (Log.on) Log.log(this, "IO Exception while reading from file");
317                 if (Log.on) Log.log(this, e);
318                 throw new JS.Exn("error reading from Resource");
319             }
320             return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
321         }
322     }
323
324     public void writeTo(OutputStream os) throws IOException {
325         InputStream is = getInputStream();
326         byte[] buf = new byte[1024];
327         while(true) {
328             int numread = is.read(buf, 0, 1024);
329             if (numread == -1) break;
330             if (Log.on) Log.log(this, "wrote " + numread + " bytes");
331             os.write(buf, 0, numread);
332         }
333         os.flush();
334
335         // we have to close this because flush() doesn't work on Win32-GCJ
336         os.close();
337     }
338 }