org.apache.commons.compress.tar.TarInputStream.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.compress.tar.TarInputStream.java

Source

/*
 * $Header: /home/cvspublic/jakarta-commons-sandbox/compress/src/java/org/apache/commons/compress/tar/TarInputStream.java,v 1.1 2003/11/23 20:07:46 bayard Exp $
 * $Revision: 1.1 $
 * $Date: 2003/11/23 20:07:46 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowledgement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgement may appear in the software itself,
 *    if and wherever such third-party acknowledgements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package org.apache.commons.compress.tar;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * The TarInputStream reads a UNIX tar archive as an InputStream. methods are
 * provided to position at each successive entry in the archive, and the read
 * each entry as a normal input stream using read().
 *
 * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
 * @version $Revision: 1.1 $ $Date: 2003/11/23 20:07:46 $
 * @see TarInputStream
 * @see TarEntry
 */
public class TarInputStream extends FilterInputStream {
    private TarBuffer m_buffer;
    private TarEntry m_currEntry;
    private boolean m_debug;
    private int m_entryOffset;
    private int m_entrySize;
    private boolean m_hasHitEOF;
    private byte[] m_oneBuf;
    private byte[] m_readBuf;

    /**
     * Construct a TarInputStream using specified input
     * stream and default block and record sizes.
     *
     * @param input stream to create TarInputStream from
     * @see TarBuffer#DEFAULT_BLOCKSIZE
     * @see TarBuffer#DEFAULT_RECORDSIZE
     */
    public TarInputStream(final InputStream input) {
        this(input, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE);
    }

    /**
     * Construct a TarInputStream using specified input
     * stream, block size and default record sizes.
     *
     * @param input stream to create TarInputStream from
     * @param blockSize the block size to use
     * @see TarBuffer#DEFAULT_RECORDSIZE
     */
    public TarInputStream(final InputStream input, final int blockSize) {
        this(input, blockSize, TarBuffer.DEFAULT_RECORDSIZE);
    }

    /**
     * Construct a TarInputStream using specified input
     * stream, block size and record sizes.
     *
     * @param input stream to create TarInputStream from
     * @param blockSize the block size to use
     * @param recordSize the record size to use
     */
    public TarInputStream(final InputStream input, final int blockSize, final int recordSize) {
        super(input);

        m_buffer = new TarBuffer(input, blockSize, recordSize);
        m_oneBuf = new byte[1];
    }

    /**
     * Sets the debugging flag.
     *
     * @param debug The new Debug value
     */
    public void setDebug(final boolean debug) {
        m_debug = debug;
        m_buffer.setDebug(debug);
    }

    /**
     * Get the next entry in this tar archive. This will skip over any remaining
     * data in the current entry, if there is one, and place the input stream at
     * the header of the next entry, and read the header and instantiate a new
     * TarEntry from the header bytes and return that entry. If there are no
     * more entries in the archive, null will be returned to indicate that the
     * end of the archive has been reached.
     *
     * @return The next TarEntry in the archive, or null.
     * @exception IOException Description of Exception
     */
    public TarEntry getNextEntry() throws IOException {
        if (m_hasHitEOF) {
            return null;
        }

        if (m_currEntry != null) {
            final int numToSkip = m_entrySize - m_entryOffset;

            if (m_debug) {
                final String message = "TarInputStream: SKIP currENTRY '" + m_currEntry.getName() + "' SZ "
                        + m_entrySize + " OFF " + m_entryOffset + "  skipping " + numToSkip + " bytes";
                debug(message);
            }

            if (numToSkip > 0) {
                skip(numToSkip);
            }

            m_readBuf = null;
        }

        final byte[] headerBuf = m_buffer.readRecord();
        if (headerBuf == null) {
            if (m_debug) {
                debug("READ NULL RECORD");
            }
            m_hasHitEOF = true;
        } else if (m_buffer.isEOFRecord(headerBuf)) {
            if (m_debug) {
                debug("READ EOF RECORD");
            }
            m_hasHitEOF = true;
        }

        if (m_hasHitEOF) {
            m_currEntry = null;
        } else {
            m_currEntry = new TarEntry(headerBuf);

            if (!(headerBuf[257] == 'u' && headerBuf[258] == 's' && headerBuf[259] == 't' && headerBuf[260] == 'a'
                    && headerBuf[261] == 'r')) {
                //Must be v7Format
            }

            if (m_debug) {
                final String message = "TarInputStream: SET CURRENTRY '" + m_currEntry.getName() + "' size = "
                        + m_currEntry.getSize();
                debug(message);
            }

            m_entryOffset = 0;

            // REVIEW How do we resolve this discrepancy?!
            m_entrySize = (int) m_currEntry.getSize();
        }

        if (null != m_currEntry && m_currEntry.isGNULongNameEntry()) {
            // read in the name
            final StringBuffer longName = new StringBuffer();
            final byte[] buffer = new byte[256];
            int length = 0;
            while ((length = read(buffer)) >= 0) {
                final String str = new String(buffer, 0, length);
                longName.append(str);
            }
            getNextEntry();
            m_currEntry.setName(longName.toString());
        }

        return m_currEntry;
    }

    /**
     * Get the record size being used by this stream's TarBuffer.
     *
     * @return The TarBuffer record size.
     */
    public int getRecordSize() {
        return m_buffer.getRecordSize();
    }

