1    // Copyright 2003 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      static {
136          mapFrom.addElement("xwt.standard");
137          mapTo.addElement("org.xwt.themes.monopoly");
138      }
139  
140      /**
141       *  Resolves the partial resource name <tt>name</tt> to a fully
142       *  resolved resource name, using <tt>importlist</tt> as a search
143       *  list, or null if no resource was found.
144       *
145       *  Both the arguments and return values from this function SHOULD
146       *  include extensions (".xwt", ".xwf", etc) and SHOULD use dots
147       *  (".") instead of slashes ("/").
148       */
149      public static String resolve(String name, String[] importlist) {
150          final int imax = importlist == null ? 0 : importlist.length;
151          for(int i=-1; i < imax; i++) {
152              String resolved = i == -1 ? name : (importlist[i] + '.' + name);
153              for(int j=mapFrom.size() - 1; j>=0; j--) {
154                  String from = mapFrom.elementAt(j).toString();
155                  if (resolved.startsWith(from) && (resolved.endsWith(".xwt") || resolved.endsWith(".xwf"))) {
156                      String tryme = mapTo.elementAt(j) + resolved.substring(from.length());
157                      if (bytes.get(tryme) != null) return tryme;
158                  }
159              }
160              if (bytes.get(resolved) != null) return resolved;
161          }
162          return null;
163      }
164  
165      /** Returns the named resource as a byte[].
166       *  @param name A fully resolved resource name, using slashes
167       *              instead of periods. If it is null, this function
168       *              will return null.
169       */
170      public static byte[] getResource(String name) {
171          if (name == null) return null;
172          synchronized(bytes) {
173              Object o = bytes.get(name);
174              if (o == null) return null;
175              if (o instanceof byte[]) return ((byte[])o);
176              if (o instanceof File) {
177                  try {
178                      FileInputStream fi = new FileInputStream((File)o);
179                      byte[] b = isToByteArray(fi);
180                      bytes.put(name, b);
181                      return b;
182                  } catch (Exception e) {
183                      if (Log.on) Log.log(Resources.class, "Exception while reading from file " + o);
184                      if (Log.on) Log.log(Resources.class, e);
185                      return null;
186                  }
187              }
188              return null;
189          }
190      }
191      
192      /** scratch space for isToByteArray() */
193      private static byte[] workspace = new byte[16 * 1024];
194  
195      /** Trivial method to completely read an InputStream */
196      public static synchronized byte[] isToByteArray(InputStream is) throws IOException {
197          int pos = 0;
198          while (true) {
199              int numread = is.read(workspace, pos, workspace.length - pos);
200              if (numread == -1) break;
201              else if (pos + numread < workspace.length) pos += numread;
202              else {
203                  pos += numread;
204                  byte[] temp = new byte[workspace.length * 2];
205                  System.arraycopy(workspace, 0, temp, 0, workspace.length);
206                  workspace = temp;
207              }
208          }
209          byte[] ret = new byte[pos];
210          System.arraycopy(workspace, 0, ret, 0, pos);
211          return ret;
212      }
213  }
214  
215  
216  
217