Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 412   Methods: 16
NCLOC: 192   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
TarInputStream.java 50% 49.5% 37.5% 48.5%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2002 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 /*
 56   
  * This package is based on the work done by Timothy Gerard Endres
 57   
  * (time@ice.com) to whom the Ant project is very grateful for his great code.
 58   
  */
 59   
 
 60   
 package org.apache.tools.tar;
 61   
 
 62   
 import java.io.FilterInputStream;
 63   
 import java.io.IOException;
 64   
 import java.io.InputStream;
 65   
 import java.io.OutputStream;
 66   
 
 67   
 /**
 68   
  * The TarInputStream reads a UNIX tar archive as an InputStream.
 69   
  * methods are provided to position at each successive entry in
 70   
  * the archive, and the read each entry as a normal input stream
 71   
  * using read().
 72   
  *
 73   
  * @author Timothy Gerard Endres <a href="mailto:time@ice.com">time@ice.com</a>
 74   
  * @author Stefano Mazzocchi <a href="mailto:stefano@apache.org">stefano@apache.org</a>
 75   
  */
 76   
 public class TarInputStream extends FilterInputStream {
 77   
 
 78   
     protected boolean debug;
 79   
     protected boolean hasHitEOF;
 80   
     protected int entrySize;
 81   
     protected int entryOffset;
 82   
     protected byte[] oneBuf;
 83   
     protected byte[] readBuf;
 84   
     protected TarBuffer buffer;
 85   
     protected TarEntry currEntry;
 86   
     private boolean v7Format;
 87   
     
 88  10
     public TarInputStream(InputStream is) {
 89  10
         this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
 90   
     }
 91   
 
 92  0
     public TarInputStream(InputStream is, int blockSize) {
 93  0
         this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
 94   
     }
 95   
 
 96  10
     public TarInputStream(InputStream is, int blockSize, int recordSize) {
 97  10
         super(is);
 98   
 
 99  10
         this.buffer = new TarBuffer(is, blockSize, recordSize);
 100  10
         this.readBuf = null;
 101  10
         this.oneBuf = new byte[1];
 102  10
         this.debug = false;
 103  10
         this.hasHitEOF = false;
 104  10
         this.v7Format = false;
 105   
     }
 106   
 
 107   
     /**
 108   
      * Sets the debugging flag.
 109   
      *
 110   
      * @param debugF True to turn on debugging.
 111   
      */
 112  0
     public void setDebug(boolean debug) {
 113  0
         this.debug = debug;
 114  0
         this.buffer.setDebug(debug);
 115   
     }
 116   
 
 117   
     /**
 118   
      * Closes this stream. Calls the TarBuffer's close() method.
 119   
      */
 120  10
     public void close() throws IOException {
 121  10
         this.buffer.close();
 122   
     }
 123   
 
 124   
     /**
 125   
      * Get the record size being used by this stream's TarBuffer.
 126   
      *
 127   
      * @return The TarBuffer record size.
 128   
      */
 129  0
     public int getRecordSize() {
 130  0
         return this.buffer.getRecordSize();
 131   
     }
 132   
 
 133   
     /**
 134   
      * Get the available data that can be read from the current
 135   
      * entry in the archive. This does not indicate how much data
 136   
      * is left in the entire archive, only in the current entry.
 137   
      * This value is determined from the entry's size header field
 138   
      * and the amount of data already read from the current entry.
 139   
      *
 140   
      *
 141   
      * @return The number of available bytes for the current entry.
 142   
      */
 143  0
     public int available() throws IOException {
 144  0
         return this.entrySize - this.entryOffset;
 145   
     }
 146   
 
 147   
     /**
 148   
      * Skip bytes in the input buffer. This skips bytes in the
 149   
      * current entry's data, not the entire archive, and will
 150   
      * stop at the end of the current entry's data if the number
 151   
      * to skip extends beyond that point.
 152   
      *
 153   
      * @param numToSkip The number of bytes to skip.
 154   
      */
 155  0
     public long skip(long numToSkip) throws IOException {
 156   
         // REVIEW
 157   
         // This is horribly inefficient, but it ensures that we
 158   
         // properly skip over bytes via the TarBuffer...
 159   
         //
 160  0
         byte[] skipBuf = new byte[8 * 1024];
 161  0
         long skip = numToSkip;
 162  0
         while (skip > 0) {
 163  0
             int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
 164  0
             int numRead = this.read(skipBuf, 0, realSkip);
 165  0
             if (numRead == -1) {
 166  0
                 break;
 167   
             }
 168  0
             skip -= numRead;
 169   
         }
 170  0
         return (numToSkip - skip);
 171   
     }
 172   
 
 173   
     /**
 174   
      * Since we do not support marking just yet, we return false.
 175   
      *
 176   
      * @return False.
 177   
      */
 178  0
     public boolean markSupported() {
 179  0
         return false;
 180   
     }
 181   
 
 182   
     /**
 183   
      * Since we do not support marking just yet, we do nothing.
 184   
      *
 185   
      * @param markLimit The limit to mark.
 186   
      */
 187  0
     public void mark(int markLimit) {
 188   
     }
 189   
 
 190   
     /**
 191   
      * Since we do not support marking just yet, we do nothing.
 192   
      */
 193  0
     public void reset() {
 194   
     }
 195   
 
 196   
     /**
 197   
      * Get the next entry in this tar archive. This will skip
 198   
      * over any remaining data in the current entry, if there
 199   
      * is one, and place the input stream at the header of the
 200   
      * next entry, and read the header and instantiate a new
 201   
      * TarEntry from the header bytes and return that entry.
 202   
      * If there are no more entries in the archive, null will
 203   
      * be returned to indicate that the end of the archive has
 204   
      * been reached.
 205   
      *
 206   
      * @return The next TarEntry in the archive, or null.
 207   
      */
 208  21
     public TarEntry getNextEntry() throws IOException {
 209  21
         if (this.hasHitEOF) {
 210  0
             return null;
 211   
         }
 212   
 
 213  21
         if (this.currEntry != null) {
 214  11
             int numToSkip = this.entrySize - this.entryOffset;
 215   
 
 216  11
             if (this.debug) {
 217  0
                 System.err.println("TarInputStream: SKIP currENTRY '"
 218   
                         + this.currEntry.getName() + "' SZ "
 219   
                         + this.entrySize + " OFF "
 220   
                         + this.entryOffset + "  skipping "
 221   
                         + numToSkip + " bytes");
 222   
             }
 223   
 
 224  11
             if (numToSkip > 0) {
 225  0
                 this.skip(numToSkip);
 226   
             }
 227   
 
 228  11
             this.readBuf = null;
 229   
         }
 230   
 
 231  21
         byte[] headerBuf = this.buffer.readRecord();
 232   
 
 233  21
         if (headerBuf == null) {
 234  0
             if (this.debug) {
 235  0
                 System.err.println("READ NULL RECORD");
 236   
             }
 237  0
             this.hasHitEOF = true;
 238  21
         } else if (this.buffer.isEOFRecord(headerBuf)) {
 239  10
             if (this.debug) {
 240  0
                 System.err.println("READ EOF RECORD");
 241   
             }
 242  10
             this.hasHitEOF = true;
 243   
         }
 244   
 
 245  21
         if (this.hasHitEOF) {
 246  10
             this.currEntry = null;
 247   
         } else {
 248  11
             this.currEntry = new TarEntry(headerBuf);
 249   
 
 250  11
             if (!(headerBuf[257] == 'u' && headerBuf[258] == 's'
 251   
                     && headerBuf[259] == 't' && headerBuf[260] == 'a'
 252   
                     && headerBuf[261] == 'r')) {
 253  0
                 this.v7Format = true;
 254   
             }
 255   
 
 256  11
             if (this.debug) {
 257  0
                 System.err.println("TarInputStream: SET CURRENTRY '"
 258   
                         + this.currEntry.getName()
 259   
                         + "' size = "
 260   
                         + this.currEntry.getSize());
 261   
             }
 262   
 
 263  11
             this.entryOffset = 0;
 264   
 
 265   
             // REVIEW How do we resolve this discrepancy?!
 266  11
             this.entrySize = (int) this.currEntry.getSize();
 267   
         }
 268   
 
 269  21
         if (this.currEntry != null && this.currEntry.isGNULongNameEntry()) {
 270   
             // read in the name
 271  0
             StringBuffer longName = new StringBuffer();
 272  0
             byte[] buffer = new byte[256];
 273  0
             int length = 0;
 274  0
             while ((length = read(buffer)) >= 0) {
 275  0
                 longName.append(new String(buffer, 0, length));
 276   
             }
 277  0
             getNextEntry();
 278  0
             this.currEntry.setName(longName.toString());
 279   
         }
 280   
 
 281  21
         return this.currEntry;
 282   
     }
 283   
 
 284   
     /**
 285   
      * Reads a byte from the current tar archive entry.
 286   
      *
 287   
      * This method simply calls read( byte[], int, int ).
 288   
      *
 289   
      * @return The byte read, or -1 at EOF.
 290   
      */
 291  0
     public int read() throws IOException {
 292  0
         int num = this.read(this.oneBuf, 0, 1);
 293   
 
 294  0
         if (num == -1) {
 295  0
             return num;
 296   
         } else {
 297  0
             return (int) this.oneBuf[0];
 298   
         }
 299   
     }
 300   
 
 301   
     /**
 302   
      * Reads bytes from the current tar archive entry.
 303   
      *
 304   
      * This method simply calls read( byte[], int, int ).
 305   
      *
 306   
      * @param buf The buffer into which to place bytes read.
 307   
      * @return The number of bytes read, or -1 at EOF.
 308   
      */
 309  66
     public int read(byte[] buf) throws IOException {
 310  66
         return this.read(buf, 0, buf.length);
 311   
     }
 312   
 
 313   
     /**
 314   
      * Reads bytes from the current tar archive entry.
 315   
      *
 316   
      * This method is aware of the boundaries of the current
 317   
      * entry in the archive and will deal with them as if they
 318   
      * were this stream's start and EOF.
 319   
      *
 320   
      * @param buf The buffer into which to place bytes read.
 321   
      * @param offset The offset at which to place bytes read.
 322   
      * @param numToRead The number of bytes to read.
 323   
      * @return The number of bytes read, or -1 at EOF.
 324   
      */
 325  66
     public int read(byte[] buf, int offset, int numToRead) throws IOException {
 326  66
         int totalRead = 0;
 327   
 
 328  66
         if (this.entryOffset >= this.entrySize) {
 329  9
             return -1;
 330   
         }
 331   
 
 332  57
         if ((numToRead + this.entryOffset) > this.entrySize) {
 333  9
             numToRead = (this.entrySize - this.entryOffset);
 334   
         }
 335   
 
 336  57
         if (this.readBuf != null) {
 337  0
             int sz = (numToRead > this.readBuf.length) ? this.readBuf.length
 338   
                     : numToRead;
 339   
 
 340  0
             System.arraycopy(this.readBuf, 0, buf, offset, sz);
 341   
 
 342  0
             if (sz >= this.readBuf.length) {
 343  0
                 this.readBuf = null;
 344   
             } else {
 345  0
                 int newLen = this.readBuf.length - sz;
 346  0
                 byte[] newBuf = new byte[newLen];
 347   
 
 348  0
                 System.arraycopy(this.readBuf, sz, newBuf, 0, newLen);
 349   
 
 350  0
                 this.readBuf = newBuf;
 351   
             }
 352   
 
 353  0
             totalRead += sz;
 354  0
             numToRead -= sz;
 355  0
             offset += sz;
 356   
         }
 357   
 
 358  57
         while (numToRead > 0) {
 359  108
             byte[] rec = this.buffer.readRecord();
 360   
 
 361  108
             if (rec == null) {
 362   
                 // Unexpected EOF!
 363  0
                 throw new IOException("unexpected EOF with " + numToRead
 364   
                         + " bytes unread");
 365   
             }
 366   
 
 367  108
             int sz = numToRead;
 368  108
             int recLen = rec.length;
 369   
 
 370  108
             if (recLen > sz) {
 371  9
                 System.arraycopy(rec, 0, buf, offset, sz);
 372   
 
 373  9
                 this.readBuf = new byte[recLen - sz];
 374   
 
 375  9
                 System.arraycopy(rec, sz, this.readBuf, 0, recLen - sz);
 376   
             } else {
 377  99
                 sz = recLen;
 378   
 
 379  99
                 System.arraycopy(rec, 0, buf, offset, recLen);
 380   
             }
 381   
 
 382  108
             totalRead += sz;
 383  108
             numToRead -= sz;
 384  108
             offset += sz;
 385   
         }
 386   
 
 387  57
         this.entryOffset += totalRead;
 388   
 
 389  57
         return totalRead;
 390   
     }
 391   
 
 392   
     /**
 393   
      * Copies the contents of the current tar archive entry directly into
 394   
      * an output stream.
 395   
      *
 396   
      * @param out The OutputStream into which to write the entry's data.
 397   
      */
 398  0
     public void copyEntryContents(OutputStream out) throws IOException {
 399  0
         byte[] buf = new byte[32 * 1024];
 400   
 
 401  0
         while (true) {
 402  0
             int numRead = this.read(buf, 0, buf.length);
 403   
 
 404  0
             if (numRead == -1) {
 405  0
                 break;
 406   
             }
 407   
 
 408  0
             out.write(buf, 0, numRead);
 409   
         }
 410   
     }
 411   
 }
 412