JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / javajs / util / CompoundDocument.java
1 /* $RCSfile$\r
2  * $Author: egonw $\r
3  * $Date: 2006-03-18 15:59:33 -0600 (Sat, 18 Mar 2006) $\r
4  * $Revision: 4652 $\r
5  *\r
6  * Copyright (C) 2003-2005  Miguel, Jmol Development, www.jmol.org\r
7  *\r
8  * Contact: hansonr@stolaf.edu\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 St, Fifth Floor, Boston, MA 02110-1301 USA.\r
23  */\r
24 package javajs.util;\r
25 \r
26 \r
27 import java.io.DataInputStream;\r
28 import java.io.BufferedInputStream;\r
29 \r
30 \r
31 import java.util.Map;\r
32 \r
33 import javajs.api.GenericZipTools;\r
34 \r
35 \r
36 \r
37 /* a simple compound document reader. \r
38  * See http://sc.openoffice.org/compdocfileformat.pdf\r
39  * \r
40  * random access file info: \r
41  * http://java.sun.com/docs/books/tutorial/essential/io/rafs.html\r
42  * \r
43  * SHOOT! random access is only for applications, not applets!\r
44  * \r
45  * With a bit more work, this could be set up to deliver binary files, but\r
46  * right now I've only implemented it for string-based data. All Jmol is using\r
47  * is getAllData().\r
48  * \r
49  */\r
50 \r
51 public class CompoundDocument extends BinaryDocument{\r
52 \r
53 //  RandomAccessFile file;\r
54   CompoundDocHeader header = new CompoundDocHeader(this);\r
55   Lst<CompoundDocDirEntry> directory = new  Lst<CompoundDocDirEntry>();\r
56   CompoundDocDirEntry rootEntry;\r
57 \r
58   int[] SAT;\r
59   int[] SSAT;\r
60   int sectorSize;\r
61   int shortSectorSize;\r
62   int nShortSectorsPerStandardSector;\r
63   int nIntPerSector;\r
64   int nDirEntriesperSector;\r
65 \r
66   // called by reflection\r
67   \r
68   public CompoundDocument(){\r
69     super();\r
70     this.isBigEndian = true;\r
71   }\r
72   \r
73   @Override\r
74   public void setStream(GenericZipTools jzt, BufferedInputStream bis, boolean isBigEndian) {\r
75     // isBigEndian is ignored here; it must be true\r
76     /*    try {\r
77      file = new RandomAccessFile(fileName, "r");\r
78      isRandom = true;\r
79      } catch (Exception e) {\r
80      // probably an applet\r
81      }\r
82      */\r
83     this.jzt = jzt;\r
84     if (!isRandom) {\r
85       stream = new DataInputStream(bis);\r
86     }\r
87     stream.mark(Integer.MAX_VALUE);\r
88     if (!readHeader())\r
89       return;\r
90     getSectorAllocationTable();\r
91     getShortSectorAllocationTable();\r
92     getDirectoryTable();\r
93   }\r
94 \r
95   public Lst<CompoundDocDirEntry> getDirectory() {\r
96     return directory;\r
97   }\r
98 \r
99   public String getDirectoryListing(String separator) {\r
100     String str = "";\r
101     for (int i = 0; i < directory.size(); i++) {\r
102       CompoundDocDirEntry thisEntry = directory.get(i);\r
103       if (!thisEntry.isEmpty)\r
104         str += separator\r
105             + thisEntry.entryName\r
106             + "\tlen="\r
107             + thisEntry.lenStream\r
108             + "\tSID="\r
109             + thisEntry.SIDfirstSector\r
110             + (thisEntry.isStandard ? "\tfileOffset="\r
111                 + getOffset(thisEntry.SIDfirstSector) : "");\r
112     }\r
113     return str;\r
114   }\r
115 \r
116   SB data;\r
117   \r
118   public SB getAllData() {\r
119     return getAllDataFiles(null, null);\r
120   }\r
121 \r
122   /**\r
123    * reads a compound document directory and saves all data in a Hashtable\r
124    * so that the files may be organized later in a different order. Also adds\r
125    * a #Directory_Listing entry.\r
126    * \r
127    * Files are bracketed by BEGIN Directory Entry and END Directory Entry lines, \r
128    * similar to ZipUtil.getAllData.\r
129    * \r
130    * @param prefix\r
131    * @param binaryFileList   |-separated list of files that should be saved\r
132    *                         as xx xx xx hex byte strings. The directory listing\r
133    *                         is appended with ":asBinaryString"\r
134    * @param fileData\r
135    */\r
136   @Override\r
137   public void getAllDataMapped(String prefix, \r
138                          String binaryFileList, Map<String, String> fileData) {\r
139     fileData.put("#Directory_Listing", getDirectoryListing("|"));\r
140     binaryFileList = "|" + binaryFileList + "|";\r
141     for (int i = 0; i < directory.size(); i++) {\r
142       CompoundDocDirEntry thisEntry = directory.get(i);\r
143       if (!thisEntry.isEmpty && thisEntry.entryType != 5) {\r
144         String name = thisEntry.entryName;\r
145         System.out.println("CompoundDocument file " + name);\r
146         boolean isBinary = (binaryFileList.indexOf("|" + name + "|") >= 0);\r
147         if (isBinary)\r
148           name += ":asBinaryString";\r
149         SB data = new SB();\r
150         data.append("BEGIN Directory Entry ").append(name).append("\n"); \r
151         data.appendSB(getEntryAsString(thisEntry, isBinary));\r
152         data.append("\nEND Directory Entry ").append(name).append("\n");\r
153         fileData.put(prefix + "/" + name, data.toString());\r
154       }\r
155     }\r
156     close();\r
157   }\r
158 \r
159   @Override\r
160   public SB getAllDataFiles(String binaryFileList, String firstFile) {\r
161     if (firstFile != null) {\r
162       for (int i = 0; i < directory.size(); i++) {\r
163         CompoundDocDirEntry thisEntry = directory.get(i);\r
164         if (thisEntry.entryName.equals(firstFile)) {\r
165           directory.remove(i);\r
166           directory.add(1, thisEntry); // after ROOT_ENTRY\r
167           break;\r
168         }\r
169       }\r
170     }\r
171     data = new SB();\r
172     data.append("Compound Document File Directory: ");\r
173     data.append(getDirectoryListing("|"));\r
174     data.append("\n");\r
175     binaryFileList = "|" + binaryFileList + "|";\r
176     for (int i = 0; i < directory.size(); i++) {\r
177       CompoundDocDirEntry thisEntry = directory.get(i);\r
178       //System.out.println("CompoundDocument reading " + thisEntry.entryName);\r
179       if (!thisEntry.isEmpty && thisEntry.entryType != 5) {\r
180         String name = thisEntry.entryName;\r
181         if (name.endsWith(".gz"))\r
182           name = name.substring(0, name.length() - 3);\r
183         data.append("BEGIN Directory Entry ").append(name).append("\n");            \r
184         data.appendSB(getEntryAsString(thisEntry, binaryFileList.indexOf("|" + thisEntry.entryName + "|") >= 0));\r
185         data.append("\n");\r
186         data.append("END Directory Entry ").append(thisEntry.entryName).append("\n");            \r
187       }\r
188     }\r
189     close();\r
190     return data;\r
191   }\r
192 \r
193   public SB getFileAsString(String entryName) {\r
194     for (int i = 0; i < directory.size(); i++) {\r
195       CompoundDocDirEntry thisEntry = directory.get(i);\r
196       if (thisEntry.entryName.equals(entryName))\r
197         return getEntryAsString(thisEntry, false);\r
198     }\r
199     return new SB();\r
200   }\r
201 \r
202   private long getOffset(int SID) {\r
203     return (SID + 1) * sectorSize;\r
204   }\r
205 \r
206   private void gotoSector(int SID) {\r
207     seek(getOffset(SID));\r
208   }\r
209 \r
210   private boolean readHeader() {\r
211     if (!header.readData())\r
212       return false;\r
213     sectorSize = 1 << header.sectorPower;\r
214     shortSectorSize = 1 << header.shortSectorPower;\r
215     nShortSectorsPerStandardSector = sectorSize / shortSectorSize; // e.g. 512 / 64 = 8\r
216     nIntPerSector = sectorSize / 4; // e.g. 512 / 4 = 128\r
217     nDirEntriesperSector = sectorSize / 128; // e.g. 512 / 128 = 4\r
218 //    System.out.println(\r
219 //          "compound document: revNum=" + header.revNumber +\r
220 //          " verNum=" + header.verNumber + " isBigEndian=" + isBigEndian +\r
221 //          " bytes per standard/short sector=" + sectorSize + "/" + shortSectorSize);\r
222     return true;\r
223   }\r
224 \r
225   private void getSectorAllocationTable() {\r
226     int nSID = 0;\r
227     int thisSID;\r
228     SAT = new int[header.nSATsectors * nIntPerSector + 109];\r
229 \r
230     try {\r
231       for (int i = 0; i < 109; i++) {\r
232         thisSID = header.MSAT0[i];\r
233         if (thisSID < 0)\r
234           break;\r
235         gotoSector(thisSID);\r
236         for (int j = 0; j < nIntPerSector; j++) {\r
237           SAT[nSID++] = readInt();\r
238           //Logger.debug(thisSID+"."+j + "/" + (nSID - 1) + " : " + SAT[nSID - 1]);\r
239         }\r
240       }\r
241       int nMaster = header.nAdditionalMATsectors;\r
242       thisSID = header.SID_MSAT_next;\r
243       int[] MSAT = new int[nIntPerSector];\r
244       out: while (nMaster-- > 0 && thisSID >= 0) {\r
245         // read a page of sector identifiers pointing to SAT sectors\r
246         gotoSector(thisSID);\r
247         for (int i = 0; i < nIntPerSector; i++)\r
248           MSAT[i] = readInt();\r
249         // read each page of SAT sector identifiers \r
250         // last entry is pointer to next master sector allocation table page\r
251         for (int i = 0; i < nIntPerSector - 1; i++) {\r
252           thisSID = MSAT[i];\r
253           if (thisSID < 0)\r
254             break out;\r
255           gotoSector(thisSID);\r
256           for (int j = nIntPerSector; --j >= 0;)\r
257             SAT[nSID++] = readInt();\r
258         }\r
259         thisSID = MSAT[nIntPerSector - 1];\r
260       }\r
261     } catch (Exception e) {\r
262       System.out.println(e.toString());\r
263     }\r
264   }\r
265 \r
266   private void getShortSectorAllocationTable() {\r
267     int nSSID = 0;\r
268     int thisSID = header.SID_SSAT_start;\r
269     int nMax = header.nSSATsectors * nIntPerSector;\r
270     SSAT = new int[nMax];\r
271     try {\r
272       while (thisSID > 0 && nSSID < nMax) {\r
273         gotoSector(thisSID);\r
274         for (int j = 0; j < nIntPerSector; j++) {\r
275           SSAT[nSSID++] = readInt();\r
276           //System.out.println("short: " + thisSID+"."+j+" SSID=" +(nSSID-1)+" "+SSAT[nSSID-1]);\r
277         }\r
278         thisSID = SAT[thisSID];\r
279       }\r
280     } catch (Exception e) {\r
281       System.out.println(e.toString());\r
282     }\r
283   }\r
284 \r
285   private void getDirectoryTable() {\r
286     int thisSID = header.SID_DIR_start;\r
287     CompoundDocDirEntry thisEntry;\r
288     rootEntry = null;\r
289     try {\r
290       while (thisSID > 0) {\r
291         gotoSector(thisSID);\r
292         for (int j = nDirEntriesperSector; --j >= 0;) {\r
293           thisEntry = new CompoundDocDirEntry(this);\r
294           thisEntry.readData();\r
295           if (thisEntry.lenStream > 0) {\r
296             directory.addLast(thisEntry);\r
297             //System.out.println(thisEntry.entryName);\r
298           }\r
299           if (thisEntry.entryType == 5)\r
300             rootEntry = thisEntry;\r
301         }\r
302         thisSID = SAT[thisSID];\r
303       }\r
304     } catch (Exception e) {\r
305       System.out.println(e.toString());\r
306     }\r
307 //    System.out.println("CompoundDocument directory entry: \n"\r
308 //        + getDirectoryListing("\n"));\r
309   }\r
310 \r
311   private SB getEntryAsString(CompoundDocDirEntry thisEntry, boolean asBinaryString) {\r
312     if(thisEntry.isEmpty)\r
313       return new SB();\r
314     //System.out.println(thisEntry.entryName + " " + thisEntry.entryType + " " + thisEntry.lenStream + " " + thisEntry.isStandard + " " + thisEntry.SIDfirstSector);\r
315     return (thisEntry.isStandard ? getStandardStringData(\r
316             thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString)\r
317             : getShortStringData(thisEntry.SIDfirstSector, thisEntry.lenStream, asBinaryString));\r
318   }\r
319   private SB getStandardStringData(int thisSID, int nBytes,\r
320                                              boolean asBinaryString) {\r
321     SB data = new SB();\r
322     byte[] byteBuf = new byte[sectorSize];\r
323     ZipData gzipData = new ZipData(nBytes);\r
324     try {\r
325       while (thisSID > 0 && nBytes > 0) {\r
326         gotoSector(thisSID);\r
327         nBytes = getSectorData(data, byteBuf, sectorSize, nBytes, asBinaryString, gzipData);\r
328         thisSID = SAT[thisSID];\r
329       }\r
330       if (nBytes == -9999)\r
331         return new SB();\r
332     } catch (Exception e) {\r
333       System.out.println(e.toString());\r
334     }\r
335     if (gzipData.isEnabled)\r
336       gzipData.addTo(jzt, data);\r
337     return data;\r
338   }\r
339 \r
340   private int getSectorData(SB data, byte[] byteBuf,\r
341                             int nSectorBytes, int nBytes, \r
342                             boolean asBinaryString, ZipData gzipData)\r
343       throws Exception {\r
344     readByteArray(byteBuf, 0, byteBuf.length);\r
345     int n = gzipData.addBytes(byteBuf, nSectorBytes, nBytes);\r
346     if (n >= 0)\r
347       return n;\r
348     if (asBinaryString) {\r
349       for (int i = 0; i < nSectorBytes; i++) {\r
350         data.append(Integer.toHexString(byteBuf[i] & 0xFF)).appendC(' ');\r
351         if (--nBytes < 1)\r
352           break;\r
353       }\r
354     } else {\r
355       for (int i = 0; i < nSectorBytes; i++) {\r
356         if (byteBuf[i] == 0)\r
357           return -9999; // don't allow binary data\r
358         data.appendC((char) byteBuf[i]);\r
359         if (--nBytes < 1)\r
360           break;\r
361       }\r
362     }\r
363     return nBytes;\r
364   }\r
365 \r
366   private SB getShortStringData(int shortSID, int nBytes, boolean asBinaryString) {\r
367     SB data = new SB();\r
368     if (rootEntry == null)\r
369       return data;\r
370     int thisSID = rootEntry.SIDfirstSector;\r
371     int ptShort = 0;\r
372     byte[] byteBuf = new byte[shortSectorSize];\r
373     ZipData gzipData = new ZipData(nBytes);\r
374     try {\r
375       //System.out.println("CD shortSID=" + shortSID);\r
376       // point to correct short data sector, 512/64 = 4 per page\r
377       while (thisSID >= 0 && shortSID >= 0 && nBytes > 0) {\r
378         while (shortSID - ptShort >= nShortSectorsPerStandardSector) {\r
379           ptShort += nShortSectorsPerStandardSector;\r
380           thisSID = SAT[thisSID];\r
381         }\r
382         seek(getOffset(thisSID) + (shortSID - ptShort) * shortSectorSize);\r
383         nBytes = getSectorData(data, byteBuf, shortSectorSize, nBytes, asBinaryString, gzipData);\r
384         shortSID = SSAT[shortSID];\r
385         //System.out.println("CD shortSID=" + shortSID);\r
386       }\r
387     } catch (Exception e) {\r
388       System.out.println(data.toString());\r
389       System.out.println("reader error in CompoundDocument " + e.toString());\r
390     }\r
391     if (gzipData.isEnabled)\r
392       gzipData.addTo(jzt, data);\r
393     return data;\r
394   }  \r
395 }\r