1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
70 return false;
71 } else if ( this.pushBack == -2 ) {
72
73 try {
74 this.pushBack = this.inputStream.read();
75 return this.hasMoreBytes();
76 } catch ( IOException ex ) {
77
78
79 throw OfficeIOAlert.unexpectedEndOfFile().mishap();
80 }
81 } else {
82
83
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
115
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
130 final byte[] b = new byte[ len ];
131
132 int read = 0;
133 try {
134 if ( len > 0 && this.pushBack != -2 ) {
135
136 b[ read++ ] = (byte) this.pushBack;
137
138 this.pushBack = -2;
139 }
140
141 while ( read != b.length ) {
142
143 final int thisTimeRead = this.inputStream.read( b, read, len - read );
144
145 if ( thisTimeRead == -1 ) {
146 throw OfficeIOAlert.unexpectedEndOfFile().mishap();
147 } else {
148 read += thisTimeRead;
149 }
150 }
151 } catch ( IOException ex ) {
152
153
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
169
170 return this.readBytes( this.read2ByteInt() );
171 }
172
173 }