1 // Copyright 2002 Adam Megacz, see the COPYING file for licensing [GPL]
7 import java.util.zip.*;
10 import org.mozilla.javascript.*;
11 import org.xwt.util.*;
14 * A singleton class that acts as a repository for files obtained
15 * from xwar archives or the local filesystem.
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
23 public class Resources {
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);
28 /** keeps track of which archive loaded templates into which package */
29 private static Hash usedPackages = new Hash();
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') ||
43 (c >= '0' && c <= '9') ||
44 (c == '.' && i == name.length() - 4))) return false;
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);
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());
66 bytes.put(name.replace(File.separatorChar, '.'), file);
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 {
76 Object thisArchive = new Object();
78 ZipInputStream zis = new ZipInputStream(new FilterInputStream(is) {
79 int bytesDownloaded = 0;
81 public int read() throws IOException {
85 public int read(byte[] b, int off, int len) throws IOException {
88 // Ugly hack to work around libgcj zlib bug -- always try to fill the buffer completely
90 int read = super.read(b, off, len);
91 if (read == -1) break;
97 if (clear && callback != null) {
99 ThreadMessage.newthread(new JSObject.JSFunction() {
100 public Object call(Context cx, Scriptable thisObj, Scriptable ctorObj, Object[] args) throws JavaScriptException {
102 callback.call(cx, null, null, new Object[] { new Double(bytesDownloaded), new Double(length) });
111 bytesDownloaded += ret;
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);
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);
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);
134 usedPackages.put(packageName, thisArchive);
135 Static.createStatic(name.replace('/', '.'), false);
136 Template.buildTemplate(zis, name.replace('/', '.'));
140 bytes.put(name.replace('/', '.'), isToByteArray(zis));
143 if (Log.verbose) Log.log(Resources.class, "done loading archive");
146 /** holds the current theme mappings */
147 static Vector mapFrom = new Vector();
149 /** holds the current theme mappings */
150 static Vector mapTo = new Vector();
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.
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 ("/").
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;
172 if (bytes.get(resolved) != null) return resolved;
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
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) {
190 FileInputStream fi = new FileInputStream((File)o);
191 byte[] b = isToByteArray(fi);
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);
204 /** scratch space for isToByteArray() */
205 private static byte[] workspace = new byte[16 * 1024];
207 /** Trivial method to completely read an InputStream */
208 public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
211 int numread = is.read(workspace, pos, workspace.length - pos);
212 if (numread == -1) break;
213 else if (pos + numread < workspace.length) pos += numread;
216 byte[] temp = new byte[workspace.length * 2];
217 System.arraycopy(workspace, 0, temp, 0, workspace.length);
221 byte[] ret = new byte[pos];
222 System.arraycopy(workspace, 0, ret, 0, pos);