1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package org.archive.io;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.io.OutputStream;
30
31
32 /***
33 * Replays the bytes recorded from a RecordingInputStream or
34 * RecordingOutputStream.
35 *
36 * This InputStream supports mark and reset.
37 *
38 * @author gojomo
39 */
40 public class ReplayInputStream extends SeekInputStream
41 {
42 private BufferedSeekInputStream diskStream;
43 private byte[] buffer;
44 private long position;
45
46 /***
47 * Total size of stream content.
48 *
49 * Size of data to replay.
50 */
51 private long size = -1;
52
53 /***
54 * Where the response body starts, if marked
55 */
56 protected long responseBodyStart = -1;
57
58
59 /***
60 * Constructor.
61 *
62 * @param buffer Buffer to read from.
63 * @param size Size of data to replay.
64 * @param responseBodyStart Start of the response body.
65 * @param backingFilename Backing file that sits behind the buffer. If
66 * <code>size<code> > than buffer then we go to backing file to read
67 * data that is beyond buffer.length.
68 *
69 * @throws IOException If we fail to open an input stream on
70 * backing file.
71 */
72 public ReplayInputStream(byte[] buffer, long size, long responseBodyStart,
73 String backingFilename)
74 throws IOException
75 {
76 this(buffer, size, backingFilename);
77 this.responseBodyStart = responseBodyStart;
78 }
79
80 /***
81 * Constructor.
82 *
83 * @param buffer Buffer to read from.
84 * @param size Size of data to replay.
85 * @param backingFilename Backing file that sits behind the buffer. If
86 * <code>size<code> > than buffer then we go to backing file to read
87 * data that is beyond buffer.length.
88 * @throws IOException If we fail to open an input stream on
89 * backing file.
90 */
91 public ReplayInputStream(byte[] buffer, long size, String backingFilename)
92 throws IOException
93 {
94 this.buffer = buffer;
95 this.size = size;
96 if (size > buffer.length) {
97 RandomAccessInputStream rais = new RandomAccessInputStream(
98 new File(backingFilename));
99 diskStream = new BufferedSeekInputStream(rais, 4096);
100 }
101 }
102
103 public long setToResponseBodyStart() throws IOException {
104 position(responseBodyStart);
105 return this.position;
106 }
107
108
109
110
111
112 public int read() throws IOException {
113 if (position == size) {
114 return -1;
115 }
116 if (position < buffer.length) {
117
118 int c = buffer[(int) position] & 0xFF;
119 position++;
120 return c;
121 }
122 int c = diskStream.read();
123 if (c >= 0) {
124 position++;
125 }
126 return c;
127 }
128
129
130
131
132
133
134 public int read(byte[] b, int off, int len) throws IOException {
135 if (position == size) {
136 return -1;
137 }
138 if (position < buffer.length) {
139 int toCopy = (int)Math.min(size - position,
140 Math.min(len, buffer.length - position));
141 System.arraycopy(buffer, (int)position, b, off, toCopy);
142 if (toCopy > 0) {
143 position += toCopy;
144 }
145 return toCopy;
146 }
147
148 int read = diskStream.read(b,off,len);
149 if(read>0) {
150 position += read;
151 }
152 return read;
153 }
154
155 public void readFullyTo(OutputStream os) throws IOException {
156 byte[] buf = new byte[4096];
157 int c = read(buf);
158 while (c != -1) {
159 os.write(buf,0,c);
160 c = read(buf);
161 }
162 }
163
164
165
166
167
168 public void readHeaderTo(OutputStream os) throws IOException {
169 position = 0;
170 byte[] buf = new byte[(int)responseBodyStart];
171 int c = read(buf,0,buf.length);
172 if(c != -1) {
173 os.write(buf,0,c);
174 }
175 }
176
177
178
179
180 public void readContentTo(OutputStream os) throws IOException {
181 setToResponseBodyStart();
182 byte[] buf = new byte[4096];
183 int c = read(buf);
184 while (c != -1) {
185 os.write(buf,0,c);
186 c = read(buf);
187 }
188 }
189
190 public void readContentTo(OutputStream os, int maxSize) throws IOException {
191 setToResponseBodyStart();
192 byte[] buf = new byte[4096];
193 int c = read(buf);
194 int tot = 0;
195 while (c != -1 && tot < maxSize) {
196 os.write(buf,0,c);
197 c = read(buf);
198 tot += c;
199 }
200 }
201
202
203
204
205 public void close() throws IOException {
206 super.close();
207 if(diskStream != null) {
208 diskStream.close();
209 }
210 }
211
212 /***
213 * Total size of stream content.
214 * @return Returns the size.
215 */
216 public long getSize()
217 {
218 return size;
219 }
220
221 /***
222 * Total size of header.
223 * @return the size of the header.
224 */
225 public long getHeaderSize()
226 {
227 return responseBodyStart;
228 }
229
230 /***
231 * Total size of content.
232 * @return the size of the content.
233 */
234 public long getContentSize()
235 {
236 return size - responseBodyStart;
237 }
238
239 /***
240 * @return Amount THEORETICALLY remaining (TODO: Its not theoretical
241 * seemingly. The class implemetentation depends on it being exact).
242 */
243 public long remaining() {
244 return size - position;
245 }
246
247
248 /***
249 * Reposition the stream.
250 *
251 * @param p the new position for this stream
252 * @throws IOException if an IO error occurs
253 */
254 public void position(long p) throws IOException {
255 if (p < 0) {
256 throw new IOException("Negative seek offset.");
257 }
258 if (p > size) {
259 throw new IOException("Desired position exceeds size.");
260 }
261 if (p < buffer.length) {
262
263 if (position > buffer.length) {
264 diskStream.position(0);
265 }
266 } else {
267 diskStream.position(p - buffer.length);
268 }
269 this.position = p;
270 }
271
272
273 public long position() throws IOException {
274 return position;
275 }
276 }