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