1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package org.archive.io;
24
25
26 import java.io.IOException;
27
28
29 /***
30 * Buffers data from some other SeekInputStream.
31 *
32 * @author pjack
33 */
34 public class BufferedSeekInputStream extends SeekInputStream {
35
36
37 /***
38 * The underlying input stream.
39 */
40 final private SeekInputStream input;
41
42
43 /***
44 * The buffered data.
45 */
46 final private byte[] buffer;
47
48
49 /***
50 * The maximum offset of valid data in the buffer. Usually the same
51 * as buffer.length, but may be shorter if we're in the last region
52 * of the stream.
53 */
54 private int maxOffset;
55
56
57 /***
58 * The offset of within the buffer of the next byte to read.
59 */
60 private int offset;
61
62
63 /***
64 * Constructor.
65 *
66 * @param input the underlying input stream
67 * @param capacity the size of the buffer
68 * @throws IOException if an IO occurs filling the first buffer
69 */
70 public BufferedSeekInputStream(SeekInputStream input, int capacity)
71 throws IOException {
72 this.input = input;
73 this.buffer = new byte[capacity];
74 buffer();
75 }
76
77 /***
78 * Fills the buffer.
79 *
80 * @throws IOException if an IO error occurs
81 */
82 private void buffer() throws IOException {
83 int remaining = buffer.length;
84 while (remaining > 0) {
85 int r = input.read(buffer, buffer.length - remaining, remaining);
86 if (r <= 0) {
87
88 offset = 0;
89 maxOffset = buffer.length - remaining;
90 return;
91 }
92 remaining -= r;
93 }
94 maxOffset = buffer.length;
95 offset = 0;
96 }
97
98
99 /***
100 * Ensures that the buffer is valid.
101 *
102 * @throws IOException if an IO error occurs
103 */
104 private void ensureBuffer() throws IOException {
105 if (offset >= maxOffset) {
106 buffer();
107 }
108 }
109
110
111 /***
112 * Returns the number of unread bytes in the current buffer.
113 *
114 * @return the remaining bytes
115 */
116 private int remaining() {
117 return maxOffset - offset;
118 }
119
120
121 @Override
122 public int read() throws IOException {
123 ensureBuffer();
124 if (maxOffset == 0) {
125 return -1;
126 }
127 int ch = buffer[offset] & 0xFF;
128 offset++;
129 return ch;
130 }
131
132
133 @Override
134 public int read(byte[] buf, int ofs, int len) throws IOException {
135 ensureBuffer();
136 if (maxOffset == 0) {
137 return 0;
138 }
139 len = Math.min(len, remaining());
140 System.arraycopy(buffer, offset, buf, ofs, len);
141 offset += len;
142 return len;
143 }
144
145
146 @Override
147 public int read(byte[] buf) throws IOException {
148 return read(buf, 0, buf.length);
149 }
150
151
152 @Override
153 public long skip(long c) throws IOException {
154 ensureBuffer();
155 if (maxOffset == 0) {
156 return 0;
157 }
158 int count = (c > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)c;
159 int skip = Math.min(count, remaining());
160 offset += skip;
161 return skip;
162 }
163
164
165 /***
166 * Returns the stream's current position.
167 *
168 * @return the current position
169 */
170 public long position() throws IOException {
171 return input.position() - buffer.length + offset;
172 }
173
174
175 /***
176 * Seeks to the given position. This method avoids re-filling the buffer
177 * if at all possible.
178 *
179 * @param p the position to set
180 * @throws IOException if an IO error occurs
181 */
182 public void position(long p) throws IOException {
183 long blockStart = (input.position() - maxOffset)
184 / buffer.length * buffer.length;
185 long blockEnd = blockStart + maxOffset;
186 if ((p >= blockStart) && (p < blockEnd)) {
187
188 long adj = p - blockStart;
189 offset = (int)adj;
190 return;
191 }
192 positionDirect(p);
193 }
194
195
196 /***
197 * Positions the underlying stream at the given position, then refills
198 * the buffer.
199 *
200 * @param p the position to set
201 * @throws IOException if an IO error occurs
202 */
203 private void positionDirect(long p) throws IOException {
204 long newBlockStart = p / buffer.length * buffer.length;
205 input.position(newBlockStart);
206 buffer();
207 offset = (int)(p % buffer.length);
208 }
209
210 /***
211 * Close the stream, including the wrapped input stream.
212 */
213 public void close() throws IOException {
214 super.close();
215 if(this.input!=null) {
216 this.input.close();
217 }
218 }
219
220
221 }