2002/10/15 20:48:08
[org.ibex.core.git] / src / org / xwt / Resources.java
1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
2 package org.xwt;
3
4 import java.io.*;
5 import java.net.*;
6 import java.util.*;
7 import java.util.zip.*;
8 import java.lang.*;
9 import java.applet.*;
10 import org.mozilla.javascript.*;
11 import org.xwt.util.*;
12
13 /**
14  *  A singleton class that acts as a repository for files obtained
15  *  from xwar archives or the local filesystem.
16  *
17  *  All names are converted to resource names (dots instead of
18  *  slashes) when they are loaded into this repository; however,
19  *  filename extensions are left on, so queries (resolveResource(),
20  *  getResource()) should include the extension when querying for
21  *  resources.
22  */
23 public class Resources {
24
25     /** Holds resources added at runtime. Initialized to hold 2000 to work around a NetscapeJVM bug. */
26     private static Hash bytes = new Hash(2000, 3);
27
28     /** keeps track of which archive loaded templates into which package */
29     private static Hash usedPackages = new Hash();
30
31     /** Returns true iff <tt>name</tt> is a valid resource name */
32     private static boolean validResourceName(String name) {
33         if (name == null || name.equals("")) return true;
34         if (name.endsWith("/box.xwt") || name.endsWith("/svg.xwt")) return false;
35         if (name.equals("box.xwt") || name.equals("svg.xwt")) return false;
36         if (!((name.charAt(0) >= 'A' && name.charAt(0) <= 'Z') ||
37               (name.charAt(0) >= 'a' && name.charAt(0) <= 'z'))) return false;
38         for(int i=1; i<name.length(); i++) {
39             char c = name.charAt(i);
40             if (!((c >= 'A' && c <= 'Z') ||
41                   (c >= 'a' && c <= 'z') ||
42                   c == '_' ||
43                   (c >= '0' && c <= '9') ||
44                   (c == '.' && i == name.length() - 4))) return false;
45         }
46         return true;
47     }
48
49     /** Load a directory as if it were an archive */
50     public static synchronized void loadDirectory(File dir) throws IOException { loadDirectory(dir, ""); }
51     private static synchronized void loadDirectory(File dir, String prefix) throws IOException {
52         new Static(prefix.replace(File.separatorChar, '.'));
53         String[] subfiles = dir.list();
54         for(int i=0; i<subfiles.length; i++) {
55             if (subfiles[i].equals("CVS") || !validResourceName(subfiles[i])) continue;
56             String name = prefix + subfiles[i];
57             File file = new File(dir.getPath() + File.separatorChar + subfiles[i]);
58             if (file.isDirectory()) {
59                 loadDirectory(file, name + File.separatorChar);
60             } else {
61                 if (name.endsWith(".xwt")) {
62                     String name2 = name.substring(0, name.length() - 4);
63                     Static.createStatic(name2.replace(File.separatorChar, '.'), false);
64                     usedPackages.put(JSObject.nodeNameToPackageName(name2.replace('/', '.')), new Object());
65                 }
66                 bytes.put(name.replace(File.separatorChar, '.'), file);
67             }
68         }
69     }
70
71     /** Load an archive from an inputstream. */
72     public static synchronized void loadArchive(InputStream is) throws IOException { loadArchive(is, 0, null); }
73     public static synchronized void loadArchive(InputStream is, final int length, final Function callback) throws IOException {
74
75         // random placeholder
76         Object thisArchive = new Object();
77
78         ZipInputStream zis = new ZipInputStream(new FilterInputStream(is) {
79                 int bytesDownloaded = 0;
80                 boolean clear = true;
81                 public int read() throws IOException {
82                     bytesDownloaded++;
83                     return super.read();
84                 }
85                 public int read(byte[] b, int off, int len) throws IOException {
86                     int ret = 0;
87
88                     // Ugly hack to work around libgcj zlib bug -- always try to fill the buffer completely
89                     while (len > 0) {
90                         int read = super.read(b, off, len);
91                         if (read == -1) break;
92                         ret += read;
93                         len -= read;
94                         off += read;
95                     }
96
97                     if (clear && callback != null) {
98                         clear = false;
99                         ThreadMessage.newthread(new JSObject.JSFunction() {
100                                 public Object call(Context cx, Scriptable thisObj, Scriptable ctorObj, Object[] args) throws JavaScriptException {
101                                     try {
102                                         callback.call(cx, null, null, new Object[] { new Double(bytesDownloaded), new Double(length) });
103                                     } finally {
104                                         clear = true;
105                                     }
106                                     return null;
107                                 }                            
108                             });
109                     }
110
111                     bytesDownloaded += ret;
112                     return ret;
113                 }
114             });
115
116         for(ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
117             String name = ze.getName();
118             if (Log.on) Log.log(Resources.class, name);
119             if (!validResourceName(name.substring(name.lastIndexOf('/') + 1))) {
120                 if (Log.on) Log.log(Resources.class, "WARNING: ignoring xwar entry with invalid name: " + name);
121                 continue;
122             }
123
124             if (name.endsWith(".xwt")) {
125                 // placeholder so resolveResource() works properly
126                 bytes.put(name.replace('/', '.'), new byte[] { });
127                 name = name.substring(0, name.length() - 4);
128
129                 String packageName = JSObject.nodeNameToPackageName(name.replace('/', '.'));
130                 Object user = usedPackages.get(packageName);
131                 if (user != null && user != thisArchive) {
132                     if (Log.on) Log.log(Resources.class, "templates have already been loaded into " + packageName + "; refusing to load " + name);
133                 } else {
134                     usedPackages.put(packageName, thisArchive);
135                     Static.createStatic(name.replace('/', '.'), false);
136                     Template.buildTemplate(zis, name.replace('/', '.'));
137                 }
138
139             } else {
140                 bytes.put(name.replace('/', '.'), isToByteArray(zis));
141             }
142         }
143         if (Log.verbose) Log.log(Resources.class, "done loading archive");
144     }
145
146     /** holds the current theme mappings */
147     static Vector mapFrom = new Vector();
148
149     /** holds the current theme mappings */
150     static Vector mapTo = new Vector();
151
152     /**
153      *  Resolves the partial resource name <tt>name</tt> to a fully
154      *  resolved resource name, using <tt>importlist</tt> as a search
155      *  list, or null if no resource was found.
156      *
157      *  Both the arguments and return values from this function SHOULD
158      *  include extensions (".xwt", ".xwf", etc) and SHOULD use dots
159      *  (".") instead of slashes ("/").
160      */
161     public static String resolve(String name, String[] importlist) {
162         final int imax = importlist == null ? 0 : importlist.length;
163         for(int i=-1; i < imax; i++) {
164             String resolved = i == -1 ? name : (importlist[i] + '.' + name);
165             for(int j=mapFrom.size() - 1; j>=0; j--) {
166                 String from = mapFrom.elementAt(j).toString();
167                 if (resolved.startsWith(from) && (resolved.endsWith(".xwt") || resolved.endsWith(".xwf"))) {
168                     String tryme = mapTo.elementAt(j) + resolved.substring(from.length());
169                     if (bytes.get(tryme) != null) return tryme;
170                 }
171             }
172             if (bytes.get(resolved) != null) return resolved;
173         }
174         return null;
175     }
176
177     /** Returns the named resource as a byte[].
178      *  @param name A fully resolved resource name, using slashes
179      *              instead of periods. If it is null, this function
180      *              will return null.
181      */
182     public static byte[] getResource(String name) {
183         if (name == null) return null;
184         synchronized(bytes) {
185             Object o = bytes.get(name);
186             if (o == null) return null;
187             if (o instanceof byte[]) return ((byte[])o);
188             if (o instanceof File) {
189                 try {
190                     FileInputStream fi = new FileInputStream((File)o);
191                     byte[] b = isToByteArray(fi);
192                     bytes.put(name, b);
193                     return b;
194                 } catch (Exception e) {
195                     if (Log.on) Log.log(Resources.class, "Exception while reading from file " + o);
196                     if (Log.on) Log.log(Resources.class, e);
197                     return null;
198                 }
199             }
200             return null;
201         }
202     }
203     
204     /** scratch space for isToByteArray() */
205     private static byte[] workspace = new byte[16 * 1024];
206
207     /** Trivial method to completely read an InputStream */
208     public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
209         int pos = 0;
210         while (true) {
211             int numread = is.read(workspace, pos, workspace.length - pos);
212             if (numread == -1) break;
213             else if (pos + numread < workspace.length) pos += numread;
214             else {
215                 pos += numread;
216                 byte[] temp = new byte[workspace.length * 2];
217                 System.arraycopy(workspace, 0, temp, 0, workspace.length);
218                 workspace = temp;
219             }
220         }
221         byte[] ret = new byte[pos];
222         System.arraycopy(workspace, 0, ret, 0, pos);
223         return ret;
224     }
225 }
226
227
228