View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript-Excel: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2006 Open World Ltd, Kevin Rogers
4   //
5   // This file is part of MillScript-Excel.
6   //
7   // MillScript-Excel is free software; you can redistribute it and/or modify it under
8   // the terms of the GNU General Public License as published by the Free
9   // Software Foundation; either version 2 of the License, or (at your option)
10  // any later version.
11  //
12  // MillScript-Excel is distributed in the hope that it will be useful, but WITHOUT
13  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  // more details.
16  //
17  // You should have received a copy of the GNU General Public License along with
18  // MillScript-Excel; if not, write to the Free Software Foundation, Inc., 59 Temple
19  // Place, Suite 330, Boston, MA  02111-1307  USA
20  ////////////////////////////////////////////////////////////////////////////////
21  package org.millscript.office.compound;
22  
23  import org.millscript.commons.alert.alerts.IOAlert;
24  import org.millscript.office.endianness.BigEndianDecoder;
25  import org.millscript.office.endianness.EndianDecoder;
26  import org.millscript.office.endianness.LittleEndianDecoder;
27  
28  import java.io.IOException;
29  import java.io.RandomAccessFile;
30  
31  
32  public class Header {
33  
34      public static final byte[] COMPOUND_DOCUMENT_FILE_IDENT = new byte[] { -48, -49, 17, -32, -95, -79, 26, -31 };
35  
36      private final int directorySID;
37  
38      private final EndianDecoder endianDecoder;
39  
40      private final int[] masterSectorAllocationTable;
41  
42      private final int masterSectorAllocationTableSID;
43  
44      private final int minimumStandardStreamSize;
45  
46      private final int[] sectorAllocationTable;
47  
48      private final int sectorsInMasterSectorAllocationTable;
49  
50      private final int sectorsInSectorAllocationTable;
51  
52      private final int sectorsInShortSectorAllocationTable;
53  
54      private final int sectorSize;
55  
56      private final int[] shortSectorAllocationTable;
57  
58      private final int shortSectorAllocationTableSID;
59  
60      private final int shortSectorSize;
61  
62      public Header( final RandomAccessFile randomFile ) throws IOException {
63          // Validate the header identifies itself as a compound document
64          final byte[] header = new byte[ 512 ];
65          randomFile.readFully( header );
66          for ( int i = 0; i < 8; i++ ) {
67              if ( header[ i ] != COMPOUND_DOCUMENT_FILE_IDENT[ i ] ) {
68                  throw new IOAlert(
69                      "This is not a compound document"
70                  ).mishap();
71              }
72          }
73          // Discover the byte order
74          if ( header[ 28 ] == -2 && header[ 29 ] == -1 ) {
75              // Little-Endian
76              this.endianDecoder = new LittleEndianDecoder();
77          } else if ( header[ 28 ] == -1 && header[ 29 ] == -2 ) {
78              // Big-Endian
79              this.endianDecoder = new BigEndianDecoder();
80          } else {
81              // Ooops. The byte order identifier is invalid.
82              throw new IOAlert(
83                  "Invalid byte order identifier"
84              ).culprit(
85                  "first byte",
86                  header[ 28 ]
87              ).culprit(
88                  "second byte",
89                  header[ 29 ]
90              ).mishap();
91          }
92          // Extract the sector size
93          final int ssz = this.endianDecoder.decode2ByteInt( header, 30 );
94          if ( ssz < 7 ) {
95              throw new IOAlert(
96                  "Sector size cannot be less than 7(i.e. 128 bytes)"
97              ).culprit(
98                  "sector size",
99                  ssz
100             ).mishap();
101         } else {
102             this.sectorSize = 2 << ssz - 1;
103         }
104         // Extract the short sector size
105         final int sssz = this.endianDecoder.decode2ByteInt( header, 32 );
106         if ( sssz > ssz ) {
107             throw new IOAlert(
108                 "Short sector size cannot be larger than the normal sector size"
109             ).culprit(
110                 "short sector size",
111                 sssz
112             ).culprit(
113                 "sector size",
114                 ssz
115             ).mishap();
116         }
117         this.shortSectorSize = 2 << sssz - 1;
118         // Extract the number of sections in the SAT 
119         this.sectorsInSectorAllocationTable = this.endianDecoder.decode4ByteInt( header, 44 );
120         // Extract the SID of the directory
121         this.directorySID = this.endianDecoder.decode4ByteInt( header, 48 );
122         // Extract the minimum size for a standard stream
123         this.minimumStandardStreamSize = this.endianDecoder.decode4ByteInt( header, 56 );
124         // Extract the short-SAT SID
125         this.shortSectorAllocationTableSID = this.endianDecoder.decode4ByteInt( header, 60 );
126         // Extract the number of sectors in the short-SAT
127         this.sectorsInShortSectorAllocationTable = this.endianDecoder.decode4ByteInt( header, 64 );
128         // Extract the SID of the first sector of the master SAT
129         this.masterSectorAllocationTableSID = this.endianDecoder.decode4ByteInt( header, 68 );
130         // Extract the number of sectors in the master SAT
131         this.sectorsInMasterSectorAllocationTable = this.endianDecoder.decode4ByteInt( header, 72 );
132         // Read the master sector allocation table
133         this.masterSectorAllocationTable = this.readMasterSectorAllocationTable( randomFile, header );
134         // Read the sector allocation table
135         this.sectorAllocationTable = this.readSectorAllocationTable( randomFile );
136         // Read the short sector allocation table
137         this.shortSectorAllocationTable = this.readShortSectorAllocationTable( randomFile );
138     }
139 
140     /**
141      * @return Returns the directorySID.
142      */
143     public int getDirectorySID() {
144         return directorySID;
145     }
146 
147     /**
148      * @return Returns the endianDecoder.
149      */
150     public EndianDecoder getEndianDecoder() {
151         return endianDecoder;
152     }
153 
154     /**
155      * @return Returns the masterSectorAllocationTableSID.
156      */
157     public int getMasterSectorAllocationTableSID() {
158         return masterSectorAllocationTableSID;
159     }
160 
161     /**
162      * @return Returns the minimumStandardStreamSize.
163      */
164     public int getMinimumStandardStreamSize() {
165         return minimumStandardStreamSize;
166     }
167 
168     public int[] getSectorChain( final int sid ) {
169         // First calculate the number of sectors in the chain
170         int index = 1;
171         int currentSid = this.sectorAllocationTable[ sid ];
172         while ( currentSid != -2 ) {
173             switch ( currentSid ) {
174                 case -1:
175                     throw new IOAlert(
176                         "Unexpected end of chain in sector allocation table"
177                     ).mishap();
178                 case -3:
179                     throw new IOAlert(
180                         "Unexpected sector allocation table sector in chain"
181                     ).mishap();
182                 case -4:
183                     throw new IOAlert(
184                         "Unexpected master sector allocation table sector in chain"
185                     ).mishap();
186             }
187             index++;
188             currentSid = this.sectorAllocationTable[ currentSid ];
189         }
190         // Now construct the chain
191         final int[] chain = new int[ index ];
192         // Set the index
193         index = 0;
194         // Reset the current sector
195         currentSid = sid;
196         // Now fill in the chain
197         while ( currentSid != -2 ) {
198             chain[ index ] = currentSid;
199             index++;
200             // Get the next sector in the chain
201             currentSid = this.sectorAllocationTable[ currentSid ];
202         }
203         return chain;
204     }
205 
206     /**
207      * Returns the offset of the start of the sector with the given sector id.
208      *
209      * @param sid   the id of the sector whose offset is required
210      * @return  the offset of the specified sector within the file
211      */
212     public long getSectorOffset( final int sid ) {
213         return 512 + sid * this.sectorSize;
214     }
215 
216     /**
217      * @return Returns the sectorsInMasterSectorAllocationTable.
218      */
219     public int getSectorsInMasterSectorAllocationTable() {
220         return sectorsInMasterSectorAllocationTable;
221     }
222 
223     /**
224      * @return Returns the sectorsInSectorAllocationTable.
225      */
226     public int getSectorsInSectorAllocationTable() {
227         return sectorsInSectorAllocationTable;
228     }
229 
230     /**
231      * @return Returns the sectorsInShortSectorAllocationTable.
232      */
233     public int getSectorsInShortSectorAllocationTable() {
234         return sectorsInShortSectorAllocationTable;
235     }
236 
237     /**
238      * @return Returns the sectorSize.
239      */
240     public int getSectorSize() {
241         return sectorSize;
242     }
243 
244     /**
245      * @return Returns the shortSectorAllocationTableSID.
246      */
247     public int getShortSectorAllocationTableSID() {
248         return shortSectorAllocationTableSID;
249     }
250 
251     public int[] getShortSectorChain( final int sid ) {
252         // First calculate the number of sectors in the chain
253         int index = 1;
254         int currentSid = this.shortSectorAllocationTable[ sid ];
255         while ( currentSid != -2 ) {
256             switch ( currentSid ) {
257                 case -1:
258                     throw new IOAlert(
259                         "Unexpected end of chain in short sector allocation table"
260                     ).mishap();
261                 case -3:
262                     throw new IOAlert(
263                         "Unexpected sector allocation table sector in chain"
264                     ).mishap();
265                 case -4:
266                     throw new IOAlert(
267                         "Unexpected master sector allocation table sector in chain"
268                     ).mishap();
269             }
270             index++;
271             currentSid = this.shortSectorAllocationTable[ currentSid ];
272         }
273         // Now construct the chain
274         final int[] chain = new int[ index ];
275         // Set the index
276         index = 0;
277         // Reset the current sector
278         currentSid = sid;
279         // Now fill in the chain
280         while ( currentSid != -2 ) {
281             chain[ index ] = currentSid;
282             index++;
283             // Get the next sector in the chain
284             currentSid = this.shortSectorAllocationTable[ currentSid ];
285         }
286         return chain;
287     }
288 
289     /**
290      * @return Returns the shortSectorSize.
291      */
292     public int getShortSectorSize() {
293         return shortSectorSize;
294     }
295 
296     /**
297      * Checks if the specified directory entry refers to a short stream.
298      *
299      * @param entry the directory entry to test
300      * @return  <code>true</code> if the specified directory entry is for a
301      * short stream and <code>false</code> otherwise
302      */
303     public boolean isShortStream( final DirectoryEntry entry ) {
304         return entry.getStreamSize() < this.getMinimumStandardStreamSize();
305     }
306 
307     public int[] readMasterSectorAllocationTable( final RandomAccessFile randomFile, final byte[] header ) throws IOException {
308         final int[] mSAT = new int[ 109 + this.sectorSize * this.sectorsInMasterSectorAllocationTable ];
309         byte[] buffer = header;
310         int offset = 76;
311         // Decode the master sector allocation table. Up to the first 109 SID's
312         // of that are held in the document header.
313         for ( int i = 0; i < mSAT.length; offset += 4, i++ ) {
314             final int sid = this.endianDecoder.decode4ByteInt( buffer, offset );
315             if ( sid == -2 ) {
316                 // We're at the end of the MSAT. We can infer that we must be
317                 // in a sector outside the header too.
318                 if ( i + 1 != mSAT.length ) {
319                     // Note that we do not add 1 to i here, as i is ready
320                     // to store the current sid in the mSAT array. i.e. it
321                     // has already been incremented
322                     final int[] retval = new int[ i ];
323                     System.arraycopy( mSAT, 0, retval, 0, i );
324                     return retval;
325                 } else {
326                     break;
327                 }
328             } else if ( sid == -1 ) {
329                 // We've got an SID of -1(free sector) so we must be in the
330                 // header still, otherwise this is an error
331                 if ( this.masterSectorAllocationTableSID == -2 ) {
332                     // Ok, the MSAT doesn't extend outside the header so we
333                     // must be at the end.
334                     if ( i + 1 != mSAT.length ) {
335                         // Note that we do not add 1 to i here, as i is ready
336                         // to store the current sid in the mSAT array. i.e. it
337                         // has already been incremented
338                         final int[] retval = new int[ i ];
339                         System.arraycopy( mSAT, 0, retval, 0, i );
340                         return retval;
341                     } else {
342                         break;
343                     }
344                 } else {
345                     // We've got an SID of -1(free sector)
346                     throw new IOAlert(
347                         "Unexpected end of master sector allocation table"
348                     ).mishap();
349                 }
350             } else if ( offset + 4 == buffer.length && i + 1 != mSAT.length ) {
351                 // We have got to the last element in the current sector but it
352                 // is not the last element of the master sector allocation
353                 // table, so jump to the next sector.
354                 randomFile.seek( this.getSectorOffset( sid ) );
355                 buffer = new byte[ this.sectorSize ];
356                 randomFile.readFully( buffer );
357                 offset = 0;
358                 final int sid2 = this.endianDecoder.decode4ByteInt( buffer, offset );
359                 mSAT[ i ] = sid2;
360             } else {
361                 mSAT[ i ] = sid;
362             }
363         }
364         return mSAT;
365     }
366 
367     public int[] readSectorAllocationTable( final RandomAccessFile randomFile ) throws IOException {
368         final int[] SAT = new int[ this.sectorSize * this.sectorsInSectorAllocationTable >> 2 ];
369         int satOffset = 0;
370         for ( int i = 0; i < this.masterSectorAllocationTable.length; i++ ) {
371             // Intialise a buffer to store the sector data
372             final byte[] sector = new byte[ this.sectorSize ];
373             // Read the sector of information for the current SID
374             randomFile.seek(
375                 this.getSectorOffset( this.masterSectorAllocationTable[ i ] )
376             );
377             randomFile.readFully( sector );
378             // Now append all the SAT information from this sector
379             for ( int sectorOffset = 0; sectorOffset < sector.length; sectorOffset += 4 ) {
380                 SAT[ satOffset++ ] = this.endianDecoder.decode4ByteInt( sector, sectorOffset );
381             }
382         }
383         return SAT;
384     }
385 
386     public int[] readShortSectorAllocationTable( final RandomAccessFile randomFile ) throws IOException {
387         final int[] SSAT = new int[ this.sectorSize * this.sectorsInShortSectorAllocationTable >> 2 ];
388         int ssatOffset = 0;
389         final int[] shortSATSectorChain = this.getSectorChain( this.shortSectorAllocationTableSID ); 
390         for ( int i = 0; i < shortSATSectorChain.length; i++ ) {
391             // Intialise a buffer to store the sector data
392             final byte[] sector = new byte[ this.sectorSize ];
393             // Read the sector of information for the current SID
394             randomFile.seek(
395                 this.getSectorOffset( shortSATSectorChain[ i ] )
396             );
397             randomFile.readFully( sector );
398             // Now append all the SAT information from this sector
399             for ( int sectorOffset = 0; sectorOffset < sector.length; sectorOffset += 4 ) {
400                 SSAT[ ssatOffset++ ] = this.endianDecoder.decode4ByteInt( sector, sectorOffset );
401             }
402         }
403         return SSAT;
404     }
405 
406 }