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