    /**
     * Get the available data that can be read from the current entry in the
     * archive. This does not indicate how much data is left in the entire
     * archive, only in the current entry. This value is determined from the
     * entry's size header field and the amount of data already read from the
     * current entry.
     *
     * @return The number of available bytes for the current entry.
     * @exception IOException when an IO error causes operation to fail
     */
    public int available() throws IOException {
        return m_entrySize - m_entryOffset;
    }

    /**
     * Closes this stream. Calls the TarBuffer's close() method.
     *
     * @exception IOException when an IO error causes operation to fail
     */
    public void close() throws IOException {
        m_buffer.close();
    }

    /**
     * Copies the contents of the current tar archive entry directly into an
     * output stream.
     *
     * @param output The OutputStream into which to write the entry's data.
     * @exception IOException when an IO error causes operation to fail
     */
    public void copyEntryContents(final OutputStream output) throws IOException {
        final byte[] buffer = new byte[32 * 1024];
        while (true) {
            final int numRead = read(buffer, 0, buffer.length);
            if (numRead == -1) {
                break;
            }

            output.write(buffer, 0, numRead);
        }
    }

    /**
     * Since we do not support marking just yet, we do nothing.
     *
     * @param markLimit The limit to mark.
     */
    public void mark(int markLimit) {
    }

    /**
     * Since we do not support marking just yet, we return false.
     *
     * @return False.
     */
    public boolean markSupported() {
        return false;
    }

    /**
     * Reads a byte from the current tar archive entry. This method simply calls
     * read( byte[], int, int ).
     *
     * @return The byte read, or -1 at EOF.
     * @exception IOException when an IO error causes operation to fail
     */
    public int read() throws IOException {
        final int num = read(m_oneBuf, 0, 1);
        if (num == -1) {
            return num;
        } else {
            return (int) m_oneBuf[0];
        }
    }

    /**
     * Reads bytes from the current tar archive entry. This method simply calls
     * read( byte[], int, int ).
     *
     * @param buffer The buffer into which to place bytes read.
     * @return The number of bytes read, or -1 at EOF.
     * @exception IOException when an IO error causes operation to fail
     */
    public int read(final byte[] buffer) throws IOException {
        return read(buffer, 0, buffer.length);
    }

    /**
     * Reads bytes from the current tar archive entry. This method is aware of
     * the boundaries of the current entry in the archive and will deal with
     * them as if they were this stream's start and EOF.
     *
     * @param buffer The buffer into which to place bytes read.
     * @param offset The offset at which to place bytes read.
     * @param count The number of bytes to read.
     * @return The number of bytes read, or -1 at EOF.
     * @exception IOException when an IO error causes operation to fail
     */
    public int read(final byte[] buffer, final int offset, final int count) throws IOException {
        int position = offset;
        int numToRead = count;
        int totalRead = 0;

        if (m_entryOffset >= m_entrySize) {
            return -1;
        }

        if ((numToRead + m_entryOffset) > m_entrySize) {
            numToRead = (m_entrySize - m_entryOffset);
        }

        if (null != m_readBuf) {
            final int size = (numToRead > m_readBuf.length) ? m_readBuf.length : numToRead;

            System.arraycopy(m_readBuf, 0, buffer, position, size);

            if (size >= m_readBuf.length) {
                m_readBuf = null;
            } else {
                final int newLength = m_readBuf.length - size;
                final byte[] newBuffer = new byte[newLength];

                System.arraycopy(m_readBuf, size, newBuffer, 0, newLength);

                m_readBuf = newBuffer;
            }

            totalRead += size;
            numToRead -= size;
            position += size;
        }

        while (numToRead > 0) {
            final byte[] rec = m_buffer.readRecord();
            if (null == rec) {
                // Unexpected EOF!
                final String message = "unexpected EOF with " + numToRead + " bytes unread";
                throw new IOException(message);
            }

            int size = numToRead;
            final int recordLength = rec.length;

            if (recordLength > size) {
                System.arraycopy(rec, 0, buffer, position, size);

                m_readBuf = new byte[recordLength - size];

                System.arraycopy(rec, size, m_readBuf, 0, recordLength - size);
            } else {
                size = recordLength;

                System.arraycopy(rec, 0, buffer, position, recordLength);
            }

            totalRead += size;
            numToRead -= size;
            position += size;
        }

        m_entryOffset += totalRead;

        return totalRead;
    }

    /**
     * Since we do not support marking just yet, we do nothing.
     */
    public void reset() {
    }

    /**
     * Skip bytes in the input buffer. This skips bytes in the current entry's
     * data, not the entire archive, and will stop at the end of the current
     * entry's data if the number to skip extends beyond that point.
     *
     * @param numToSkip The number of bytes to skip.
     * @exception IOException when an IO error causes operation to fail
     */
    public void skip(final int numToSkip) throws IOException {
        // REVIEW
        // This is horribly inefficient, but it ensures that we
        // properly skip over bytes via the TarBuffer...
        //
        final byte[] skipBuf = new byte[8 * 1024];
        int num = numToSkip;
        while (num > 0) {
            final int count = (num > skipBuf.length) ? skipBuf.length : num;
            final int numRead = read(skipBuf, 0, count);
            if (numRead == -1) {
                break;
            }

            num -= numRead;
        }
    }

    /**
     * Utility method to do debugging.
     * Capable of being overidden in sub-classes.
     *
     * @param message the message to use in debugging
     */
    protected void debug(final String message) {
        if (m_debug) {
            System.err.println(message);
        }
    }
}