View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript-Excel: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2005 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.excel;
22  
23  import org.millscript.office.alerts.OfficeIOAlert;
24  import org.millscript.office.endianness.LittleEndianDecoder;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  
30  /**
31   * This class extends a filter input stream with the methods required to decode
32   * BIFF records.
33   */
34  public class BIFFRecordInputStream {
35  
36      private final InputStream inputStream;
37  
38      /**
39       * This field stores a previously read character. This field can hold one
40       * of several values, <code>-1</code> means we've reached the end of the
41       * stream, <code>-2</code> means we should read the next character from the
42       * underlying input stream, any other value is the value that should be
43       * returned on the next {@link #read()}.
44       */
45      private int pushBack = -2;
46  
47      /**
48       * Constructs a new BIFF record input stream to read records from the
49       * specified byte array.
50       *
51       * @param b the byte array to decode
52       */
53      public BIFFRecordInputStream( final byte[] b ) {
54          this.inputStream = new ByteArrayInputStream( b );
55      }
56  
57      /**
58       * Constructs a new BIFF record input stream to read records from the
59       * specified input stream.
60       *
61       * @param is    the InputStream to fetch byte data from
62       */
63      public BIFFRecordInputStream( final InputStream is ) {
64          this.inputStream = is;
65      }
66  
67      public boolean hasMoreBytes() {
68          if ( this.pushBack == -1 ) {
69              // We're at the end of the stream
70              return false;
71          } else if ( this.pushBack == -2 ) {
72              // We need to try and read a byte from the underlying input stream
73              try {
74                  this.pushBack = this.inputStream.read();
75                  return this.hasMoreBytes();
76              } catch ( IOException ex ) {
77                  // FIXME - This alert isn't really the right one, we have an
78                  // abnormal situation, not an end of file
79                  throw OfficeIOAlert.unexpectedEndOfFile().mishap();
80              }
81          } else {
82              // We have a normal value in the push back buffer, so there is at
83              // least one more byte available
84              return true;
85          }
86      }
87  
88      public int read() throws IOException { 
89          if ( this.pushBack == -2 ) {
90              return this.inputStream.read();
91          } else {
92              final int store = this.pushBack;
93              this.pushBack = -2;
94              return store;
95          }
96      }
97  
98      /**
99       * Reads 2 bytes from the stream as a 16 bit little endian positive
100      * integer.
101      *
102      * @return  an int for the two bytes read from the stream
103      * @throws IOException  if there is a problem reading from the stream
104      */
105     public int read2ByteInt() {
106         try {
107             final int byte1 = this.read();
108             final int byte2 = this.read();
109             if ( byte1 == -1 || byte2 == -1 ) {
110                 throw OfficeIOAlert.unexpectedEndOfFile().mishap();
111             }
112             return LittleEndianDecoder.DECODER.decodeInt( byte1, byte2 );
113         } catch ( IOException ex ) {
114             // FIXME - This alert isn't really the right one, we have an
115             // abnormal situation, not an end of file
116             throw OfficeIOAlert.unexpectedEndOfFile().mishap();
117         }
118     }
119 
120     /**
121      * Reads the requeste number of bytes from the stream and returns them as
122      * a byte array.
123      *
124      * @param len   the number of bytes to read from the stream
125      * @return  a byte array holding the bytes read
126      * @throws IOException  if there is a problem reading from the stream
127      */
128     public byte[] readBytes( final int len ) {
129         // Make a new byte array big enough to hold all the data
130         final byte[] b = new byte[ len ];
131         // This integer keeps track of the number of bytes read so far
132         int read = 0;
133         try {
134             if ( len > 0 && this.pushBack != -2 ) {
135                 // Store the push back value in the array
136                 b[ read++ ] = (byte) this.pushBack;
137                 // Reset the push back buffer
138                 this.pushBack = -2;
139             }
140             // Keep reading until we've read the requested amount of data
141             while ( read != b.length ) {
142                 // Read data
143                 final int thisTimeRead = this.inputStream.read( b, read, len - read );
144                 // Did we read anything?
145                 if ( thisTimeRead == -1 ) {
146                     throw OfficeIOAlert.unexpectedEndOfFile().mishap();
147                 } else {
148                     read += thisTimeRead;
149                 }
150             }
151         } catch ( IOException ex ) {
152             // FIXME - This alert isn't really the right one, we have an
153             // abnormal situation, not an end of file
154             throw OfficeIOAlert.unexpectedEndOfFile().mishap();
155         }
156         return b;
157     }
158 
159     /**
160      * Returns the next records data. This is equivalent to a call to
161      * {@linkplain #read2ByteInt()} to determine the record data length then
162      * calling {@linkplain #readBytes(int)} to read the data.
163      *
164      * @return  a byte array with the next records data.
165      * @throws IOException  if there is a problem reading from the stream
166      */
167     public byte[] readRecordData() {
168         // The following is a little backward but it will read the record data
169         // length before calling the readBytes method
170         return this.readBytes( this.read2ByteInt() );
171     }
172 
173 }