399476b770fbe4ec8c8e431dd0a872774dc36900
[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     /** the Builtin resource */
149     public static class Builtin extends Res {
150         public Builtin() { };
151         public String getDescriptiveName() { return "[builtin]"; }
152         public InputStream getInputStream(String path) throws IOException {
153             if (!path.equals("")) throw new IOException("the builtin resource has no subresources");
154             return Platform.getBuiltinInputStream();
155         }
156     }
157
158     /** what you get when you reference a subresource */
159     public static class Ref extends Res {
160         Res parent;
161         Object key;
162         Ref(Res parent, Object key) { this.parent = parent; this.key = key; }
163         public String getDescriptiveName() {
164             String pdn = parent.getDescriptiveName();
165             if (pdn.equals("")) return key.toString();
166             if (!pdn.endsWith("!")) pdn += ".";
167             return pdn + key.toString();
168         }
169         public Res addExtension(String extension) {
170             return (key instanceof String && ((String)key).endsWith(extension)) ? this : new Ref(parent, key + extension);
171         }
172         public InputStream getInputStream(String path) throws IOException {
173             return parent.getInputStream("/" + key + path);
174         }
175         public Res getParent() { return parent; }
176         public Res graft(Object newResource) { return new Graft(parent, key, newResource); }
177     }
178
179     // FEATURE: eliminate code duplication with JS.Graft
180     /** shadow resource which replaces the graft */
181     public static class Graft extends Res {
182         Res graftee;
183         Object replaced_key;
184         Object replaced_val;
185         Graft(Res graftee, Object key, Object val) { this.graftee = graftee; replaced_key = key; replaced_val = val; }
186         public boolean equals(Object o) { return (this == o || graftee.equals(o)); }
187         public int hashCode() { return graftee.hashCode(); }
188         public InputStream getInputStream(String s) throws IOException { return graftee.getInputStream(s); }
189         public Object get(Object key) { return replaced_key.equals(key) ? replaced_val : graftee.get(key); }
190         public String getDescriptiveName() { return graftee.getDescriptiveName(); }
191         public Res getParent() { return graftee.getParent(); }
192         public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
193             if (!replaced_key.equals(method)) return graftee.callMethod(method, args, checkOnly);
194             if (replaced_val instanceof Callable) return checkOnly ? Boolean.TRUE : ((Callable)replaced_val).call(args);
195             if (checkOnly) return Boolean.FALSE;
196             throw new JS.Exn("attempt to call non-function");
197         }
198         public Number coerceToNumber() { return graftee.coerceToNumber(); }
199         public String coerceToString() { return graftee.coerceToString(); }
200         public boolean coerceToBoolean() { return graftee.coerceToBoolean(); }
201         public String typeName() { return graftee.typeName(); }
202     }
203
204     /** shadow resource which replaces the graft */
205     public static class ProgressWatcher extends Res {
206         final Res watchee;
207         JS.CompiledFunction callback;
208         ProgressWatcher(Res watchee, JS.CompiledFunction callback) { this.watchee = watchee; this.callback = callback; }
209         public String getDescriptiveName() { return watchee.getDescriptiveName(); }
210         public InputStream getInputStream(String s) throws IOException {
211             final InputStream is = watchee.getInputStream(s);
212             return new FilterInputStream(is) {
213                     int bytesDownloaded = 0;
214                     public int read() throws IOException {
215                         int ret = super.read();
216                         if (ret != -1) bytesDownloaded++;
217                         return ret;
218                     }
219                     public int read(byte[] b, int off, int len) throws IOException {
220                         int ret = super.read(b, off, len);
221                         if (ret != 1) bytesDownloaded += ret;
222                         Scheduler.add(new Scheduler.Task() { public void perform() {
223                             JS.Array args = new JS.Array();
224                             args.addElement(new Integer(bytesDownloaded));
225                             args.addElement(new Integer(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0));
226                             // FIXME
227                             //new JS.Context(callback, null, args).resume();
228                         } });
229                         return ret;
230                     }
231                 };
232         }
233     }
234
235     /** unpacks a Microsoft CAB file (possibly embedded in another file; we scan for 'MSCF' */
236     public static class CAB extends Res {
237         private Res parent;
238         CAB(Res parent) { this.parent = parent; }
239         private int swap_endian(int i) {
240             return ((i & 0xff) << 24) | ((i & 0xff00) << 8) | ((i & 0xff0000) >>> 8) | (i >>> 24);
241         }
242         public InputStream getInputStream(String path) throws IOException {
243             try {
244                return org.xwt.util.CAB.getFileInputStream(parent.getInputStream(), 2, path);
245             } catch (EOFException eof) {
246                throw new JS.Exn("MSCF header tag not found in file");
247             } catch (IOException ioe) {
248                throw new JS.Exn("IOException while reading file");
249             }
250         }
251     }
252
253     public Object callMethod(Object method, Array args, boolean checkOnly) throws JS.Exn {
254         if (method.equals("getUTF")) {
255             if (checkOnly) return Boolean.TRUE;
256             if (args.length() != 0) return null;
257             try {
258                 CharArrayWriter caw = new CharArrayWriter();
259                 InputStream is = getInputStream();
260                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
261                 char[] buf = new char[1024];
262                 while(true) {
263                     int numread = r.read(buf, 0, 1024);
264                     if (numread == -1) break;
265                     caw.write(buf, 0, numread);
266                 }
267                 return caw.toString();
268             } catch (IOException e) {
269                 if (Log.on) Log.log(Res.class, "IO Exception while reading from file");
270                 if (Log.on) Log.log(Res.class, e);
271                 throw new JS.Exn("error while reading from Resource");
272             }
273         } else if (method.equals("getDOM")) {
274             if (checkOnly) return Boolean.TRUE;
275             if (args.length() != 0) return null;
276             return new XMLHelper().doParse();
277         }
278         if (checkOnly) return Boolean.FALSE;
279         return null;
280     }
281
282     private class XMLHelper extends XML {
283         Vector obStack = new Vector();
284         public XMLHelper() { super(BUFFER_SIZE); }
285         public void startElement(XML.Element c) throws XML.SchemaException {
286             JS o = new JS.Obj();
287             o.put("$name", c.localName);
288             for(int i=0; i<c.len; i++) o.put(c.keys[i], c.vals[i]);
289             o.put("$numchildren", new Integer(0));
290             obStack.addElement(o);
291         }
292         public void endElement(XML.Element c) throws XML.SchemaException {
293             if (obStack.size() == 1) return;
294             JS me = (JS)obStack.lastElement();
295             obStack.setSize(obStack.size() - 1);
296             JS parent = (JS)obStack.lastElement();
297             int numchildren = ((Integer)parent.get("$numchildren")).intValue();
298             parent.put("$numchildren", new Integer(numchildren + 1));
299             parent.put(new Integer(numchildren), me);
300         }
301         public void characters(char[] ch, int start, int length) throws XML.SchemaException {
302             String s = new String(ch, start, length);
303             JS parent = (JS)obStack.lastElement();
304             int numchildren = ((Integer)parent.get("$numchildren")).intValue();
305             Object lastChild = parent.get(new Integer(numchildren - 1));
306             if (lastChild instanceof String) {
307                 parent.put(new Integer(numchildren - 1), lastChild + s);
308             } else {
309                 parent.put("$numchildren", new Integer(numchildren + 1));
310                 parent.put(new Integer(numchildren), s);
311             }
312         }
313         public void whitespace(char[] ch, int start, int length) {}
314         public JS doParse() throws JS.Exn {
315             try { 
316                 InputStream is = getInputStream();
317                 BufferedReader r = new BufferedReader(new InputStreamReader(is));
318                 parse(r);
319             } catch (XML.XMLException e) {
320                 throw new JS.Exn("error parsing XML: " + e.toString());
321             } catch (IOException e) {
322                 if (Log.on) Log.log(this, "IO Exception while reading from file");
323                 if (Log.on) Log.log(this, e);
324                 throw new JS.Exn("error reading from Resource");
325             }
326             return obStack.size() >= 1 ? (JS)obStack.elementAt(0) : null;
327         }
328     }
329
330     public void writeTo(OutputStream os) throws IOException {
331         InputStream is = getInputStream();
332         byte[] buf = new byte[1024];
333         while(true) {
334             int numread = is.read(buf, 0, 1024);
335             if (numread == -1) break;
336             if (Log.on) Log.log(this, "wrote " + numread + " bytes");
337             os.write(buf, 0, numread);
338         }
339         os.flush();
340
341         // we have to close this because flush() doesn't work on Win32-GCJ
342         os.close();
343     }
344 }