Clover coverage report - Maven Clover report
Coverage timestamp: Sun Aug 20 2006 04:01:44 PDT
file stats: LOC: 581   Methods: 24
NCLOC: 222   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
SharedFileInputStream.java 71.2% 87.8% 95.8% 84.4%
coverage coverage
 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