|
|||||||||||||||||||
Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
SharedFileInputStream.java | 71.2% | 87.8% | 95.8% | 84.4% |
|
1 | /** | |
2 | * | |
3 | * Copyright 2006 The Apache Software Foundation | |
4 | * | |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | * you may not use this file except in compliance with the License. | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | package javax.mail.util; | |
19 | ||
20 | import java.io.BufferedInputStream; | |
21 | import java.io.File; | |
22 | import java.io.IOException; | |
23 | import java.io.InputStream; | |
24 | import java.io.RandomAccessFile; | |
25 | ||
26 | import javax.mail.internet.SharedInputStream; | |
27 | ||
28 | public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream { | |
29 | ||
30 | ||
31 | // This initial size isn't documented, but bufsize is 2048 after initialization for the | |
32 | // Sun implementation. | |
33 | private static final int DEFAULT_BUFFER_SIZE = 2048; | |
34 | ||
35 | // the shared file information, used to synchronize opens/closes of the base file. | |
36 | private SharedFileSource source; | |
37 | ||
38 | /** | |
39 | * The file offset that is the first byte in the read buffer. | |
40 | */ | |
41 | protected long bufpos; | |
42 | ||
43 | /** | |
44 | * The normal size of the read buffer. | |
45 | */ | |
46 | protected int bufsize; | |
47 | ||
48 | /** | |
49 | * The size of the file subset represented by this stream instance. | |
50 | */ | |
51 | protected long datalen; | |
52 | ||
53 | /** | |
54 | * The source of the file data. This is shared across multiple | |
55 | * instances. | |
56 | */ | |
57 | protected RandomAccessFile in; | |
58 | ||
59 | /** | |
60 | * The starting position of data represented by this stream relative | |
61 | * to the start of the file data. This stream instance represents | |
62 | * data in the range start to (start + datalen - 1). | |
63 | */ | |
64 | protected long start; | |
65 | ||
66 | ||
67 | /** | |
68 | * Construct a SharedFileInputStream from a file name, using the default buffer size. | |
69 | * | |
70 | * @param file The name of the file. | |
71 | * | |
72 | * @exception IOException | |
73 | */ | |
74 | 1 | public SharedFileInputStream(String file) throws IOException { |
75 | 1 | this(file, DEFAULT_BUFFER_SIZE); |
76 | } | |
77 | ||
78 | ||
79 | /** | |
80 | * Construct a SharedFileInputStream from a File object, using the default buffer size. | |
81 | * | |
82 | * @param file The name of the file. | |
83 | * | |
84 | * @exception IOException | |
85 | */ | |
86 | 2 | public SharedFileInputStream(File file) throws IOException { |
87 | 2 | this(file, DEFAULT_BUFFER_SIZE); |
88 | } | |
89 | ||
90 | ||
91 | /** | |
92 | * Construct a SharedFileInputStream from a file name, with a given initial buffer size. | |
93 | * | |
94 | * @param file The name of the file. | |
95 | * @param bufferSize The initial buffer size. | |
96 | * | |
97 | * @exception IOException | |
98 | */ | |
99 | 2 | public SharedFileInputStream(String file, int bufferSize) throws IOException { |
100 | // I'm not sure this is correct or not. The SharedFileInputStream spec requires this | |
101 | // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream, | |
102 | // which we're not really working from at this point. Using null seems to work so far. | |
103 | 2 | super(null); |
104 | 2 | init(new File(file), bufferSize); |
105 | } | |
106 | ||
107 | ||
108 | /** | |
109 | * Construct a SharedFileInputStream from a File object, with a given initial buffer size. | |
110 | * | |
111 | * @param file The name of the file. | |
112 | * @param bufferSize The initial buffer size. | |
113 | * | |
114 | * @exception IOException | |
115 | */ | |
116 | 5 | public SharedFileInputStream(File file, int bufferSize) throws IOException { |
117 | // I'm not sure this is correct or not. The SharedFileInputStream spec requires this | |
118 | // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream, | |
119 | // which we're not really working from at this point. Using null seems to work so far. | |
120 | 5 | super(null); |
121 | 5 | init(file, bufferSize); |
122 | } | |
123 | ||
124 | ||
125 | /** | |
126 | * Private constructor used to spawn off a shared instance | |
127 | * of this stream. | |
128 | * | |
129 | * @param source The internal class object that manages the shared resources of | |
130 | * the stream. | |
131 | * @param start The starting offset relative to the beginning of the file. | |
132 | * @param len The length of file data in this shared instance. | |
133 | * @param bufsize The initial buffer size (same as the spawning parent. | |
134 | */ | |
135 | 3 | private SharedFileInputStream(SharedFileSource source, long start, long len, int bufsize) { |
136 | 3 | super(null); |
137 | 3 | this.source = source; |
138 | 3 | in = source.open(); |
139 | 3 | this.start = start; |
140 | 3 | bufpos = start; |
141 | 3 | datalen = len; |
142 | 3 | this.bufsize = bufsize; |
143 | 3 | buf = new byte[bufsize]; |
144 | // other fields such as pos and count initialized by the super class constructor. | |
145 | } | |
146 | ||
147 | ||
148 | /** | |
149 | * Shared initializtion routine for the constructors. | |
150 | * | |
151 | * @param file The file we're accessing. | |
152 | * @param bufferSize The initial buffer size to use. | |
153 | * | |
154 | * @exception IOException | |
155 | */ | |
156 | 7 | private void init(File file, int bufferSize) throws IOException { |
157 | 7 | if (bufferSize <= 0) { |
158 | 0 | throw new IllegalArgumentException("Buffer size must be positive"); |
159 | } | |
160 | // create a random access file for accessing the data, then create an object that's used to share | |
161 | // instances of the same stream. | |
162 | 7 | source = new SharedFileSource(file); |
163 | // we're opening the first one. | |
164 | 7 | in = source.open(); |
165 | // this represents the entire file, for now. | |
166 | 7 | start = 0; |
167 | // use the current file length for the bounds | |
168 | 7 | datalen = in.length(); |
169 | // now create our buffer version | |
170 | 7 | bufsize = bufferSize; |
171 | 7 | bufpos = 0; |
172 | // NB: this is using the super class protected variable. | |
173 | 7 | buf = new byte[bufferSize]; |
174 | } | |
175 | ||
176 | ||
177 | /** | |
178 | * Check to see if we need to read more data into our buffer. | |
179 | * | |
180 | * @return False if there's not valid data in the buffer (generally means | |
181 | * an EOF condition). | |
182 | * @exception IOException | |
183 | */ | |
184 | 249 | private boolean checkFill() throws IOException { |
185 | // if we have data in the buffer currently, just return | |
186 | 249 | if (pos < count) { |
187 | 219 | return true; |
188 | } | |
189 | ||
190 | // ugh, extending BufferedInputStream also means supporting mark positions. That complicates everything. | |
191 | // life is so much easier if marks are not used.... | |
192 | 30 | if (markpos < 0) { |
193 | // reset back to the buffer position | |
194 | 20 | pos = 0; |
195 | // this will be the new position within the file once we're read some data. | |
196 | 20 | bufpos += count; |
197 | } | |
198 | else { | |
199 | ||
200 | ||
201 | // we have marks to worry about....damn. | |
202 | // if we have room in the buffer to read more data, then we will. Otherwise, we need to see | |
203 | // if it's possible to shift the data in the buffer or extend the buffer (up to the mark limit). | |
204 | 10 | if (pos >= buf.length) { |
205 | // the mark position is not at the beginning of the buffer, so just shuffle the bytes, leaving | |
206 | // us room to read more data. | |
207 | 10 | if (markpos > 0) { |
208 | // this is the size of the data we need to keep. | |
209 | 2 | int validSize = pos - markpos; |
210 | // perform the shift operation. | |
211 | 2 | System.arraycopy(buf, markpos, buf, 0, validSize); |
212 | // now adjust the positional markers for this shift. | |
213 | 2 | pos = validSize; |
214 | 2 | bufpos += markpos; |
215 | 2 | markpos = 0; |
216 | } | |
217 | // the mark is at the beginning, and we've used up the buffer. See if we're allowed to | |
218 | // extend this. | |
219 | 8 | else if (buf.length < marklimit) { |
220 | // try to double this, but throttle to the mark limit | |
221 | 6 | int newSize = Math.min(buf.length * 2, marklimit); |
222 | ||
223 | 6 | byte[] newBuffer = new byte[newSize]; |
224 | 6 | System.arraycopy(buf, 0, newBuffer, 0, buf.length); |
225 | ||
226 | // replace the old buffer. Note that all other positional markers remain the same here. | |
227 | 6 | buf = newBuffer; |
228 | } | |
229 | // we've got further than allowed, so invalidate the mark, and just reset the buffer | |
230 | else { | |
231 | 2 | markpos = -1; |
232 | 2 | pos = 0; |
233 | 2 | bufpos += count; |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | // if we're past our designated end, force an eof. | |
239 | 30 | if (bufpos + pos >= start + datalen) { |
240 | 5 | return false; |
241 | } | |
242 | ||
243 | // seek to the read location start. Note this is a shared file, so this assumes all of the methods | |
244 | // doing buffer fills will be synchronized. | |
245 | 25 | int fillLength = buf.length - pos; |
246 | ||
247 | // we might be working with a subset of the file data, so normal eof processing might not apply. | |
248 | // we need to limit how much we read to the data length. | |
249 | 25 | if (bufpos - start + pos + fillLength > datalen) { |
250 | 7 | fillLength = (int)(datalen - (bufpos - start + pos)); |
251 | } | |
252 | ||
253 | // finally, try to read more data into the buffer. | |
254 | 25 | fillLength = source.read(bufpos + pos, buf, pos, fillLength); |
255 | ||
256 | // we weren't able to read anything, count this as an eof failure. | |
257 | 25 | if (fillLength <= 0) { |
258 | 0 | return false; |
259 | } | |
260 | ||
261 | // set the new buffer count | |
262 | 25 | count = fillLength + pos; |
263 | ||
264 | // we have data in the buffer. | |
265 | 25 | return true; |
266 | } | |
267 | ||
268 | ||
269 | /** | |
270 | * Return the number of bytes available for reading without | |
271 | * blocking for a long period. | |
272 | * | |
273 | * @return For this stream, this is the number of bytes between the | |
274 | * current read position and the indicated end of the file. | |
275 | * @exception IOException | |
276 | */ | |
277 | 0 | public synchronized int available() throws IOException { |
278 | 0 | checkOpen(); |
279 | ||
280 | // this is backed by a file, which doesn't really block. We can return all the way to the | |
281 | // marked data end, if necessary | |
282 | 0 | long endMarker = start + datalen; |
283 | 0 | return (int)(endMarker - (bufpos + pos)); |
284 | } | |
285 | ||
286 | ||
287 | /** | |
288 | * Return the current read position of the stream. | |
289 | * | |
290 | * @return The current position relative to the beginning of the stream. | |
291 | * This is not the position relative to the start of the file, since | |
292 | * the stream starting position may be other than the beginning. | |
293 | */ | |
294 | 19 | public long getPosition() { |
295 | 19 | checkOpenRuntime(); |
296 | ||
297 | 19 | return bufpos + pos - start; |
298 | } | |
299 | ||
300 | ||
301 | /** | |
302 | * Mark the current position for retracing. | |
303 | * | |
304 | * @param readlimit The limit for the distance the read position can move from | |
305 | * the mark position before the mark is reset. | |
306 | */ | |
307 | 2 | public synchronized void mark(int readlimit) { |
308 | 2 | checkOpenRuntime(); |
309 | 2 | marklimit = readlimit; |
310 | 2 | markpos = pos; |
311 | } | |
312 | ||
313 | ||
314 | /** | |
315 | * Read a single byte of data from the input stream. | |
316 | * | |
317 | * @return The read byte. Returns -1 if an eof condition has been hit. | |
318 | * @exception IOException | |
319 | */ | |
320 | 220 | public synchronized int read() throws IOException { |
321 | 220 | checkOpen(); |
322 | ||
323 | // check to see if we can fill more data | |
324 | 220 | if (!checkFill()) { |
325 | 5 | return -1; |
326 | } | |
327 | ||
328 | // return the current byte...anded to prevent sign extension. | |
329 | 215 | return buf[pos++] & 0xff; |
330 | } | |
331 | ||
332 | ||
333 | /** | |
334 | * Read multiple bytes of data and place them directly into | |
335 | * a byte-array buffer. | |
336 | * | |
337 | * @param buffer The target buffer. | |
338 | * @param offset The offset within the buffer to place the data. | |
339 | * @param length The length to attempt to read. | |
340 | * | |
341 | * @return The number of bytes actually read. Returns -1 for an EOF | |
342 | * condition. | |
343 | * @exception IOException | |
344 | */ | |
345 | 14 | public synchronized int read(byte buffer[], int offset, int length) throws IOException { |
346 | 14 | checkOpen(); |
347 | ||
348 | // asked to read nothing? That's what we'll do. | |
349 | 14 | if (length == 0) { |
350 | 0 | return 0; |
351 | } | |
352 | ||
353 | ||
354 | 14 | int returnCount = 0; |
355 | 14 | while (length > 0) { |
356 | // check to see if we can/must fill more data | |
357 | 24 | if (!checkFill()) { |
358 | // we've hit the end, but if we've read data, then return that. | |
359 | 0 | if (returnCount > 0) { |
360 | 0 | return returnCount; |
361 | } | |
362 | // trun eof. | |
363 | 0 | return -1; |
364 | } | |
365 | ||
366 | 24 | int available = count - pos; |
367 | 24 | int given = Math.min(available, length); |
368 | ||
369 | 24 | System.arraycopy(buf, pos, buffer, offset, given); |
370 | ||
371 | // now adjust all of our positions and counters | |
372 | 24 | pos += given; |
373 | 24 | length -= given; |
374 | 24 | returnCount += given; |
375 | 24 | offset += given; |
376 | } | |
377 | // return the accumulated count. | |
378 | 14 | return returnCount; |
379 | } | |
380 | ||
381 | ||
382 | /** | |
383 | * Skip the read pointer ahead a given number of bytes. | |
384 | * | |
385 | * @param n The number of bytes to skip. | |
386 | * | |
387 | * @return The number of bytes actually skipped. | |
388 | * @exception IOException | |
389 | */ | |
390 | 5 | public synchronized long skip(long n) throws IOException { |
391 | 5 | checkOpen(); |
392 | ||
393 | // nothing to skip, so don't skip | |
394 | 5 | if (n <= 0) { |
395 | 0 | return 0; |
396 | } | |
397 | ||
398 | // see if we need to fill more data, and potentially shift the mark positions | |
399 | 5 | if (!checkFill()) { |
400 | 0 | return 0; |
401 | } | |
402 | ||
403 | 5 | long available = count - pos; |
404 | ||
405 | // the skipped contract allows skipping within the current buffer bounds, so cap it there. | |
406 | 5 | long skipped = available < n ? available : n; |
407 | 5 | pos += skipped; |
408 | 5 | return skipped; |
409 | } | |
410 | ||
411 | /** | |
412 | * Reset the mark position. | |
413 | * | |
414 | * @exception IOException | |
415 | */ | |
416 | 6 | public synchronized void reset() throws IOException { |
417 | 6 | checkOpen(); |
418 | 6 | if (markpos < 0) { |
419 | 2 | throw new IOException("Resetting to invalid mark position"); |
420 | } | |
421 | // if we have a markpos, it will still be in the buffer bounds. | |
422 | 4 | pos = markpos; |
423 | } | |
424 | ||
425 | ||
426 | /** | |
427 | * Indicates the mark() operation is supported. | |
428 | * | |
429 | * @return Always returns true. | |
430 | */ | |
431 | 2 | public boolean markSupported() { |
432 | 2 | return true; |
433 | } | |
434 | ||
435 | ||
436 | /** | |
437 | * Close the stream. This does not close the source file until | |
438 | * the last shared instance is closed. | |
439 | * | |
440 | * @exception IOException | |
441 | */ | |
442 | 7 | public void close() throws IOException { |
443 | // already closed? This is not an error | |
444 | 7 | if (in == null) { |
445 | 0 | return; |
446 | } | |
447 | ||
448 | 7 | try { |
449 | // perform a close on the source version. | |
450 | 7 | source.close(); |
451 | } finally { | |
452 | 7 | in = null; |
453 | } | |
454 | } | |
455 | ||
456 | ||
457 | /** | |
458 | * Create a new stream from this stream, using the given | |
459 | * start offset and length. | |
460 | * | |
461 | * @param offset The offset relative to the start of this stream instance. | |
462 | * @param end The end offset of the substream. If -1, the end of the parent stream is used. | |
463 | * | |
464 | * @return A new SharedFileInputStream object sharing the same source | |
465 | * input file. | |
466 | */ | |
467 | 3 | public InputStream newStream(long offset, long end) { |
468 | 3 | checkOpenRuntime(); |
469 | ||
470 | 3 | if (offset < 0) { |
471 | 0 | throw new IllegalArgumentException("Start position is less than 0"); |
472 | } | |
473 | // the default end position is the datalen of the one we're spawning from. | |
474 | 3 | if (end == -1) { |
475 | 1 | end = datalen; |
476 | } | |
477 | ||
478 | // create a new one using the private constructor | |
479 | 3 | return new SharedFileInputStream(source, start + (int)offset, (int)(end - offset), bufsize); |
480 | } | |
481 | ||
482 | ||
483 | /** | |
484 | * Check if the file is open and throw an IOException if not. | |
485 | * | |
486 | * @exception IOException | |
487 | */ | |
488 | 245 | private void checkOpen() throws IOException { |
489 | 245 | if (in == null) { |
490 | 0 | throw new IOException("Stream has been closed"); |
491 | } | |
492 | } | |
493 | ||
494 | ||
495 | /** | |
496 | * Check if the file is open and throw an IOException if not. This version is | |
497 | * used because several API methods are not defined as throwing IOException, so | |
498 | * checkOpen() can't be used. The Sun implementation just throws RuntimeExceptions | |
499 | * in those methods, hence 2 versions. | |
500 | * | |
501 | * @exception RuntimeException | |
502 | */ | |
503 | 24 | private void checkOpenRuntime() { |
504 | 24 | if (in == null) { |
505 | 0 | throw new RuntimeException("Stream has been closed"); |
506 | } | |
507 | } | |
508 | ||
509 | ||
510 | /** | |
511 | * Internal class used to manage resources shared between the | |
512 | * ShareFileInputStream instances. | |
513 | */ | |
514 | class SharedFileSource { | |
515 | // the file source | |
516 | public RandomAccessFile source; | |
517 | // the shared instance count for this file (open instances) | |
518 | public int instanceCount = 0; | |
519 | ||
520 | 7 | public SharedFileSource(File file) throws IOException { |
521 | 7 | source = new RandomAccessFile(file, "r"); |
522 | } | |
523 | ||
524 | /** | |
525 | * Open the shared stream to keep track of open instances. | |
526 | */ | |
527 | 10 | public synchronized RandomAccessFile open() { |
528 | 10 | instanceCount++; |
529 | 10 | return source; |
530 | } | |
531 | ||
532 | /** | |
533 | * Process a close request for this stream. If there are multiple | |
534 | * instances using this underlying stream, the stream will not | |
535 | * be closed. | |
536 | * | |
537 | * @exception IOException | |
538 | */ | |
539 | 7 | public synchronized void close() throws IOException { |
540 | 7 | if (instanceCount > 0) { |
541 | 7 | instanceCount--; |
542 | // if the last open instance, close the real source file. | |
543 | 7 | if (instanceCount == 0) { |
544 | 5 | source.close(); |
545 | } | |
546 | } | |
547 | } | |
548 | ||
549 | /** | |
550 | * Read a buffer of data from the shared file. | |
551 | * | |
552 | * @param position The position to read from. | |
553 | * @param buf The target buffer for storing the read data. | |
554 | * @param offset The starting offset within the buffer. | |
555 | * @param length The length to attempt to read. | |
556 | * | |
557 | * @return The number of bytes actually read. | |
558 | * @exception IOException | |
559 | */ | |
560 | 25 | public synchronized int read(long position, byte[] buf, int offset, int length) throws IOException { |
561 | // seek to the read location start. Note this is a shared file, so this assumes all of the methods | |
562 | // doing buffer fills will be synchronized. | |
563 | 25 | source.seek(position); |
564 | 25 | return source.read(buf, offset, length); |
565 | } | |
566 | ||
567 | ||
568 | /** | |
569 | * Ensure the stream is closed when this shared object is finalized. | |
570 | * | |
571 | * @exception Throwable | |
572 | */ | |
573 | 7 | protected void finalize() throws Throwable { |
574 | 7 | super.finalize(); |
575 | 7 | if (instanceCount > 0) { |
576 | 2 | source.close(); |
577 | } | |
578 | } | |
579 | } | |
580 | } | |
581 |
|