|
|||||||||||||||||||
| 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 |
|
||||||||||