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

Java tutorial

Introduction

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

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.commons.compress.archivers.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().
 */
public class TarInputStream extends FilterInputStream {
    private TarBuffer m_buffer;
    private TarArchiveEntry 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 TarArchiveEntry 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 TarArchiveEntry(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 StringBuilder longName = new StringBuilder();
            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();

            // remove trailing null terminator
            if (longName.length() > 0 && longName.charAt(longName.length() - 1) == 0) {
                longName.deleteCharAt(longName.length() - 1);
            }

            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);
        }
    }
}