1    // FIXEME
2    // Copyright 2003 Adam Megacz, see the COPYING file for licensing [GPL]
3    package org.xwt;
4    
5    import java.io.*;
6    import java.util.*;
7    import java.util.zip.*;
8    import org.xwt.js.*;
9    import org.xwt.util.*;
10   import org.bouncycastle.util.encoders.Base64;
11   
12   
13   /** Base class for XWT resources */
14   public abstract class Res extends JS {
15   
16       /** return a resource for a given url */
17       public static final Res fromURL(String url) throws JSExn {
18           if (url.startsWith("http://")) return new Res.HTTP(url);
19           else if (url.startsWith("https://")) return new Res.HTTP(url);
20           else if (url.startsWith("data:")) return new Res.ByteArray(Base64.decode(url.substring(5)), null);
21           else if (url.startsWith("utf8:")) return new Res.ByteArray(url.substring(5).getBytes(), null);
22           throw new JSExn("invalid resource specifier " + url);
23       }
24   
25       // Base Class //////////////////////////////////////////////////////////////////////
26   
27       public String typeName() { return "resource"; }
28   
29       /** so that we get the same subresource each time */
30       private Hash refCache = null;
31   
32       public Template t = null;
33   
34       public final InputStream getInputStream() throws IOException { return getInputStream(""); }
35       public abstract InputStream getInputStream(String path) throws IOException;
36   
37       public Res addExtension(String extension) { return new Ref(this, extension); }
38   
39       public Object get(Object key) throws JSExn {
40           if ("".equals(key)) {
41               try {
42                   Template t = Template.getTemplate(addExtension(".xwt"));
43                   return t == null ? null : t.getStatic(null);  /** FIXME VERY BAD! */
44               } catch (Exception e) {
45                   Log.info(this, e);
46                   return null;
47               }
48           }
49           Object ret = refCache == null ? null : refCache.get(key);
50           if (ret != null) return ret;
51           ret = new Ref(this, key);
52           if (refCache == null) refCache = new Hash();
53           refCache.put(key, ret);
54           return ret;
55       }
56   
57   
58   
59       // Caching //////////////////////////////////////////////////////////////////////
60   
61       public static class NotCacheableException extends Exception { }
62       public static NotCacheableException notCacheable = new NotCacheableException();
63   
64       /** if it makes sense to cache a resource, the resource must return a unique key */
65       public String getCacheKey() throws NotCacheableException { throw notCacheable; }
66   
67       /** subclass from this if you want a CachedInputStream for each path */
68       public static class CachedRes extends Res {
69           private Res parent;
70           private boolean disk = false;
71           private String key;
72           public String getCacheKey() throws NotCacheableException { return key; }
73           public String toString() { return key; }
74           private Hash cachedInputStreams = new Hash();
75           public CachedRes(Res p, String s, boolean d) throws NotCacheableException {
76               this.parent = p; this.disk = d; this.key = p.getCacheKey();
77           }
78           public InputStream getInputStream(String path) throws IOException {
79               CachedInputStream cis = (CachedInputStream)cachedInputStreams.get(path);
80               if (cis == null) {
81                   if (disk) {
82                       java.io.File f = LocalStorage.Cache.getCacheFileForKey(key);
83                       if (f.exists()) return new FileInputStream(f);
84                       cis = new CachedInputStream(parent.getInputStream(path), f);
85                   } else {
86                       cis = new CachedInputStream(parent.getInputStream(path));
87                   }
88                   cachedInputStreams.put(path, cis);
89               }
90               return cis.getInputStream();
91           }
92       }
93   
94   
95       // Useful Subclasses //////////////////////////////////////////////////////////////////////
96   
97       /** HTTP or HTTPS resource */
98       public static class HTTP extends Res {
99           private String url;
100          HTTP(String url) { while (url.endsWith("/")) url = url.substring(0, url.length() - 1); this.url = url; }
101          public String toString() { return url; }
102          public String getCacheKey() throws NotCacheableException { return url; }
103          public InputStream getInputStream(String path) throws IOException { return new org.xwt.HTTP(url + path).GET(); }
104      }
105  
106      /** byte arrays */
107      public static class ByteArray extends Res {
108          private byte[] bytes;
109          private String cacheKey = null;
110          ByteArray(byte[] bytes, String cacheKey) { this.bytes = bytes; this.cacheKey = cacheKey; }
111          public String toString() { return "byte[]"; }
112          public String getCacheKey() throws NotCacheableException { return cacheKey; }
113          public InputStream getInputStream(String path) throws IOException {
114              if (!"".equals(path)) throw new IOException("can't get subresources of a byte[] resource");
115              return new ByteArrayInputStream(bytes);
116          }
117      }
118  
119      /** a file */
120      public static class File extends Res {
121          private String path;
122          File(String path) {
123              while (path.endsWith(java.io.File.separatorChar + "")) path = path.substring(0, path.length() - 1);
124              this.path = path;
125          }
126          public String toString() { return "file:" + path; }
127          public String getCacheKey() throws NotCacheableException { throw notCacheable; }  // already on the disk!
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 toString() { return parent.toString() + "!zip"; }
137          public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!zip:"; }
138          public InputStream getInputStream(String path) throws IOException {
139              if (path.startsWith("/")) path = path.substring(1);
140              InputStream pis = parent.getInputStream();
141              ZipInputStream zis = new ZipInputStream(pis);
142              ZipEntry ze = zis.getNextEntry();
143              while(ze != null && !ze.getName().equals(path)) ze = zis.getNextEntry();
144              if (ze == null) throw new IOException("requested file (" + path + ") not found in archive");
145              return new KnownLength.KnownLengthInputStream(zis, (int)ze.getSize());
146          }
147      }
148  
149      /** "unwrap" a Cab archive */
150      public static class Cab extends Res {
151          private Res parent;
152          Cab(Res parent) { this.parent = parent; }
153          public String toString() { return parent.toString() + "!cab"; }
154          public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "!cab:"; }
155          public InputStream getInputStream(String path) throws IOException {
156              if (path.startsWith("/")) path = path.substring(1);
157              return new org.xwt.translators.MSPack(parent.getInputStream()).getInputStream(path);
158          }
159      }
160  
161      /** the Builtin resource */
162      public static class Builtin extends Res {
163          public Builtin() { };
164          public String getCacheKey() throws NotCacheableException { throw notCacheable; }    // not cacheable
165          public String toString() { return "builtin:"; }
166          public InputStream getInputStream(String path) throws IOException {
167              if (!path.equals("")) throw new IOException("the builtin resource has no subresources");
168              return Platform.getBuiltinInputStream();
169          }
170      }
171  
172      /** what you get when you reference a subresource */
173      public static class Ref extends Res {
174          Res parent;
175          Object key;
176          public String toString() { return parent.toString() + "/" + key; }
177          Ref(Res parent, Object key) { this.parent = parent; this.key = key; }
178          public String getCacheKey() throws NotCacheableException { return parent.getCacheKey() + "/" + key; }
179          public Res addExtension(String extension) {
180              return ((String)key).endsWith(extension) ? this : new Ref(parent, key + extension); }
181          public InputStream getInputStream(String path) throws IOException { return parent.getInputStream("/" + key + path); }
182      }
183  
184      /** provides redirection of a specified key */
185      public static class Graft extends Res {
186          Res graftee;
187          ObjectObjectreplaced_keyreplaced_val       Graft(Res graftee, Object key, Object val) { this.graftee = graftee; this.replaced_key = key; this.replaced_val = val; }
188          public boolean equals(Object o) { return this == o || graftee.equals(o); }
189          public int hashCode() { return graftee.hashCode(); }
190          public InputStream getInputStream(String s) throws IOException { return graftee.getInputStream(s); }
191          public Object get(Object key) throws JSExn { return replaced_key.equals(key) ? replaced_val : graftee.get(key); }
192          public Object callMethod(Object name, Object a, Object b, Object c, Object[] rest, int nargs) throws JSExn {
193              if (replaced_key.equals(name)) {
194                  if (replaced_val instanceof JS) return ((JS)replaced_val).call(a, b, c, rest, nargs);
195                  else throw new JSExn("attempted to call non-function (class="+replaced_val.getClass()+")");
196              } else {
197                  return graftee.callMethod(name, a, b, c, rest, nargs);
198              }
199          }
200          public Number coerceToNumber() { return graftee.coerceToNumber(); }
201          public String coerceToString() { return graftee.coerceToString(); }
202          public boolean coerceToBoolean() { return graftee.coerceToBoolean(); }
203          public String typeName() { return graftee.typeName(); }
204      }
205  
206      /** shadow resource which replaces the graft */
207      public static class ProgressWatcher extends Res {
208          final Res watchee;
209          JSFunction callback;
210          ProgressWatcher(Res watchee, JSFunction callback) { this.watchee = watchee; this.callback = callback; }
211          public String toString() { return watchee.toString(); }
212          public String getCacheKey() throws NotCacheableException { return watchee.getCacheKey(); }
213          public InputStream getInputStream(String s) throws IOException {
214              final InputStream is = watchee.getInputStream(s);
215              return new FilterInputStream(is) {
216                      int bytesDownloaded = 0;
217                      public int read() throws IOException {
218                          int ret = super.read();
219                          if (ret != -1) bytesDownloaded++;
220                          return ret;
221                      }
222                      public int read(byte[] b, int off, int len) throws IOException {
223                          int ret = super.read(b, off, len);
224                          if (ret != 1) bytesDownloaded += ret;
225                          Scheduler.add(new Scheduler.Task() { public void perform() throws Exception {
226                              callback.call(N(bytesDownloaded),
227                                            N(is instanceof KnownLength ? ((KnownLength)is).getLength() : 0), null, null, 2);
228                          } });
229                          return ret;
230                      }
231                  };
232          }
233      }
234  }
235