JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / javajs / util / ZipTools.java
1 /* $RCSfile$\r
2  * $Author$\r
3  * $Date$\r
4  * $Revision$\r
5  *\r
6  * Copyright (C) 2006  The Jmol Development Team\r
7  *\r
8  * Contact: jmol-developers@lists.sf.net\r
9  *\r
10  *  This library is free software; you can redistribute it and/or\r
11  *  modify it under the terms of the GNU Lesser General Public\r
12  *  License as published by the Free Software Foundation; either\r
13  *  version 2.1 of the License, or (at your option) any later version.\r
14  *\r
15  *  This library is distributed in the hope that it will be useful,\r
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
18  *  Lesser General Public License for more details.\r
19  *\r
20  *  You should have received a copy of the GNU Lesser General Public\r
21  *  License along with this library; if not, write to the Free Software\r
22  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA\r
23  *  02110-1301, USA.\r
24  */\r
25 \r
26 package javajs.util;\r
27 \r
28 import java.io.BufferedInputStream;\r
29 import java.io.IOException;\r
30 import java.io.InputStream;\r
31 import java.io.OutputStream;\r
32 \r
33 import javajs.J2SIgnoreImport;\r
34 import javajs.api.GenericZipInputStream;\r
35 import javajs.api.GenericZipTools;\r
36 import javajs.api.ZInputStream;\r
37 \r
38 import java.util.Map;\r
39 import java.util.zip.CRC32;\r
40 import java.util.zip.GZIPInputStream;\r
41 import java.util.zip.ZipEntry;\r
42 import java.util.zip.ZipInputStream;\r
43 import java.util.zip.ZipOutputStream;\r
44 \r
45 \r
46 /**\r
47  * Note the JSmol/HTML5 must use its own version of java.util.zip.ZipOutputStream.\r
48  * \r
49  */\r
50 @J2SIgnoreImport({ java.util.zip.ZipOutputStream.class })\r
51 public class ZipTools implements GenericZipTools {\r
52 \r
53   public ZipTools() {\r
54     // for reflection\r
55   }\r
56   \r
57   @Override\r
58   public ZInputStream newZipInputStream(InputStream is) {\r
59     return newZIS(is);\r
60   }\r
61 \r
62   @SuppressWarnings("resource")\r
63   private static ZInputStream newZIS(InputStream is) {\r
64     return (is instanceof ZInputStream ? (ZInputStream) is\r
65         : is instanceof BufferedInputStream ? new GenericZipInputStream(is)\r
66             : new GenericZipInputStream(new BufferedInputStream(is)));\r
67   }\r
68 \r
69   /**\r
70    * reads a ZIP file and saves all data in a Hashtable so that the files may be\r
71    * organized later in a different order. Also adds a #Directory_Listing entry.\r
72    * \r
73    * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines,\r
74    * similar to CompoundDocument.getAllData.\r
75    * \r
76    * @param is\r
77    * @param subfileList\r
78    * @param name0\r
79    *        prefix for entry listing\r
80    * @param binaryFileList\r
81    *        |-separated list of files that should be saved as xx xx xx hex byte\r
82    *        strings. The directory listing is appended with ":asBinaryString"\r
83    * @param fileData\r
84    */\r
85   @Override\r
86   public void getAllZipData(InputStream is, String[] subfileList,\r
87                                           String name0, String binaryFileList,\r
88                                           Map<String, String> fileData) {\r
89     ZipInputStream zis = (ZipInputStream) newZIS(is);\r
90     ZipEntry ze;\r
91     SB listing = new SB();\r
92     binaryFileList = "|" + binaryFileList + "|";\r
93     String prefix = PT.join(subfileList, '/', 1);\r
94     String prefixd = null;\r
95     if (prefix != null) {\r
96       prefixd = prefix.substring(0, prefix.indexOf("/") + 1);\r
97       if (prefixd.length() == 0)\r
98         prefixd = null;\r
99     }\r
100     try {\r
101       while ((ze = zis.getNextEntry()) != null) {\r
102         String name = ze.getName();\r
103         if (prefix != null && prefixd != null\r
104             && !(name.equals(prefix) || name.startsWith(prefixd)))\r
105           continue;\r
106         //System.out.println("ziputil: " + name);\r
107         listing.append(name).appendC('\n');\r
108         String sname = "|" + name.substring(name.lastIndexOf("/") + 1) + "|";\r
109         boolean asBinaryString = (binaryFileList.indexOf(sname) >= 0);\r
110         byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());\r
111         String str;\r
112         if (asBinaryString) {\r
113           str = getBinaryStringForBytes(bytes);\r
114           name += ":asBinaryString";\r
115         } else {\r
116           str = Rdr.fixUTF(bytes);\r
117         }\r
118         str = "BEGIN Directory Entry " + name + "\n" + str\r
119             + "\nEND Directory Entry " + name + "\n";\r
120         fileData.put(name0 + "|" + name, str);\r
121       }\r
122     } catch (Exception e) {\r
123     }\r
124     fileData.put("#Directory_Listing", listing.toString());\r
125   }\r
126 \r
127   private String getBinaryStringForBytes(byte[] bytes) {\r
128     SB ret = new SB();\r
129     for (int i = 0; i < bytes.length; i++)\r
130       ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');\r
131     return ret.toString();\r
132   }\r
133 \r
134   /**\r
135    * iteratively drills into zip files of zip files to extract file content or\r
136    * zip file directory. Also works with JAR files.\r
137    * \r
138    * Does not return "__MACOS" paths\r
139    * \r
140    * @param bis\r
141    * @param list\r
142    * @param listPtr\r
143    * @param asBufferedInputStream\r
144    *        for Pmesh\r
145    * @return directory listing or subfile contents\r
146    */\r
147   @Override\r
148   public Object getZipFileDirectory(BufferedInputStream bis, String[] list,\r
149                                     int listPtr, boolean asBufferedInputStream) {\r
150     SB ret;\r
151     if (list == null || listPtr >= list.length)\r
152       return getZipDirectoryAsStringAndClose(bis);\r
153     bis = Rdr.getPngZipStream(bis, true);\r
154     String fileName = list[listPtr];\r
155     ZipInputStream zis = new ZipInputStream(bis);\r
156     ZipEntry ze;\r
157     //System.out.println("fname=" + fileName);\r
158     try {\r
159       boolean isAll = (fileName.equals("."));\r
160       if (isAll || fileName.lastIndexOf("/") == fileName.length() - 1) {\r
161         ret = new SB();\r
162         while ((ze = zis.getNextEntry()) != null) {\r
163           String name = ze.getName();\r
164           if (isAll || name.startsWith(fileName))\r
165             ret.append(name).appendC('\n');\r
166         }\r
167         String str = ret.toString();\r
168         return (asBufferedInputStream ? Rdr.getBIS(str.getBytes()) : str);\r
169       }\r
170       int pt = fileName.indexOf(":asBinaryString");\r
171       boolean asBinaryString = (pt > 0);\r
172       if (asBinaryString)\r
173         fileName = fileName.substring(0, pt);\r
174       fileName = fileName.replace('\\', '/');\r
175       while ((ze = zis.getNextEntry()) != null\r
176           && !fileName.equals(ze.getName())) {\r
177       }\r
178       byte[] bytes = (ze == null ? null : Rdr.getLimitedStreamBytes(zis,\r
179           ze.getSize()));\r
180       ze = null;\r
181       zis.close();\r
182       if (bytes == null)\r
183         return "";\r
184       if (Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes))\r
185         return getZipFileDirectory(Rdr.getBIS(bytes), list, ++listPtr,\r
186             asBufferedInputStream);\r
187       if (asBufferedInputStream)\r
188         return Rdr.getBIS(bytes);\r
189       if (asBinaryString) {\r
190         ret = new SB();\r
191         for (int i = 0; i < bytes.length; i++)\r
192           ret.append(Integer.toHexString(bytes[i] & 0xFF)).appendC(' ');\r
193         return ret.toString();\r
194       }\r
195       if (Rdr.isGzipB(bytes))\r
196         bytes = Rdr.getLimitedStreamBytes(getUnGzippedInputStream(bytes), -1);\r
197       return Rdr.fixUTF(bytes);\r
198     } catch (Exception e) {\r
199       return "";\r
200     }\r
201   }\r
202 \r
203   @Override\r
204   public byte[] getZipFileContentsAsBytes(BufferedInputStream bis,\r
205                                           String[] list, int listPtr) {\r
206     byte[] ret = new byte[0];\r
207     String fileName = list[listPtr];\r
208     if (fileName.lastIndexOf("/") == fileName.length() - 1)\r
209       return ret;\r
210     try {\r
211       bis = Rdr.getPngZipStream(bis, true);\r
212       ZipInputStream zis = new ZipInputStream(bis);\r
213       ZipEntry ze;\r
214       while ((ze = zis.getNextEntry()) != null) {\r
215         if (!fileName.equals(ze.getName()))\r
216           continue;\r
217         byte[] bytes = Rdr.getLimitedStreamBytes(zis, ze.getSize());\r
218         return ((Rdr.isZipB(bytes) || Rdr.isPngZipB(bytes)) && ++listPtr < list.length ? getZipFileContentsAsBytes(\r
219             Rdr.getBIS(bytes), list, listPtr) : bytes);\r
220       }\r
221     } catch (Exception e) {\r
222     }\r
223     return ret;\r
224   }\r
225   \r
226   @Override\r
227   public String getZipDirectoryAsStringAndClose(BufferedInputStream bis) {\r
228     SB sb = new SB();\r
229     String[] s = new String[0];\r
230     try {\r
231       s = getZipDirectoryOrErrorAndClose(bis, null);\r
232       bis.close();\r
233     } catch (Exception e) {\r
234       System.out.println(e.toString());\r
235     }\r
236     for (int i = 0; i < s.length; i++)\r
237       sb.append(s[i]).appendC('\n');\r
238     return sb.toString();\r
239   }\r
240 \r
241   @Override\r
242   public String[] getZipDirectoryAndClose(BufferedInputStream bis,\r
243                                                  String manifestID) {\r
244     String[] s = new String[0];\r
245     try {\r
246       s = getZipDirectoryOrErrorAndClose(bis, manifestID);\r
247       bis.close();\r
248     } catch (Exception e) {\r
249       System.out.println(e.toString());\r
250     }\r
251     return s;\r
252   }\r
253 \r
254   private String[] getZipDirectoryOrErrorAndClose(BufferedInputStream bis,\r
255                                                   String manifestID)\r
256       throws IOException {\r
257     bis = Rdr.getPngZipStream(bis, true);\r
258     Lst<String> v = new Lst<String>();\r
259     ZipInputStream zis = new ZipInputStream(bis);\r
260     ZipEntry ze;\r
261     String manifest = null;\r
262     while ((ze = zis.getNextEntry()) != null) {\r
263       String fileName = ze.getName();\r
264       if (manifestID != null && fileName.startsWith(manifestID))\r
265         manifest = getStreamAsString(zis);\r
266       else if (!fileName.startsWith("__MACOS")) // resource fork not nec.\r
267         v.addLast(fileName);\r
268     }\r
269     zis.close();\r
270     if (manifestID != null)\r
271       v.add(0, manifest == null ? "" : manifest + "\n############\n");\r
272     return v.toArray(new String[v.size()]);\r
273   }\r
274 \r
275   public static String getStreamAsString(InputStream is) throws IOException {\r
276     return Rdr.fixUTF(Rdr.getLimitedStreamBytes(is, -1));\r
277   }\r
278 \r
279   @Override\r
280   public InputStream newGZIPInputStream(InputStream is) throws IOException {\r
281     return new BufferedInputStream(new GZIPInputStream(is, 512));\r
282   }\r
283 \r
284   @Override\r
285   public BufferedInputStream getUnGzippedInputStream(byte[] bytes) {\r
286     try {\r
287       return Rdr.getUnzippedInputStream(this, Rdr.getBIS(bytes));\r
288     } catch (Exception e) {\r
289       return null;\r
290     }\r
291   }\r
292 \r
293   @Override\r
294   public void addZipEntry(Object zos, String fileName) throws IOException {\r
295     ((ZipOutputStream) zos).putNextEntry(new ZipEntry(fileName));\r
296   }\r
297 \r
298   @Override\r
299   public void closeZipEntry(Object zos) throws IOException {\r
300     ((ZipOutputStream) zos).closeEntry();\r
301   }\r
302 \r
303   @Override\r
304   public Object getZipOutputStream(Object bos) {\r
305     /**\r
306      * @j2sNative\r
307      * \r
308      *            return javajs.api.Interface.getInterface(\r
309      *            "java.util.zip.ZipOutputStream").setZOS(bos);\r
310      * \r
311      */\r
312     {\r
313       return new ZipOutputStream((OutputStream) bos);\r
314     }\r
315   }\r
316 \r
317   @Override\r
318   public int getCrcValue(byte[] bytes) {\r
319     CRC32 crc = new CRC32();\r
320     crc.update(bytes, 0, bytes.length);\r
321     return (int) crc.getValue();\r
322   }\r
323 \r
324   @Override\r
325   public void readFileAsMap(BufferedInputStream bis, Map<String, Object> bdata, String name) {\r
326     int pt = (name == null ? -1 : name.indexOf("|"));\r
327     name = (pt >= 0 ? name.substring(pt + 1) : null);\r
328     try {\r
329       if (Rdr.isPngZipStream(bis)) {\r
330         boolean isImage = "_IMAGE_".equals(name);\r
331         if (name == null || isImage)\r
332           bdata.put((isImage ? "_DATA_" : "_IMAGE_"), new BArray(getPngImageBytes(bis)));\r
333         if (!isImage)\r
334           cacheZipContents(bis, name, bdata, true);\r
335       } else if (Rdr.isZipS(bis)) {\r
336         cacheZipContents(bis, name, bdata, true);\r
337       } else if (name == null){\r
338         bdata.put("_DATA_", new BArray(Rdr.getLimitedStreamBytes(bis, -1)));\r
339       } else {\r
340         throw new IOException("ZIP file " + name + " not found");\r
341       }\r
342       bdata.put("$_BINARY_$", Boolean.TRUE);\r
343     } catch (IOException e) {\r
344       bdata.clear();\r
345       bdata.put("_ERROR_", e.getMessage());\r
346     }\r
347   }\r
348 \r
349   @Override\r
350   public String cacheZipContents(BufferedInputStream bis,\r
351                                         String fileName,\r
352                                         Map<String, Object> cache, \r
353                                         boolean asByteArray) {\r
354     ZipInputStream zis = (ZipInputStream) newZIS(bis);\r
355     ZipEntry ze;\r
356     SB listing = new SB();\r
357     long n = 0;\r
358     boolean oneFile = (asByteArray && fileName != null);\r
359     int pt = (oneFile ? fileName.indexOf("|") : -1);\r
360     String file0 = (pt >= 0 ? fileName : null);\r
361     if (pt >= 0)\r
362       fileName = fileName.substring(0,  pt);\r
363     try {\r
364       while ((ze = zis.getNextEntry()) != null) {\r
365         String name = ze.getName();\r
366         if (fileName != null) {\r
367           if (oneFile) {\r
368             if (!name.equalsIgnoreCase(fileName))\r
369               continue;\r
370           } else {\r
371             listing.append(name).appendC('\n');\r
372           }\r
373         }\r
374         long nBytes = ze.getSize();\r
375         byte[] bytes = Rdr.getLimitedStreamBytes(zis, nBytes);\r
376         if (file0 != null) {\r
377           readFileAsMap(Rdr.getBIS(bytes), cache, file0);\r
378           return null;\r
379         }\r
380         n += bytes.length;\r
381         Object o = (asByteArray ? new BArray(bytes) : bytes);        \r
382         cache.put((oneFile ? "_DATA_" : (fileName == null ? "" : fileName + "|") + name), o);\r
383         if (oneFile)\r
384           break;\r
385       }\r
386       zis.close();\r
387     } catch (Exception e) {\r
388       try {\r
389         zis.close();\r
390       } catch (IOException e1) {\r
391       }\r
392       return null;\r
393     }\r
394     if (n == 0 || fileName == null)\r
395       return null;\r
396     System.out.println("ZipTools cached " + n + " bytes from " + fileName);\r
397     return listing.toString();\r
398   }\r
399 \r
400   private static byte[] getPngImageBytes(BufferedInputStream bis) {\r
401     try {\r
402       if (Rdr.isPngZipStream(bis)) {\r
403         int pt_count[] = new int[2];\r
404         Rdr.getPngZipPointAndCount(bis, pt_count);\r
405         if (pt_count[1] != 0)\r
406           return deActivatePngZipB(Rdr.getLimitedStreamBytes(bis, pt_count[0]));\r
407       }\r
408       return Rdr.getLimitedStreamBytes(bis, -1);\r
409     } catch (IOException e) {\r
410       return null;\r
411     }\r
412   }\r
413 \r
414   /**\r
415    * Once a PNGJ image has been extracted, we want to red-line its\r
416    * iTXt "Jmol Type PNGJ" tag, since it is no longer associated with\r
417    * ZIP data.\r
418    *  \r
419    * @param bytes\r
420    * @return disfigured bytes\r
421    * \r
422    */\r
423   private static byte[] deActivatePngZipB(byte[] bytes) {\r
424     // \0PNGJ starting at byte 50 changed to \0 NGJ\r
425     if (Rdr.isPngZipB(bytes))\r
426       bytes[51] = 32;\r
427     return bytes;\r
428   }\r
429 \r
430 \r
431 \r
432 }\r