applied patch from Christian Grobmeier

updated tar implementation from ant
https://issues.apache.org/jira/browse/SANDBOX-273



git-svn-id: https://svn.apache.org/repos/asf/commons/sandbox/compress/trunk@732682 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index f580067..4a6491b 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -25,28 +25,34 @@
 import org.apache.commons.compress.archivers.ArchiveEntry;
 
 /**
- * This class represents an entry in a Tar archive. It consists of the entry's
- * header, as well as the entry's File. Entries can be instantiated in one of
- * three ways, depending on how they are to be used. <p>
+ * This class represents an entry in a Tar archive. It consists
+ * of the entry's header, as well as the entry's File. Entries
+ * can be instantiated in one of three ways, depending on how
+ * they are to be used.
+ * <p>
+ * TarEntries that are created from the header bytes read from
+ * an archive are instantiated with the TarEntry( byte[] )
+ * constructor. These entries will be used when extracting from
+ * or listing the contents of an archive. These entries have their
+ * header filled in using the header bytes. They also set the File
+ * to null, since they reference an archive entry not a file.
+ * <p>
+ * TarEntries that are created from Files that are to be written
+ * into an archive are instantiated with the TarEntry( File )
+ * constructor. These entries have their header filled in using
+ * the File's information. They also keep a reference to the File
+ * for convenience when writing entries.
+ * <p>
+ * Finally, TarEntries can be constructed from nothing but a name.
+ * This allows the programmer to construct the entry by hand, for
+ * instance when only an InputStream is available for writing to
+ * the archive, and the header information is constructed from
+ * other information. In this case the header fields are set to
+ * defaults and the File is set to null.
  *
- * TarEntries that are created from the header bytes read from an archive are
- * instantiated with the TarEntry( byte[] ) constructor. These entries will be
- * used when extracting from or listing the contents of an archive. These
- * entries have their header filled in using the header bytes. They also set the
- * File to null, since they reference an archive entry not a file. <p>
- *
- * TarEntries that are created from Files that are to be written into an archive
- * are instantiated with the TarEntry( File ) constructor. These entries have
- * their header filled in using the File's information. They also keep a
- * reference to the File for convenience when writing entries. <p>
- *
- * Finally, TarEntries can be constructed from nothing but a name. This allows
- * the programmer to construct the entry by hand, for instance when only an
- * InputStream is available for writing to the archive, and the header
- * information is constructed from other information. In this case the header
- * fields are set to defaults and the File is set to null. <p>
- *
- * The C structure for a Tar Entry's header is: <pre>
+ * <p>
+ * The C structure for a Tar Entry's header is:
+ * <pre>
  * struct header {
  * char name[NAMSIZ];
  * char mode[8];
@@ -64,424 +70,247 @@
  * char devminor[8];
  * } header;
  * </pre>
+ *
  */
-public class TarArchiveEntry implements ArchiveEntry {
-    /**
-     * The length of the name field in a header buffer.
-     */
-    public static final int NAMELEN = 99;
 
-    /**
-     * The entry's modification time.
-     */
-    private int m_checkSum;
+public class TarArchiveEntry implements TarConstants, ArchiveEntry {
+    /** The entry's name. */
+    private StringBuffer name;
 
-    /**
-     * The entry's group name.
-     */
-    private int m_devMajor;
+    /** The entry's permission mode. */
+    private int mode;
 
-    /**
-     * The entry's major device number.
-     */
-    private int m_devMinor;
+    /** The entry's user id. */
+    private int userId;
 
-    /**
-     * The entry's minor device number.
-     */
-    private File m_file;
+    /** The entry's group id. */
+    private int groupId;
 
-    /**
-     * The entry's user id.
-     */
-    private int m_groupID;
+    /** The entry's size. */
+    private long size;
 
-    /**
-     * The entry's user name.
-     */
-    private StringBuffer m_groupName;
+    /** The entry's modification time. */
+    private long modTime;
 
-    /**
-     * The entry's checksum.
-     */
-    private byte m_linkFlag;
+    /** The entry's link flag. */
+    private byte linkFlag;
 
-    /**
-     * The entry's link flag.
-     */
-    private StringBuffer m_linkName;
+    /** The entry's link name. */
+    private StringBuffer linkName;
 
-    /**
-     * The entry's link name.
-     */
-    private StringBuffer m_magic;
+    /** The entry's magic tag. */
+    private StringBuffer magic;
 
-    /**
-     * The entry's size.
-     */
-    private long m_modTime;
+    /** The entry's user name. */
+    private StringBuffer userName;
 
-    /**
-     * The entry's name.
-     */
-    private int m_mode;
+    /** The entry's group name. */
+    private StringBuffer groupName;
 
-    private StringBuffer m_name;
+    /** The entry's major device number. */
+    private int devMajor;
 
-    /**
-     * The entry's group id.
-     */
-    private long m_size;
+    /** The entry's minor device number. */
+    private int devMinor;
 
-    /**
-     * The entry's permission mode.
-     */
-    private int m_userID;
+    /** The entry's file reference */
+    private File file;
 
-    /**
-     * The entry's magic tag.
-     */
-    private StringBuffer m_userName;
+    /** Maximum length of a user's name in the tar file */
+    public static final int MAX_NAMELEN = 31;
 
-    /**
-     * Construct an entry with only a name. This allows the programmer to
-     * construct the entry's header "by hand". File is set to null.
-     *
-     * @param name the name of the entry
-     */
-    public TarArchiveEntry( final String name )
-    {
-        this();
+    /** Default permissions bits for directories */
+    public static final int DEFAULT_DIR_MODE = 040755;
 
-        final boolean isDir = name.endsWith( "/" );
+    /** Default permissions bits for files */
+    public static final int DEFAULT_FILE_MODE = 0100644;
 
-        m_name = new StringBuffer( name );
-        m_mode = isDir ? 040755 : 0100644;
-        m_linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
-        m_modTime = ( new Date() ).getTime() / 1000;
-        m_linkName = new StringBuffer( "" );
-        m_userName = new StringBuffer( "" );
-        m_groupName = new StringBuffer( "" );
-    }
-
-    /**
-     * Construct an entry with a name an a link flag.
-     *
-     * @param name Description of Parameter
-     * @param linkFlag Description of Parameter
-     */
-    public TarArchiveEntry( final String name, final byte linkFlag )
-    {
-        this( name );
-        m_linkFlag = linkFlag;
-    }
-
-    /**
-     * Construct an entry for a file. File is set to file, and the header is
-     * constructed from information from the file.
-     *
-     * @param file The file that the entry represents.
-     */
-    public TarArchiveEntry( final File file )
-    {
-        this();
-
-        m_file = file;
-
-        String name = file.getPath();
-
-        // Strip off drive letters!
-        final String osName =
-            System.getProperty( "os.name" ).toLowerCase( Locale.US );
-        if( -1 != osName.indexOf( "netware" ) )
-        {
-            if( name.length() > 2 )
-            {
-                final char ch1 = name.charAt( 0 );
-                final char ch2 = name.charAt( 1 );
-
-                if( ch2 == ':' &&
-                    ( ( ch1 >= 'a' && ch1 <= 'z' ) ||
-                    ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
-                {
-                    name = name.substring( 2 );
-                }
-            }
-        }
-        else if( -1 != osName.indexOf( "netware" ) )
-        {
-            final int colon = name.indexOf( ':' );
-            if( colon != -1 )
-            {
-                name = name.substring( colon + 1 );
-            }
-        }
-
-        name = name.replace( File.separatorChar, '/' );
-
-        // No absolute pathnames
-        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
-        // so we loop on starting /'s.
-        while( name.startsWith( "/" ) )
-        {
-            name = name.substring( 1 );
-        }
-
-        m_linkName = new StringBuffer( "" );
-        m_name = new StringBuffer( name );
-
-        if( file.isDirectory() )
-        {
-            m_mode = 040755;
-            m_linkFlag = TarConstants.LF_DIR;
-
-            if( m_name.charAt( m_name.length() - 1 ) != '/' )
-            {
-                m_name.append( "/" );
-            }
-        }
-        else
-        {
-            m_mode = 0100644;
-            m_linkFlag = TarConstants.LF_NORMAL;
-        }
-
-        m_size = file.length();
-        m_modTime = file.lastModified() / 1000;
-        m_checkSum = 0;
-        m_devMajor = 0;
-        m_devMinor = 0;
-    }
-
-    /**
-     * Construct an entry from an archive's header bytes. File is set to null.
-     *
-     * @param header The header bytes from a tar archive entry.
-     */
-    public TarArchiveEntry( final byte[] header )
-    {
-        this();
-        parseTarHeader( header );
-    }
+    /** Convert millis to seconds */
+    public static final int MILLIS_PER_SECOND = 1000;
 
     /**
      * Construct an empty entry and prepares the header values.
      */
-    private TarArchiveEntry()
-    {
-        m_magic = new StringBuffer( TarConstants.TMAGIC );
-        m_name = new StringBuffer();
-        m_linkName = new StringBuffer();
+    private TarArchiveEntry () {
+        this.magic = new StringBuffer(TMAGIC);
+        this.name = new StringBuffer();
+        this.linkName = new StringBuffer();
 
-        String user = System.getProperty( "user.name", "" );
-        if( user.length() > 31 )
-        {
-            user = user.substring( 0, 31 );
+        String user = System.getProperty("user.name", "");
+
+        if (user.length() > MAX_NAMELEN) {
+            user = user.substring(0, MAX_NAMELEN);
         }
 
-        m_userName = new StringBuffer( user );
-        m_groupName = new StringBuffer( "" );
+        this.userId = 0;
+        this.groupId = 0;
+        this.userName = new StringBuffer(user);
+        this.groupName = new StringBuffer("");
+        this.file = null;
     }
 
     /**
-     * Set this entry's group id.
+     * Construct an entry with only a name. This allows the programmer
+     * to construct the entry's header "by hand". File is set to null.
      *
-     * @param groupId This entry's new group id.
+     * @param name the entry name
      */
-    public void setGroupID( final int groupId )
-    {
-        m_groupID = groupId;
+    public TarArchiveEntry(String name) {
+        this();
+
+        boolean isDir = name.endsWith("/");
+
+        this.devMajor = 0;
+        this.devMinor = 0;
+        this.name = new StringBuffer(name);
+        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
+        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
+        this.userId = 0;
+        this.groupId = 0;
+        this.size = 0;
+        this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
+        this.linkName = new StringBuffer("");
+        this.userName = new StringBuffer("");
+        this.groupName = new StringBuffer("");
+        this.devMajor = 0;
+        this.devMinor = 0;
+
     }
 
     /**
-     * Set this entry's group id.
+     * Construct an entry with a name and a link flag.
      *
-     * @param groupId This entry's new group id.
-     * @deprecated Use setGroupID() instead
-     * @see #setGroupID(int)
+     * @param name the entry name
+     * @param linkFlag the entry link flag.
      */
-    public void setGroupId( final int groupId )
-    {
-        m_groupID = groupId;
+    public TarArchiveEntry(String name, byte linkFlag) {
+        this(name);
+        this.linkFlag = linkFlag;
     }
 
     /**
-     * Set this entry's group name.
+     * Construct an entry for a file. File is set to file, and the
+     * header is constructed from information from the file.
      *
-     * @param groupName This entry's new group name.
+     * @param file The file that the entry represents.
      */
-    public void setGroupName( final String groupName )
-    {
-        m_groupName = new StringBuffer( groupName );
-    }
+    public TarArchiveEntry(File file) {
+        this();
 
-    /**
-     * Set this entry's modification time. The parameter passed to this method
-     * is in "Java time".
-     *
-     * @param time This entry's new modification time.
-     */
-    public void setModTime( final long time )
-    {
-        m_modTime = time / 1000;
-    }
+        this.file = file;
 
-    /**
-     * Set this entry's modification time.
-     *
-     * @param time This entry's new modification time.
-     */
-    public void setModTime( final Date time )
-    {
-        m_modTime = time.getTime() / 1000;
-    }
+        String fileName = file.getPath();
+        String osname = System.getProperty("os.name").toLowerCase(Locale.US);
 
-    /**
-     * Set the mode for this entry
-     *
-     * @param mode The new Mode value
-     */
-    public void setMode( final int mode )
-    {
-        m_mode = mode;
-    }
+        if (osname != null) {
 
-    /**
-     * Set this entry's name.
-     *
-     * @param name This entry's new name.
-     */
-    public void setName( final String name )
-    {
-        m_name = new StringBuffer( name );
-    }
+            // Strip off drive letters!
+            // REVIEW Would a better check be "(File.separator == '\')"?
 
-    /**
-     * Set this entry's file size.
-     *
-     * @param size This entry's new file size.
-     */
-    public void setSize( final long size )
-    {
-        m_size = size;
-    }
+            if (osname.startsWith("windows")) {
+                if (fileName.length() > 2) {
+                    char ch1 = fileName.charAt(0);
+                    char ch2 = fileName.charAt(1);
 
-    /**
-     * Set this entry's user id.
-     *
-     * @param userId This entry's new user id.
-     */
-    public void setUserID( final int userId )
-    {
-        m_userID = userId;
-    }
-
-    /**
-     * Set this entry's user id.
-     *
-     * @param userId This entry's new user id.
-     * @deprecated Use setUserID() instead
-     * @see #setUserID(int)
-     */
-    public void setUserId( final int userId )
-    {
-        m_userID = userId;
-    }
-
-    /**
-     * Set this entry's user name.
-     *
-     * @param userName This entry's new user name.
-     */
-    public void setUserName( final String userName )
-    {
-        m_userName = new StringBuffer( userName );
-    }
-
-    /**
-     * If this entry represents a file, and the file is a directory, return an
-     * array of TarEntries for this entry's children.
-     *
-     * @return An array of TarEntry's for this entry's children.
-     */
-    public TarArchiveEntry[] getDirectoryEntries()
-    {
-        if( null == m_file || !m_file.isDirectory() )
-        {
-            return new TarArchiveEntry[ 0 ];
+                    if (ch2 == ':'
+                            && ((ch1 >= 'a' && ch1 <= 'z')
+                                || (ch1 >= 'A' && ch1 <= 'Z'))) {
+                        fileName = fileName.substring(2);
+                    }
+                }
+            } else if (osname.indexOf("netware") > -1) {
+                int colon = fileName.indexOf(':');
+                if (colon != -1) {
+                    fileName = fileName.substring(colon + 1);
+                }
+            }
         }
 
-        final String[] list = m_file.list();
-        final TarArchiveEntry[] result = new TarArchiveEntry[ list.length ];
+        fileName = fileName.replace(File.separatorChar, '/');
 
-        for( int i = 0; i < list.length; ++i )
-        {
-            result[ i ] = new TarArchiveEntry( new File( m_file, list[ i ] ) );
+        // No absolute pathnames
+        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
+        // so we loop on starting /'s.
+        while (fileName.startsWith("/")) {
+            fileName = fileName.substring(1);
         }
 
-        return result;
+        this.linkName = new StringBuffer("");
+        this.name = new StringBuffer(fileName);
+
+        if (file.isDirectory()) {
+            this.mode = DEFAULT_DIR_MODE;
+            this.linkFlag = LF_DIR;
+
+            if (this.name.charAt(this.name.length() - 1) != '/') {
+                this.name.append("/");
+            }
+        } else {
+            this.mode = DEFAULT_FILE_MODE;
+            this.linkFlag = LF_NORMAL;
+        }
+
+        this.size = file.length();
+        this.modTime = file.lastModified() / MILLIS_PER_SECOND;
+        this.devMajor = 0;
+        this.devMinor = 0;
     }
 
     /**
-     * Get this entry's file.
+     * Construct an entry from an archive's header bytes. File is set
+     * to null.
      *
-     * @return This entry's file.
+     * @param headerBuf The header bytes from a tar archive entry.
      */
-    public File getFile()
-    {
-        return m_file;
+    public TarArchiveEntry(byte[] headerBuf) {
+        this();
+        parseTarHeader(headerBuf);
     }
 
     /**
-     * Get this entry's group id.
+     * Determine if the two entries are equal. Equality is determined
+     * by the header names being equal.
      *
-     * @return This entry's group id.
-     * @deprecated Use getGroupID() instead
-     * @see #getGroupID()
+     * @param it Entry to be checked for equality.
+     * @return True if the entries are equal.
      */
-    public int getGroupId()
-    {
-        return m_groupID;
+    public boolean equals(TarArchiveEntry it) {
+        return getName().equals(it.getName());
     }
 
     /**
-     * Get this entry's group id.
+     * Determine if the two entries are equal. Equality is determined
+     * by the header names being equal.
      *
-     * @return This entry's group id.
+     * @param it Entry to be checked for equality.
+     * @return True if the entries are equal.
      */
-    public int getGroupID()
-    {
-        return m_groupID;
+    public boolean equals(Object it) {
+        if (it == null || getClass() != it.getClass()) {
+            return false;
+        }
+        return equals((TarArchiveEntry) it);
     }
 
     /**
-     * Get this entry's group name.
+     * Hashcodes are based on entry names.
      *
-     * @return This entry's group name.
+     * @return the entry hashcode
      */
-    public String getGroupName()
-    {
-        return m_groupName.toString();
+    public int hashCode() {
+        return getName().hashCode();
     }
 
     /**
-     * Set this entry's modification time.
+     * Determine if the given entry is a descendant of this entry.
+     * Descendancy is determined by the name of the descendant
+     * starting with this entry's name.
      *
-     * @return The ModTime value
+     * @param desc Entry to be checked as a descendent of this.
+     * @return True if entry is a descendant of this.
      */
-    public Date getModTime()
-    {
-        return new Date( m_modTime * 1000 );
-    }
-
-    /**
-     * Get this entry's mode.
-     *
-     * @return This entry's mode.
-     */
-    public int getMode()
-    {
-        return m_mode;
+    public boolean isDescendent(TarArchiveEntry desc) {
+        return desc.getName().startsWith(getName());
     }
 
     /**
@@ -489,41 +318,35 @@
      *
      * @return This entry's name.
      */
-    public String getName()
-    {
-        return m_name.toString();
+    public String getName() {
+        return name.toString();
     }
 
     /**
-     * Get this entry's file size.
+     * Set this entry's name.
      *
-     * @return This entry's file size.
+     * @param name This entry's new name.
      */
-    public long getSize()
-    {
-        return m_size;
+    public void setName(String name) {
+        this.name = new StringBuffer(name);
     }
 
     /**
-     * Get this entry's checksum.
+     * Set the mode for this entry
      *
-     * @return This entry's checksum.
+     * @param mode the mode for this entry
      */
-    public int getCheckSum()
-    {
-        return m_checkSum;
+    public void setMode(int mode) {
+        this.mode = mode;
     }
 
     /**
-     * Get this entry's user id.
+     * Get this entry's link name.
      *
-     * @return This entry's user id.
-     * @deprecated Use getUserID() instead
-     * @see #getUserID()
+     * @return This entry's link name.
      */
-    public int getUserId()
-    {
-        return m_userID;
+    public String getLinkName() {
+        return linkName.toString();
     }
 
     /**
@@ -531,9 +354,35 @@
      *
      * @return This entry's user id.
      */
-    public int getUserID()
-    {
-        return m_userID;
+    public int getUserId() {
+        return userId;
+    }
+
+    /**
+     * Set this entry's user id.
+     *
+     * @param userId This entry's new user id.
+     */
+    public void setUserId(int userId) {
+        this.userId = userId;
+    }
+
+    /**
+     * Get this entry's group id.
+     *
+     * @return This entry's group id.
+     */
+    public int getGroupId() {
+        return groupId;
+    }
+
+    /**
+     * Set this entry's group id.
+     *
+     * @param groupId This entry's new group id.
+     */
+    public void setGroupId(int groupId) {
+        this.groupId = groupId;
     }
 
     /**
@@ -541,22 +390,132 @@
      *
      * @return This entry's user name.
      */
-    public String getUserName()
-    {
-        return m_userName.toString();
+    public String getUserName() {
+        return userName.toString();
     }
 
     /**
-     * Determine if the given entry is a descendant of this entry. Descendancy
-     * is determined by the name of the descendant starting with this entry's
-     * name.
+     * Set this entry's user name.
      *
-     * @param desc Entry to be checked as a descendent of
-     * @return True if entry is a descendant of
+     * @param userName This entry's new user name.
      */
-    public boolean isDescendent( final TarArchiveEntry desc )
-    {
-        return desc.getName().startsWith( getName() );
+    public void setUserName(String userName) {
+        this.userName = new StringBuffer(userName);
+    }
+
+    /**
+     * Get this entry's group name.
+     *
+     * @return This entry's group name.
+     */
+    public String getGroupName() {
+        return groupName.toString();
+    }
+
+    /**
+     * Set this entry's group name.
+     *
+     * @param groupName This entry's new group name.
+     */
+    public void setGroupName(String groupName) {
+        this.groupName = new StringBuffer(groupName);
+    }
+
+    /**
+     * Convenience method to set this entry's group and user ids.
+     *
+     * @param userId This entry's new user id.
+     * @param groupId This entry's new group id.
+     */
+    public void setIds(int userId, int groupId) {
+        setUserId(userId);
+        setGroupId(groupId);
+    }
+
+    /**
+     * Convenience method to set this entry's group and user names.
+     *
+     * @param userName This entry's new user name.
+     * @param groupName This entry's new group name.
+     */
+    public void setNames(String userName, String groupName) {
+        setUserName(userName);
+        setGroupName(groupName);
+    }
+
+    /**
+     * Set this entry's modification time. The parameter passed
+     * to this method is in "Java time".
+     *
+     * @param time This entry's new modification time.
+     */
+    public void setModTime(long time) {
+        modTime = time / MILLIS_PER_SECOND;
+    }
+
+    /**
+     * Set this entry's modification time.
+     *
+     * @param time This entry's new modification time.
+     */
+    public void setModTime(Date time) {
+        modTime = time.getTime() / MILLIS_PER_SECOND;
+    }
+
+    /**
+     * Set this entry's modification time.
+     *
+     * @return time This entry's new modification time.
+     */
+    public Date getModTime() {
+        return new Date(modTime * MILLIS_PER_SECOND);
+    }
+
+    /**
+     * Get this entry's file.
+     *
+     * @return This entry's file.
+     */
+    public File getFile() {
+        return file;
+    }
+
+    /**
+     * Get this entry's mode.
+     *
+     * @return This entry's mode.
+     */
+    public int getMode() {
+        return mode;
+    }
+
+    /**
+     * Get this entry's file size.
+     *
+     * @return This entry's file size.
+     */
+    public long getSize() {
+        return size;
+    }
+
+    /**
+     * Set this entry's file size.
+     *
+     * @param size This entry's new file size.
+     */
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+
+    /**
+     * Indicate if this entry is a GNU long name block
+     *
+     * @return true if this is a long name extension provided by GNU tar
+     */
+    public boolean isGNULongNameEntry() {
+        return linkFlag == LF_GNUTYPE_LONGNAME
+                           && name.toString().equals(GNU_LONGLINK);
     }
 
     /**
@@ -564,20 +523,16 @@
      *
      * @return True if this entry is a directory.
      */
-    public boolean isDirectory()
-    {
-        if( m_file != null )
-        {
-            return m_file.isDirectory();
+    public boolean isDirectory() {
+        if (file != null) {
+            return file.isDirectory();
         }
 
-        if( m_linkFlag == TarConstants.LF_DIR )
-        {
+        if (linkFlag == LF_DIR) {
             return true;
         }
 
-        if( getName().endsWith( "/" ) )
-        {
+        if (getName().endsWith("/")) {
             return true;
         }
 
@@ -585,26 +540,62 @@
     }
 
     /**
-     * Indicate if this entry is a GNU long name block
+     * If this entry represents a file, and the file is a directory, return
+     * an array of TarEntries for this entry's children.
      *
-     * @return true if this is a long name extension provided by GNU tar
+     * @return An array of TarEntry's for this entry's children.
      */
-    public boolean isGNULongNameEntry()
-    {
-        return m_linkFlag == TarConstants.LF_GNUTYPE_LONGNAME &&
-            m_name.toString().equals( TarConstants.GNU_LONGLINK );
+    public TarArchiveEntry[] getDirectoryEntries() {
+        if (file == null || !file.isDirectory()) {
+            return new TarArchiveEntry[0];
+        }
+
+        String[]   list = file.list();
+        TarArchiveEntry[] result = new TarArchiveEntry[list.length];
+
+        for (int i = 0; i < list.length; ++i) {
+            result[i] = new TarArchiveEntry(new File(file, list[i]));
+        }
+
+        return result;
     }
 
     /**
-     * Determine if the two entries are equal. Equality is determined by the
-     * header names being equal.
+     * Write an entry's header information to a header buffer.
      *
-     * @param other Entry to be checked for equality.
-     * @return True if the entries are equal.
+     * @param outbuf The tar entry header buffer to fill in.
      */
-    public boolean equals( final TarArchiveEntry other )
-    {
-        return getName().equals( other.getName() );
+    public void writeEntryHeader(byte[] outbuf) {
+        int offset = 0;
+
+        offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);
+        offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);
+        offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);
+        offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);
+        offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);
+        offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
+
+        int csOffset = offset;
+
+        for (int c = 0; c < CHKSUMLEN; ++c) {
+            outbuf[offset++] = (byte) ' ';
+        }
+
+        outbuf[offset++] = linkFlag;
+        offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);
+        offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);
+        offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);
+        offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);
+        offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);
+        offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);
+
+        while (offset < outbuf.length) {
+            outbuf[offset++] = 0;
+        }
+
+        long chk = TarUtils.computeCheckSum(outbuf);
+
+        TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
     }
 
     /**
@@ -612,74 +603,34 @@
      *
      * @param header The tar entry header buffer to get information from.
      */
-    private void parseTarHeader( final byte[] header )
-    {
+    public void parseTarHeader(byte[] header) {
         int offset = 0;
 
-        m_name = TarUtils.parseName( header, offset, NAMELEN );
+        name = TarUtils.parseName(header, offset, NAMELEN);
         offset += NAMELEN;
-        m_mode = (int)TarUtils.parseOctal( header, offset, TarConstants.MODELEN );
-        offset += TarConstants.MODELEN;
-        m_userID = (int)TarUtils.parseOctal( header, offset, TarConstants.UIDLEN );
-        offset += TarConstants.UIDLEN;
-        m_groupID = (int)TarUtils.parseOctal( header, offset, TarConstants.GIDLEN );
-        offset += TarConstants.GIDLEN;
-        m_size = TarUtils.parseOctal( header, offset, TarConstants.SIZELEN );
-        offset += TarConstants.SIZELEN;
-        m_modTime = TarUtils.parseOctal( header, offset, TarConstants.MODTIMELEN );
-        offset += TarConstants.MODTIMELEN;
-        m_checkSum = (int)TarUtils.parseOctal( header, offset, TarConstants.CHKSUMLEN );
-        offset += TarConstants.CHKSUMLEN;
-        m_linkFlag = header[ offset++ ];
-        m_linkName = TarUtils.parseName( header, offset, NAMELEN );
+        mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
+        offset += MODELEN;
+        userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
+        offset += UIDLEN;
+        groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
+        offset += GIDLEN;
+        size = TarUtils.parseOctal(header, offset, SIZELEN);
+        offset += SIZELEN;
+        modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
+        offset += MODTIMELEN;
+        offset += CHKSUMLEN;
+        linkFlag = header[offset++];
+        linkName = TarUtils.parseName(header, offset, NAMELEN);
         offset += NAMELEN;
-        m_magic = TarUtils.parseName( header, offset, TarConstants.MAGICLEN );
-        offset += TarConstants.MAGICLEN;
-        m_userName = TarUtils.parseName( header, offset, TarConstants.UNAMELEN );
-        offset += TarConstants.UNAMELEN;
-        m_groupName = TarUtils.parseName( header, offset, TarConstants.GNAMELEN );
-        offset += TarConstants.GNAMELEN;
-        m_devMajor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
-        offset += TarConstants.DEVLEN;
-        m_devMinor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
-    }
-
-    /**
-     * Write an entry's header information to a header buffer.
-     *
-     * @param buffer The tar entry header buffer to fill in.
-     */
-    public void writeEntryHeader( final byte[] buffer )
-    {
-        int offset = 0;
-
-        offset = TarUtils.getNameBytes( m_name, buffer, offset, NAMELEN );
-        offset = TarUtils.getOctalBytes( m_mode, buffer, offset, TarConstants.MODELEN );
-        offset = TarUtils.getOctalBytes( m_userID, buffer, offset, TarConstants.UIDLEN );
-        offset = TarUtils.getOctalBytes( m_groupID, buffer, offset, TarConstants.GIDLEN );
-        offset = TarUtils.getLongOctalBytes( m_size, buffer, offset, TarConstants.SIZELEN );
-        offset = TarUtils.getLongOctalBytes( m_modTime, buffer, offset, TarConstants.MODTIMELEN );
-
-        final int checkSumOffset = offset;
-        for( int i = 0; i < TarConstants.CHKSUMLEN; ++i )
-        {
-            buffer[ offset++ ] = (byte)' ';
-        }
-
-        buffer[ offset++ ] = m_linkFlag;
-        offset = TarUtils.getNameBytes( m_linkName, buffer, offset, NAMELEN );
-        offset = TarUtils.getNameBytes( m_magic, buffer, offset, TarConstants.MAGICLEN );
-        offset = TarUtils.getNameBytes( m_userName, buffer, offset, TarConstants.UNAMELEN );
-        offset = TarUtils.getNameBytes( m_groupName, buffer, offset, TarConstants.GNAMELEN );
-        offset = TarUtils.getOctalBytes( m_devMajor, buffer, offset, TarConstants.DEVLEN );
-        offset = TarUtils.getOctalBytes( m_devMinor, buffer, offset, TarConstants.DEVLEN );
-
-        while( offset < buffer.length )
-        {
-            buffer[ offset++ ] = 0;
-        }
-
-        final long checkSum = TarUtils.computeCheckSum( buffer );
-        TarUtils.getCheckSumOctalBytes( checkSum, buffer, checkSumOffset, TarConstants.CHKSUMLEN );
+        magic = TarUtils.parseName(header, offset, MAGICLEN);
+        offset += MAGICLEN;
+        userName = TarUtils.parseName(header, offset, UNAMELEN);
+        offset += UNAMELEN;
+        groupName = TarUtils.parseName(header, offset, GNAMELEN);
+        offset += GNAMELEN;
+        devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+        offset += DEVLEN;
+        devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
     }
 }
+
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
index a72e806..fd16aed 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
@@ -24,65 +24,129 @@
 import java.util.Arrays;
 
 /**
- * The TarBuffer class implements the tar archive concept of a buffered input
- * stream. This concept goes back to the days of blocked tape drives and special
- * io devices. In the Java universe, the only real function that this class
- * performs is to ensure that files have the correct "block" size, or other tars
- * will complain. <p>
+ * The TarBuffer class implements the tar archive concept
+ * of a buffered input stream. This concept goes back to the
+ * days of blocked tape drives and special io devices. In the
+ * Java universe, the only real function that this class
+ * performs is to ensure that files have the correct "block"
+ * size, or other tars will complain.
+ * <p>
+ * You should never have a need to access this class directly.
+ * TarBuffers are created by Tar IO Streams.
  *
- * You should never have a need to access this class directly. TarBuffers are
- * created by Tar IO Streams.
  */
-class TarBuffer
-{
-    public static final int DEFAULT_RECORDSIZE = ( 512 );
-    public static final int DEFAULT_BLOCKSIZE = ( DEFAULT_RECORDSIZE * 20 );
 
-    private byte[] m_blockBuffer;
-    private int m_blockSize;
-    private int m_currBlkIdx;
-    private int m_currRecIdx;
-    private boolean m_debug;
+public class TarBuffer {
 
-    private InputStream m_input;
-    private OutputStream m_output;
-    private int m_recordSize;
-    private int m_recsPerBlock;
+    /** Default record size */
+    public static final int DEFAULT_RCDSIZE = (512);
 
-    public TarBuffer( final InputStream input )
-    {
-        this( input, TarBuffer.DEFAULT_BLOCKSIZE );
+    /** Default block size */
+    public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
+
+    private InputStream     inStream;
+    private OutputStream    outStream;
+    private byte[]          blockBuffer;
+    private int             currBlkIdx;
+    private int             currRecIdx;
+    private int             blockSize;
+    private int             recordSize;
+    private int             recsPerBlock;
+    private boolean         debug;
+
+    /**
+     * Constructor for a TarBuffer on an input stream.
+     * @param inStream the input stream to use
+     */
+    public TarBuffer(InputStream inStream) {
+        this(inStream, TarBuffer.DEFAULT_BLKSIZE);
     }
 
-    public TarBuffer( final InputStream input, final int blockSize )
-    {
-        this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+    /**
+     * Constructor for a TarBuffer on an input stream.
+     * @param inStream the input stream to use
+     * @param blockSize the block size to use
+     */
+    public TarBuffer(InputStream inStream, int blockSize) {
+        this(inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
     }
 
-    public TarBuffer( final InputStream input,
-                      final int blockSize,
-                      final int recordSize )
-    {
-        m_input = input;
-        initialize( blockSize, recordSize );
+    /**
+     * Constructor for a TarBuffer on an input stream.
+     * @param inStream the input stream to use
+     * @param blockSize the block size to use
+     * @param recordSize the record size to use
+     */
+    public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
+        this.inStream = inStream;
+        this.outStream = null;
+
+        this.initialize(blockSize, recordSize);
     }
 
-    public TarBuffer( final OutputStream output )
-    {
-        this( output, TarBuffer.DEFAULT_BLOCKSIZE );
+    /**
+     * Constructor for a TarBuffer on an output stream.
+     * @param outStream the output stream to use
+     */
+    public TarBuffer(OutputStream outStream) {
+        this(outStream, TarBuffer.DEFAULT_BLKSIZE);
     }
 
-    public TarBuffer( final OutputStream output, final int blockSize )
-    {
-        this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+    /**
+     * Constructor for a TarBuffer on an output stream.
+     * @param outStream the output stream to use
+     * @param blockSize the block size to use
+     */
+    public TarBuffer(OutputStream outStream, int blockSize) {
+        this(outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
     }
 
-    public TarBuffer( final OutputStream output,
-                      final int blockSize,
-                      final int recordSize )
-    {
-        m_output = output;
-        initialize( blockSize, recordSize );
+    /**
+     * Constructor for a TarBuffer on an output stream.
+     * @param outStream the output stream to use
+     * @param blockSize the block size to use
+     * @param recordSize the record size to use
+     */
+    public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
+        this.inStream = null;
+        this.outStream = outStream;
+
+        this.initialize(blockSize, recordSize);
+    }
+
+    /**
+     * Initialization common to all constructors.
+     */
+    private void initialize(int blockSize, int recordSize) {
+        this.debug = false;
+        this.blockSize = blockSize;
+        this.recordSize = recordSize;
+        this.recsPerBlock = (this.blockSize / this.recordSize);
+        this.blockBuffer = new byte[this.blockSize];
+
+        if (this.inStream != null) {
+            this.currBlkIdx = -1;
+            this.currRecIdx = this.recsPerBlock;
+        } else {
+            this.currBlkIdx = 0;
+            this.currRecIdx = 0;
+        }
+    }
+
+    /**
+     * Get the TAR Buffer's block size. Blocks consist of multiple records.
+     * @return the block size
+     */
+    public int getBlockSize() {
+        return this.blockSize;
+    }
+
+    /**
+     * Get the TAR Buffer's record size.
+     * @return the record size
+     */
+    public int getRecordSize() {
+        return this.recordSize;
     }
 
     /**
@@ -90,66 +154,20 @@
      *
      * @param debug If true, print debugging output.
      */
-    public void setDebug( final boolean debug )
-    {
-        m_debug = debug;
+    public void setDebug(boolean debug) {
+        this.debug = debug;
     }
 
     /**
-     * Get the TAR Buffer's block size. Blocks consist of multiple records.
-     *
-     * @return The BlockSize value
-     */
-    public int getBlockSize()
-    {
-        return m_blockSize;
-    }
-
-    /**
-     * Get the current block number, zero based.
-     *
-     * @return The current zero based block number.
-     */
-    public int getCurrentBlockNum()
-    {
-        return m_currBlkIdx;
-    }
-
-    /**
-     * Get the current record number, within the current block, zero based.
-     * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
-     *
-     * @return The current zero based record number.
-     */
-    public int getCurrentRecordNum()
-    {
-        return m_currRecIdx - 1;
-    }
-
-    /**
-     * Get the TAR Buffer's record size.
-     *
-     * @return The RecordSize value
-     */
-    public int getRecordSize()
-    {
-        return m_recordSize;
-    }
-
-    /**
-     * Determine if an archive record indicate End of Archive. End of archive is
-     * indicated by a record that consists entirely of null bytes.
+     * Determine if an archive record indicate End of Archive. End of
+     * archive is indicated by a record that consists entirely of null bytes.
      *
      * @param record The record data to check.
-     * @return The EOFRecord value
+     * @return true if the record data is an End of Archive
      */
-    public boolean isEOFRecord( final byte[] record )
-    {
-        final int size = getRecordSize();
-        for( int i = 0; i < size; ++i )
-        {
-            if( record[ i ] != 0 )
-            {
+    public boolean isEOFRecord(byte[] record) {
+        for (int i = 0, sz = getRecordSize(); i < sz; ++i) {
+            if (record[i] != 0) {
                 return false;
             }
         }
@@ -158,270 +176,81 @@
     }
 
     /**
-     * Close the TarBuffer. If this is an output buffer, also flush the current
-     * block before closing.
+     * Skip over a record on the input stream.
+     * @throws IOException on error
      */
-    public void close()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            debug( "TarBuffer.closeBuffer()." );
+    public void skipRecord() throws IOException {
+        if (debug) {
+            System.err.println("SkipRecord: recIdx = " + currRecIdx
+                               + " blkIdx = " + currBlkIdx);
         }
 
-        if( null != m_output )
-        {
-            flushBlock();
+        if (inStream == null) {
+            throw new IOException("reading (via skip) from an output buffer");
+        }
 
-            if( m_output != System.out && m_output != System.err )
-            {
-                m_output.close();
-                m_output = null;
+        if (currRecIdx >= recsPerBlock) {
+            if (!readBlock()) {
+                return;    // UNDONE
             }
         }
-        else if( m_input != null )
-        {
-            if( m_input != System.in )
-            {
-                m_input.close();
-                m_input = null;
-            }
-        }
+
+        currRecIdx++;
     }
 
     /**
      * Read a record from the input stream and return the data.
      *
      * @return The record data.
-     * @exception IOException Description of Exception
+     * @throws IOException on error
      */
-    public byte[] readRecord()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "ReadRecord: recIdx = " + m_currRecIdx +
-                " blkIdx = " + m_currBlkIdx;
-            debug( message );
+    public byte[] readRecord() throws IOException {
+        if (debug) {
+            System.err.println("ReadRecord: recIdx = " + currRecIdx
+                               + " blkIdx = " + currBlkIdx);
         }
 
-        if( null == m_input )
-        {
-            final String message = "reading from an output buffer";
-            throw new IOException( message );
+        if (inStream == null) {
+            throw new IOException("reading from an output buffer");
         }
 
-        if( m_currRecIdx >= m_recsPerBlock )
-        {
-            if( !readBlock() )
-            {
+        if (currRecIdx >= recsPerBlock) {
+            if (!readBlock()) {
                 return null;
             }
         }
 
-        final byte[] result = new byte[ m_recordSize ];
-        System.arraycopy( m_blockBuffer,
-                          ( m_currRecIdx * m_recordSize ),
-                          result,
-                          0,
-                          m_recordSize );
+        byte[] result = new byte[recordSize];
 
-        m_currRecIdx++;
+        System.arraycopy(blockBuffer,
+                         (currRecIdx * recordSize), result, 0,
+                         recordSize);
+
+        currRecIdx++;
 
         return result;
     }
 
     /**
-     * Skip over a record on the input stream.
-     */
-    public void skipRecord()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "SkipRecord: recIdx = " + m_currRecIdx +
-                " blkIdx = " + m_currBlkIdx;
-            debug( message );
-        }
-
-        if( null == m_input )
-        {
-            final String message = "reading (via skip) from an output buffer";
-            throw new IOException( message );
-        }
-
-        if( m_currRecIdx >= m_recsPerBlock )
-        {
-            if( !readBlock() )
-            {
-                return;// UNDONE
-            }
-        }
-
-        m_currRecIdx++;
-    }
-
-    /**
-     * Write an archive record to the archive.
-     *
-     * @param record The record data to write to the archive.
-     */
-    public void writeRecord( final byte[] record )
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "WriteRecord: recIdx = " + m_currRecIdx +
-                " blkIdx = " + m_currBlkIdx;
-            debug( message );
-        }
-
-        if( null == m_output )
-        {
-            final String message = "writing to an input buffer";
-            throw new IOException( message );
-        }
-
-        if( record.length != m_recordSize )
-        {
-            final String message = "record to write has length '" +
-                record.length + "' which is not the record size of '" +
-                m_recordSize + "'";
-            throw new IOException( message );
-        }
-
-        if( m_currRecIdx >= m_recsPerBlock )
-        {
-            writeBlock();
-        }
-
-        System.arraycopy( record,
-                          0,
-                          m_blockBuffer,
-                          ( m_currRecIdx * m_recordSize ),
-                          m_recordSize );
-
-        m_currRecIdx++;
-    }
-
-    /**
-     * Write an archive record to the archive, where the record may be inside of
-     * a larger array buffer. The buffer must be "offset plus record size" long.
-     *
-     * @param buffer The buffer containing the record data to write.
-     * @param offset The offset of the record data within buf.
-     */
-    public void writeRecord( final byte[] buffer, final int offset )
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "WriteRecord: recIdx = " + m_currRecIdx +
-                " blkIdx = " + m_currBlkIdx;
-            debug( message );
-        }
-
-        if( null == m_output )
-        {
-            final String message = "writing to an input buffer";
-            throw new IOException( message );
-        }
-
-        if( ( offset + m_recordSize ) > buffer.length )
-        {
-            final String message = "record has length '" + buffer.length +
-                "' with offset '" + offset + "' which is less than the record size of '" +
-                m_recordSize + "'";
-            throw new IOException( message );
-        }
-
-        if( m_currRecIdx >= m_recsPerBlock )
-        {
-            writeBlock();
-        }
-
-        System.arraycopy( buffer,
-                          offset,
-                          m_blockBuffer,
-                          ( m_currRecIdx * m_recordSize ),
-                          m_recordSize );
-
-        m_currRecIdx++;
-    }
-
-    /**
-     * Flush the current data block if it has any data in it.
-     */
-    private void flushBlock()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "TarBuffer.flushBlock() called.";
-            debug( message );
-        }
-
-        if( m_output == null )
-        {
-            final String message = "writing to an input buffer";
-            throw new IOException( message );
-        }
-
-        if( m_currRecIdx > 0 )
-        {
-            writeBlock();
-        }
-    }
-
-    /**
-     * Initialization common to all constructors.
-     */
-    private void initialize( final int blockSize, final int recordSize )
-    {
-        m_debug = false;
-        m_blockSize = blockSize;
-        m_recordSize = recordSize;
-        m_recsPerBlock = ( m_blockSize / m_recordSize );
-        m_blockBuffer = new byte[ m_blockSize ];
-
-        if( null != m_input )
-        {
-            m_currBlkIdx = -1;
-            m_currRecIdx = m_recsPerBlock;
-        }
-        else
-        {
-            m_currBlkIdx = 0;
-            m_currRecIdx = 0;
-        }
-    }
-
-    /**
      * @return false if End-Of-File, else true
      */
-    private boolean readBlock()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "ReadBlock: blkIdx = " + m_currBlkIdx;
-            debug( message );
+    private boolean readBlock() throws IOException {
+        if (debug) {
+            System.err.println("ReadBlock: blkIdx = " + currBlkIdx);
         }
 
-        if( null == m_input )
-        {
-            final String message = "reading from an output buffer";
-            throw new IOException( message );
+        if (inStream == null) {
+            throw new IOException("reading from an output buffer");
         }
 
-        m_currRecIdx = 0;
+        currRecIdx = 0;
 
         int offset = 0;
-        int bytesNeeded = m_blockSize;
+        int bytesNeeded = blockSize;
 
-        while( bytesNeeded > 0 )
-        {
-            final long numBytes = m_input.read( m_blockBuffer, offset, bytesNeeded );
+        while (bytesNeeded > 0) {
+            long numBytes = inStream.read(blockBuffer, offset,
+                                               bytesNeeded);
 
             //
             // NOTE
@@ -436,15 +265,20 @@
             //
             // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
             //
-            if( numBytes == -1 )
-            {
+            if (numBytes == -1) {
+                if (offset == 0) {
+                    // Ensure that we do not read gigabytes of zeros
+                    // for a corrupt tar file.
+                    // See http://issues.apache.org/bugzilla/show_bug.cgi?id=39924
+                    return false;
+                }
                 // However, just leaving the unread portion of the buffer dirty does
                 // cause problems in some cases.  This problem is described in
                 // http://issues.apache.org/bugzilla/show_bug.cgi?id=29877
                 //
                 // The solution is to fill the unused portion of the buffer with zeros.
 
-                Arrays.fill(m_blockBuffer, offset, offset + bytesNeeded, (byte) 0);
+                Arrays.fill(blockBuffer, offset, offset + bytesNeeded, (byte) 0);
 
                 break;
             }
@@ -452,54 +286,171 @@
             offset += numBytes;
             bytesNeeded -= numBytes;
 
-            if( numBytes != m_blockSize )
-            {
-                if( m_debug )
-                {
-                    System.err.println( "ReadBlock: INCOMPLETE READ "
-                                        + numBytes + " of " + m_blockSize
-                                        + " bytes read." );
+            if (numBytes != blockSize) {
+                if (debug) {
+                    System.err.println("ReadBlock: INCOMPLETE READ "
+                                       + numBytes + " of " + blockSize
+                                       + " bytes read.");
                 }
             }
         }
 
-        m_currBlkIdx++;
+        currBlkIdx++;
 
         return true;
     }
 
     /**
-     * Write a TarBuffer block to the archive.
+     * Get the current block number, zero based.
      *
-     * @exception IOException Description of Exception
+     * @return The current zero based block number.
      */
-    private void writeBlock()
-        throws IOException
-    {
-        if( m_debug )
-        {
-            final String message = "WriteBlock: blkIdx = " + m_currBlkIdx;
-            debug( message );
-        }
-
-        if( null == m_output )
-        {
-            final String message = "writing to an input buffer";
-            throw new IOException( message );
-        }
-
-        m_output.write( m_blockBuffer, 0, m_blockSize );
-        m_output.flush();
-
-        m_currRecIdx = 0;
-        m_currBlkIdx++;
+    public int getCurrentBlockNum() {
+        return currBlkIdx;
     }
 
-    protected void debug( final String message )
-    {
-        if( m_debug )
-        {
-            System.err.println( message );
+    /**
+     * Get the current record number, within the current block, zero based.
+     * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
+     *
+     * @return The current zero based record number.
+     */
+    public int getCurrentRecordNum() {
+        return currRecIdx - 1;
+    }
+
+    /**
+     * Write an archive record to the archive.
+     *
+     * @param record The record data to write to the archive.
+     * @throws IOException on error
+     */
+    public void writeRecord(byte[] record) throws IOException {
+        if (debug) {
+            System.err.println("WriteRecord: recIdx = " + currRecIdx
+                               + " blkIdx = " + currBlkIdx);
+        }
+
+        if (outStream == null) {
+            throw new IOException("writing to an input buffer");
+        }
+
+        if (record.length != recordSize) {
+            throw new IOException("record to write has length '"
+                                  + record.length
+                                  + "' which is not the record size of '"
+                                  + recordSize + "'");
+        }
+
+        if (currRecIdx >= recsPerBlock) {
+            writeBlock();
+        }
+
+        System.arraycopy(record, 0, blockBuffer,
+                         (currRecIdx * recordSize),
+                         recordSize);
+
+        currRecIdx++;
+    }
+
+    /**
+     * Write an archive record to the archive, where the record may be
+     * inside of a larger array buffer. The buffer must be "offset plus
+     * record size" long.
+     *
+     * @param buf The buffer containing the record data to write.
+     * @param offset The offset of the record data within buf.
+     * @throws IOException on error
+     */
+    public void writeRecord(byte[] buf, int offset) throws IOException {
+        if (debug) {
+            System.err.println("WriteRecord: recIdx = " + currRecIdx
+                               + " blkIdx = " + currBlkIdx);
+        }
+
+        if (outStream == null) {
+            throw new IOException("writing to an input buffer");
+        }
+
+        if ((offset + recordSize) > buf.length) {
+            throw new IOException("record has length '" + buf.length
+                                  + "' with offset '" + offset
+                                  + "' which is less than the record size of '"
+                                  + recordSize + "'");
+        }
+
+        if (currRecIdx >= recsPerBlock) {
+            writeBlock();
+        }
+
+        System.arraycopy(buf, offset, blockBuffer,
+                         (currRecIdx * recordSize),
+                         recordSize);
+
+        currRecIdx++;
+    }
+
+    /**
+     * Write a TarBuffer block to the archive.
+     */
+    private void writeBlock() throws IOException {
+        if (debug) {
+            System.err.println("WriteBlock: blkIdx = " + currBlkIdx);
+        }
+
+        if (outStream == null) {
+            throw new IOException("writing to an input buffer");
+        }
+
+        outStream.write(blockBuffer, 0, blockSize);
+        outStream.flush();
+
+        currRecIdx = 0;
+        currBlkIdx++;
+    }
+
+    /**
+     * Flush the current data block if it has any data in it.
+     */
+    private void flushBlock() throws IOException {
+        if (debug) {
+            System.err.println("TarBuffer.flushBlock() called.");
+        }
+
+        if (outStream == null) {
+            throw new IOException("writing to an input buffer");
+        }
+
+        if (currRecIdx > 0) {
+            writeBlock();
+        }
+    }
+
+    /**
+     * Close the TarBuffer. If this is an output buffer, also flush the
+     * current block before closing.
+     * @throws IOException on error
+     */
+    public void close() throws IOException {
+        if (debug) {
+            System.err.println("TarBuffer.closeBuffer().");
+        }
+
+        if (outStream != null) {
+            flushBlock();
+
+            if (outStream != System.out
+                    && outStream != System.err) {
+                outStream.close();
+
+                outStream = null;
+            }
+        } else if (inStream != null) {
+            if (inStream != System.in) {
+                inStream.close();
+
+                inStream = null;
+            }
         }
     }
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
index c56be6e..420dced 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
@@ -20,104 +20,116 @@
 
 /**
  * This interface contains all the definitions used in the package.
+ *
  */
-interface TarConstants
-{
+// CheckStyle:InterfaceIsTypeCheck OFF (bc)
+public interface TarConstants {
+
+    /**
+     * The length of the name field in a header buffer.
+     */
+    int    NAMELEN = 100;
+
     /**
      * The length of the mode field in a header buffer.
      */
-    int MODELEN = 8;
+    int    MODELEN = 8;
 
     /**
      * The length of the user id field in a header buffer.
      */
-    int UIDLEN = 8;
+    int    UIDLEN = 8;
 
     /**
      * The length of the group id field in a header buffer.
      */
-    int GIDLEN = 8;
+    int    GIDLEN = 8;
 
     /**
      * The length of the checksum field in a header buffer.
      */
-    int CHKSUMLEN = 8;
+    int    CHKSUMLEN = 8;
 
     /**
      * The length of the size field in a header buffer.
      */
-    int SIZELEN = 12;
+    int    SIZELEN = 12;
+
+    /**
+     * The maximum size of a file in a tar archive (That's 11 sevens, octal).
+     */
+    long   MAXSIZE = 077777777777L;
 
     /**
      * The length of the magic field in a header buffer.
      */
-    int MAGICLEN = 8;
+    int    MAGICLEN = 8;
 
     /**
      * The length of the modification time field in a header buffer.
      */
-    int MODTIMELEN = 12;
+    int    MODTIMELEN = 12;
 
     /**
      * The length of the user name field in a header buffer.
      */
-    int UNAMELEN = 32;
+    int    UNAMELEN = 32;
 
     /**
      * The length of the group name field in a header buffer.
      */
-    int GNAMELEN = 32;
+    int    GNAMELEN = 32;
 
     /**
      * The length of the devices field in a header buffer.
      */
-    int DEVLEN = 8;
+    int    DEVLEN = 8;
 
     /**
      * LF_ constants represent the "link flag" of an entry, or more commonly,
      * the "entry type". This is the "old way" of indicating a normal file.
      */
-    byte LF_OLDNORM = 0;
+    byte   LF_OLDNORM = 0;
 
     /**
      * Normal file type.
      */
-    byte LF_NORMAL = (byte)'0';
+    byte   LF_NORMAL = (byte) '0';
 
     /**
      * Link file type.
      */
-    byte LF_LINK = (byte)'1';
+    byte   LF_LINK = (byte) '1';
 
     /**
      * Symbolic link file type.
      */
-    byte LF_SYMLINK = (byte)'2';
+    byte   LF_SYMLINK = (byte) '2';
 
     /**
      * Character device file type.
      */
-    byte LF_CHR = (byte)'3';
+    byte   LF_CHR = (byte) '3';
 
     /**
      * Block device file type.
      */
-    byte LF_BLK = (byte)'4';
+    byte   LF_BLK = (byte) '4';
 
     /**
      * Directory file type.
      */
-    byte LF_DIR = (byte)'5';
+    byte   LF_DIR = (byte) '5';
 
     /**
      * FIFO (pipe) file type.
      */
-    byte LF_FIFO = (byte)'6';
+    byte   LF_FIFO = (byte) '6';
 
     /**
      * Contiguous file type.
      */
-    byte LF_CONTIG = (byte)'7';
+    byte   LF_CONTIG = (byte) '7';
 
     /**
      * The magic tag representing a POSIX tar archive.
@@ -137,5 +149,5 @@
     /**
      * Identifies the *next* file on the tape as having a long name.
      */
-    byte LF_GNUTYPE_LONGNAME = (byte)'L';
+    byte LF_GNUTYPE_LONGNAME = (byte) 'L';
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
index 21e2551..bf05051 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
@@ -1,21 +1,26 @@
 /*
- * 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
+ *  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
+ *      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.
+ *  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.
+ *
  */
+
+/*
+ * This package is based on the work done by Timothy Gerard Endres
+ * (time@ice.com) to whom the Ant project is very grateful for his great code.
+ */
+
 package org.apache.commons.compress.archivers.tar;
 
 import java.io.FilterInputStream;
@@ -24,186 +29,85 @@
 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().
+ * 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;
+public class TarInputStream extends FilterInputStream {
+    private static final int SMALL_BUFFER_SIZE = 256;
+    private static final int BUFFER_SIZE = 8 * 1024;
+    private static final int LARGE_BUFFER_SIZE = 32 * 1024;
+    private static final int BYTE_MASK = 0xFF;
+
+    // CheckStyle:VisibilityModifier OFF - bc
+    protected boolean debug;
+    protected boolean hasHitEOF;
+    protected long entrySize;
+    protected long entryOffset;
+    protected byte[] readBuf;
+    protected TarBuffer buffer;
+    protected TarArchiveEntry currEntry;
 
     /**
-     * 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
+     * This contents of this array is not used at all in this class,
+     * it is only here to avoid repreated object creation during calls
+     * to the no-arg read method.
      */
-    public TarInputStream( final InputStream input )
-    {
-        this( input, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
+    protected byte[] oneBuf;
+
+    // CheckStyle:VisibilityModifier ON
+
+    /**
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
+     */
+    public TarInputStream(InputStream is) {
+        this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
     }
 
     /**
-     * Construct a TarInputStream using specified input
-     * stream, block size and default record sizes.
-     *
-     * @param input stream to create TarInputStream from
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
      * @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 );
+    public TarInputStream(InputStream is, int blockSize) {
+        this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
     }
 
     /**
-     * Construct a TarInputStream using specified input
-     * stream, block size and record sizes.
-     *
-     * @param input stream to create TarInputStream from
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
      * @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 );
+    public TarInputStream(InputStream is, int blockSize, int recordSize) {
+        super(is);
 
-        m_buffer = new TarBuffer( input, blockSize, recordSize );
-        m_oneBuf = new byte[ 1 ];
+        this.buffer = new TarBuffer(is, blockSize, recordSize);
+        this.readBuf = null;
+        this.oneBuf = new byte[1];
+        this.debug = false;
+        this.hasHitEOF = false;
     }
 
     /**
      * Sets the debugging flag.
      *
-     * @param debug The new Debug value
+     * @param debug True to turn on debugging.
      */
-    public void setDebug( final boolean debug )
-    {
-        m_debug = debug;
-        m_buffer.setDebug( debug );
+    public void setDebug(boolean debug) {
+        this.debug = debug;
+        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
+     * Closes this stream. Calls the TarBuffer's close() method.
+     * @throws IOException on error
      */
-    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 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();
-
-            // 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;
+    public void close() throws IOException {
+        buffer.close();
     }
 
     /**
@@ -211,68 +115,55 @@
      *
      * @return The TarBuffer record size.
      */
-    public int getRecordSize()
-    {
-        return m_buffer.getRecordSize();
+    public int getRecordSize() {
+        return 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.
+     * 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.
+     * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE
+     * bytes are left in the current entry in the archive.
      *
      * @return The number of available bytes for the current entry.
-     * @exception IOException when an IO error causes operation to fail
+     * @throws IOException for signature
      */
-    public int available()
-        throws IOException
-    {
-        return m_entrySize - m_entryOffset;
+    public int available() throws IOException {
+        if (entrySize - entryOffset > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        }
+        return (int) (entrySize - entryOffset);
     }
 
     /**
-     * Closes this stream. Calls the TarBuffer's close() method.
+     * 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.
      *
-     * @exception IOException when an IO error causes operation to fail
+     * @param numToSkip The number of bytes to skip.
+     * @return the number actually skipped
+     * @throws IOException on error
      */
-    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 )
-            {
+    public long skip(long numToSkip) throws IOException {
+        // REVIEW
+        // This is horribly inefficient, but it ensures that we
+        // properly skip over bytes via the TarBuffer...
+        //
+        byte[] skipBuf = new byte[BUFFER_SIZE];
+        long skip = numToSkip;
+        while (skip > 0) {
+            int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
+            int numRead = read(skipBuf, 0, realSkip);
+            if (numRead == -1) {
                 break;
             }
-
-            output.write( buffer, 0, numRead );
+            skip -= numRead;
         }
-    }
-
-    /**
-     * Since we do not support marking just yet, we do nothing.
-     *
-     * @param markLimit The limit to mark.
-     */
-    public void mark( int markLimit )
-    {
+        return (numToSkip - skip);
     }
 
     /**
@@ -280,189 +171,227 @@
      *
      * @return False.
      */
-    public boolean markSupported()
-    {
+    public boolean markSupported() {
         return false;
     }
 
     /**
-     * Reads a byte from the current tar archive entry. This method simply calls
-     * read( byte[], int, int ).
+     * Since we do not support marking just yet, we do nothing.
      *
-     * @return The byte read, or -1 at EOF.
-     * @exception IOException when an IO error causes operation to fail
+     * @param markLimit The limit to mark.
      */
-    public int read()
-        throws IOException
-    {
-        final int num = read( m_oneBuf, 0, 1 );
-        if( num == -1 )
-        {
-            return num;
-        }
-        else
-        {
-            return (int)m_oneBuf[ 0 ] & 0xFF;
-        }
-    }
-
-    /**
-     * 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;
+    public void mark(int markLimit) {
     }
 
     /**
      * Since we do not support marking just yet, we do nothing.
      */
-    public void reset()
-    {
+    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.
+     * 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.
      *
-     * @param numToSkip The number of bytes to skip.
-     * @exception IOException when an IO error causes operation to fail
+     * @return The next TarEntry in the archive, or null.
+     * @throws IOException on error
      */
-    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 )
-            {
+    public TarArchiveEntry getNextEntry() throws IOException {
+        if (hasHitEOF) {
+            return null;
+        }
+
+        if (currEntry != null) {
+            long numToSkip = entrySize - entryOffset;
+
+            if (debug) {
+                System.err.println("TarInputStream: SKIP currENTRY '"
+                        + currEntry.getName() + "' SZ "
+                        + entrySize + " OFF "
+                        + entryOffset + "  skipping "
+                        + numToSkip + " bytes");
+            }
+
+            if (numToSkip > 0) {
+                skip(numToSkip);
+            }
+
+            readBuf = null;
+        }
+
+        byte[] headerBuf = buffer.readRecord();
+
+        if (headerBuf == null) {
+            if (debug) {
+                System.err.println("READ NULL RECORD");
+            }
+            hasHitEOF = true;
+        } else if (buffer.isEOFRecord(headerBuf)) {
+            if (debug) {
+                System.err.println("READ EOF RECORD");
+            }
+            hasHitEOF = true;
+        }
+
+        if (hasHitEOF) {
+            currEntry = null;
+        } else {
+            currEntry = new TarArchiveEntry(headerBuf);
+
+            if (debug) {
+                System.err.println("TarInputStream: SET CURRENTRY '"
+                        + currEntry.getName()
+                        + "' size = "
+                        + currEntry.getSize());
+            }
+
+            entryOffset = 0;
+
+            entrySize = currEntry.getSize();
+        }
+
+        if (currEntry != null && currEntry.isGNULongNameEntry()) {
+            // read in the name
+            StringBuffer longName = new StringBuffer();
+            byte[] buf = new byte[SMALL_BUFFER_SIZE];
+            int length = 0;
+            while ((length = read(buf)) >= 0) {
+                longName.append(new String(buf, 0, length));
+            }
+            getNextEntry();
+            if (currEntry == null) {
+                // Bugzilla: 40334
+                // Malformed tar file - long entry name not followed by entry
+                return null;
+            }
+            // remove trailing null terminator
+            if (longName.length() > 0
+                && longName.charAt(longName.length() - 1) == 0) {
+                longName.deleteCharAt(longName.length() - 1);
+            }
+            currEntry.setName(longName.toString());
+        }
+
+        return currEntry;
+    }
+
+    /**
+     * 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.
+     * @throws IOException on error
+     */
+    public int read() throws IOException {
+        int num = read(oneBuf, 0, 1);
+        return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
+    }
+
+    /**
+     * 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 buf The buffer into which to place bytes read.
+     * @param offset The offset at which to place bytes read.
+     * @param numToRead The number of bytes to read.
+     * @return The number of bytes read, or -1 at EOF.
+     * @throws IOException on error
+     */
+    public int read(byte[] buf, int offset, int numToRead) throws IOException {
+        int totalRead = 0;
+
+        if (entryOffset >= entrySize) {
+            return -1;
+        }
+
+        if ((numToRead + entryOffset) > entrySize) {
+            numToRead = (int) (entrySize - entryOffset);
+        }
+
+        if (readBuf != null) {
+            int sz = (numToRead > readBuf.length) ? readBuf.length
+                    : numToRead;
+
+            System.arraycopy(readBuf, 0, buf, offset, sz);
+
+            if (sz >= readBuf.length) {
+                readBuf = null;
+            } else {
+                int newLen = readBuf.length - sz;
+                byte[] newBuf = new byte[newLen];
+
+                System.arraycopy(readBuf, sz, newBuf, 0, newLen);
+
+                readBuf = newBuf;
+            }
+
+            totalRead += sz;
+            numToRead -= sz;
+            offset += sz;
+        }
+
+        while (numToRead > 0) {
+            byte[] rec = buffer.readRecord();
+
+            if (rec == null) {
+                // Unexpected EOF!
+                throw new IOException("unexpected EOF with " + numToRead
+                        + " bytes unread");
+            }
+
+            int sz = numToRead;
+            int recLen = rec.length;
+
+            if (recLen > sz) {
+                System.arraycopy(rec, 0, buf, offset, sz);
+
+                readBuf = new byte[recLen - sz];
+
+                System.arraycopy(rec, sz, readBuf, 0, recLen - sz);
+            } else {
+                sz = recLen;
+
+                System.arraycopy(rec, 0, buf, offset, recLen);
+            }
+
+            totalRead += sz;
+            numToRead -= sz;
+            offset += sz;
+        }
+
+        entryOffset += totalRead;
+
+        return totalRead;
+    }
+
+    /**
+     * Copies the contents of the current tar archive entry directly into
+     * an output stream.
+     *
+     * @param out The OutputStream into which to write the entry's data.
+     * @throws IOException on error
+     */
+    public void copyEntryContents(OutputStream out) throws IOException {
+        byte[] buf = new byte[LARGE_BUFFER_SIZE];
+
+        while (true) {
+            int numRead = read(buf, 0, buf.length);
+
+            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 );
+            out.write(buf, 0, numRead);
         }
     }
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
index bf73b9b..49f9d87 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
@@ -24,120 +24,125 @@
 import java.io.OutputStream;
 
 /**
- * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are
- * provided to put entries, and then write their contents by writing to this
- * stream using write().
+ * The TarOutputStream writes a UNIX tar archive as an OutputStream.
+ * Methods are provided to put entries, and then write their contents
+ * by writing to this stream using write().
+ *
  */
-public class TarOutputStream
-    extends FilterOutputStream
-{
-    /**
-     * Flag to indicate that an error should be generated if
-     * an attempt is made to write an entry that exceeds the 100 char
-     * POSIX limit.
-     */
+public class TarOutputStream extends FilterOutputStream {
+    /** Fail if a long file name is required in the archive. */
     public static final int LONGFILE_ERROR = 0;
 
-    /**
-     * Flag to indicate that entry name should be truncated if
-     * an attempt is made to write an entry that exceeds the 100 char
-     * POSIX limit.
-     */
+    /** Long paths will be truncated in the archive. */
     public static final int LONGFILE_TRUNCATE = 1;
 
-    /**
-     * Flag to indicate that entry name should be formatted
-     * according to GNU tar extension if an attempt is made
-     * to write an entry that exceeds the 100 char POSIX
-     * limit. Note that this makes the jar unreadable by
-     * non-GNU tar commands.
-     */
+    /** GNU tar extensions are used to store long file names in the archive. */
     public static final int LONGFILE_GNU = 2;
 
-    private int m_longFileMode = LONGFILE_ERROR;
-    private byte[] m_assemBuf;
-    private int m_assemLen;
-    private TarBuffer m_buffer;
-    private int m_currBytes;
-    private int m_currSize;
+    // CheckStyle:VisibilityModifier OFF - bc
+    protected boolean   debug;
+    protected long      currSize;
+    protected String    currName;
+    protected long      currBytes;
+    protected byte[]    oneBuf;
+    protected byte[]    recordBuf;
+    protected int       assemLen;
+    protected byte[]    assemBuf;
+    protected TarBuffer buffer;
+    protected int       longFileMode = LONGFILE_ERROR;
+    // CheckStyle:VisibilityModifier ON
 
-    private byte[] m_oneBuf;
-    private byte[] m_recordBuf;
+    private boolean closed = false;
 
     /**
-     * Construct a TarOutputStream using specified input
-     * stream and default block and record sizes.
-     *
-     * @param output stream to create TarOutputStream from
-     * @see TarBuffer#DEFAULT_BLOCKSIZE
-     * @see TarBuffer#DEFAULT_RECORDSIZE
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
      */
-    public TarOutputStream( final OutputStream output )
-    {
-        this( output, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
+    public TarOutputStream(OutputStream os) {
+        this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
     }
 
     /**
-     * Construct a TarOutputStream using specified input
-     * stream, block size and default record sizes.
-     *
-     * @param output stream to create TarOutputStream from
-     * @param blockSize the block size
-     * @see TarBuffer#DEFAULT_RECORDSIZE
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
+     * @param blockSize the block size to use
      */
-    public TarOutputStream( final OutputStream output,
-                            final int blockSize )
-    {
-        this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+    public TarOutputStream(OutputStream os, int blockSize) {
+        this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
     }
 
     /**
-     * Construct a TarOutputStream using specified input
-     * stream, block size and record sizes.
-     *
-     * @param output stream to create TarOutputStream from
-     * @param blockSize the block size
-     * @param recordSize the record size
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
+     * @param blockSize the block size to use
+     * @param recordSize the record size to use
      */
-    public TarOutputStream( final OutputStream output,
-                            final int blockSize,
-                            final int recordSize )
-    {
-        super( output );
+    public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
+        super(os);
 
-        m_buffer = new TarBuffer( output, blockSize, recordSize );
-        m_assemLen = 0;
-        m_assemBuf = new byte[ recordSize ];
-        m_recordBuf = new byte[ recordSize ];
-        m_oneBuf = new byte[ 1 ];
+        this.buffer = new TarBuffer(os, blockSize, recordSize);
+        this.debug = false;
+        this.assemLen = 0;
+        this.assemBuf = new byte[recordSize];
+        this.recordBuf = new byte[recordSize];
+        this.oneBuf = new byte[1];
+    }
+
+    /**
+     * Set the long file mode.
+     * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
+     * This specifies the treatment of long file names (names >= TarConstants.NAMELEN).
+     * Default is LONGFILE_ERROR.
+     * @param longFileMode the mode to use
+     */
+    public void setLongFileMode(int longFileMode) {
+        this.longFileMode = longFileMode;
+    }
+
+
+    /**
+     * Sets the debugging flag.
+     *
+     * @param debugF True to turn on debugging.
+     */
+    public void setDebug(boolean debugF) {
+        this.debug = debugF;
     }
 
     /**
      * Sets the debugging flag in this stream's TarBuffer.
      *
-     * @param debug The new BufferDebug value
+     * @param debug True to turn on debugging.
      */
-    public void setBufferDebug( boolean debug )
-    {
-        m_buffer.setDebug( debug );
+    public void setBufferDebug(boolean debug) {
+        buffer.setDebug(debug);
     }
 
     /**
-     * Set the mode used to work with entrys exceeding
-     * 100 chars (and thus break the POSIX standard).
-     * Must be one of the LONGFILE_* constants.
-     *
-     * @param longFileMode the mode
+     * Ends the TAR archive without closing the underlying OutputStream.
+     * The result is that the two EOF records of nulls are written.
+     * @throws IOException on error
      */
-    public void setLongFileMode( final int longFileMode )
-    {
-        if( LONGFILE_ERROR != longFileMode &&
-            LONGFILE_GNU != longFileMode &&
-            LONGFILE_TRUNCATE != longFileMode )
-        {
-            throw new IllegalArgumentException( "longFileMode" );
+    public void finish() throws IOException {
+        // See Bugzilla 28776 for a discussion on this
+        // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
+        writeEOFRecord();
+        writeEOFRecord();
+    }
+
+    /**
+     * Ends the TAR archive and closes the underlying OutputStream.
+     * This means that finish() is called followed by calling the
+     * TarBuffer's close().
+     * @throws IOException on error
+     */
+    public void close() throws IOException {
+        if (!closed) {
+            finish();
+            buffer.close();
+            out.close();
+            closed = true;
         }
-        m_longFileMode = longFileMode;
     }
 
     /**
@@ -145,202 +150,133 @@
      *
      * @return The TarBuffer record size.
      */
-    public int getRecordSize()
-    {
-        return m_buffer.getRecordSize();
+    public int getRecordSize() {
+        return buffer.getRecordSize();
     }
 
     /**
-     * Ends the TAR archive and closes the underlying OutputStream. This means
-     * that finish() is called followed by calling the TarBuffer's close().
+     * Put an entry on the output stream. This writes the entry's
+     * header record and positions the output stream for writing
+     * the contents of the entry. Once this method is called, the
+     * stream is ready for calls to write() to write the entry's
+     * contents. Once the contents are written, closeEntry()
+     * <B>MUST</B> be called to ensure that all buffered data
+     * is completely written to the output stream.
      *
-     * @exception IOException when an IO error causes operation to fail
+     * @param entry The TarEntry to be written to the archive.
+     * @throws IOException on error
      */
-    public void close()
-        throws IOException
-    {
-        finish();
-        m_buffer.close();
-    }
+    public void putNextEntry(TarArchiveEntry entry) throws IOException {
+        if (entry.getName().length() >= TarConstants.NAMELEN) {
 
-    /**
-     * Close an entry. This method MUST be called for all file entries that
-     * contain data. The reason is that we must buffer data written to the
-     * stream in order to satisfy the buffer's record based writes. Thus, there
-     * may be data fragments still being assembled that must be written to the
-     * output stream before this entry is closed and the next entry written.
-     *
-     * @exception IOException when an IO error causes operation to fail
-     */
-    public void closeEntry()
-        throws IOException
-    {
-        if( m_assemLen > 0 )
-        {
-            for( int i = m_assemLen; i < m_assemBuf.length; ++i )
-            {
-                m_assemBuf[ i ] = 0;
-            }
-
-            m_buffer.writeRecord( m_assemBuf );
-
-            m_currBytes += m_assemLen;
-            m_assemLen = 0;
-        }
-
-        if( m_currBytes < m_currSize )
-        {
-            final String message = "entry closed at '" + m_currBytes +
-                "' before the '" + m_currSize +
-                "' bytes specified in the header were written";
-            throw new IOException( message );
-        }
-    }
-
-    /**
-     * Ends the TAR archive without closing the underlying OutputStream. The
-     * result is that the EOF record of nulls is written.
-     *
-     * @exception IOException when an IO error causes operation to fail
-     */
-    public void finish()
-        throws IOException
-    {
-        writeEOFRecord();
-    }
-
-    /**
-     * Put an entry on the output stream. This writes the entry's header record
-     * and positions the output stream for writing the contents of the entry.
-     * Once this method is called, the stream is ready for calls to write() to
-     * write the entry's contents. Once the contents are written, closeEntry()
-     * <B>MUST</B> be called to ensure that all buffered data is completely
-     * written to the output stream.
-     *
-     * The entry must be 0 terminated. Maximum filename is 99 chars, 
-     * according to V7 specification.
-     * 
-     * @param entry The TarArchiveEntry to be written to the archive.
-     * @exception IOException when an IO error causes operation to fail
-     */
-    public void putNextEntry( final TarArchiveEntry entry )
-        throws IOException
-    {
-        if( entry.getName().length() > TarArchiveEntry.NAMELEN )
-        {
-            if( m_longFileMode == LONGFILE_GNU )
-            {
-                // create a TarArchiveEntry for the LongLink, the contents
+            if (longFileMode == LONGFILE_GNU) {
+                // create a TarEntry for the LongLink, the contents
                 // of which are the entry's name
-                final TarArchiveEntry longLinkEntry =
-                    new TarArchiveEntry( TarConstants.GNU_LONGLINK,
-                                  TarConstants.LF_GNUTYPE_LONGNAME );
+                TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK,
+                                                      TarConstants.LF_GNUTYPE_LONGNAME);
 
-                longLinkEntry.setSize( entry.getName().length() + 1);
-                putNextEntry( longLinkEntry );
-                write( entry.getName().getBytes() );
-                write( 0 );
+                longLinkEntry.setSize(entry.getName().length() + 1);
+                putNextEntry(longLinkEntry);
+                write(entry.getName().getBytes());
+                write(0);
                 closeEntry();
-            }
-            else if( m_longFileMode != LONGFILE_TRUNCATE )
-            {
-                final String message = "file name '" + entry.getName() +
-                    "' is too long ( > " + TarArchiveEntry.NAMELEN + " bytes)";
-                throw new IOException( message );
+            } else if (longFileMode != LONGFILE_TRUNCATE) {
+                throw new RuntimeException("file name '" + entry.getName()
+                                             + "' is too long ( > "
+                                             + TarConstants.NAMELEN + " bytes)");
             }
         }
 
-        entry.writeEntryHeader( m_recordBuf );
-        m_buffer.writeRecord( m_recordBuf );
+        entry.writeEntryHeader(recordBuf);
+        buffer.writeRecord(recordBuf);
 
-        m_currBytes = 0;
+        currBytes = 0;
 
-        if( entry.isDirectory() )
-        {
-            m_currSize = 0;
+        if (entry.isDirectory()) {
+            currSize = 0;
+        } else {
+            currSize = entry.getSize();
         }
-        else
-        {
-            m_currSize = (int)entry.getSize();
-        }
+        currName = entry.getName();
     }
 
     /**
-     * Copies the contents of the specified stream into current tar
-     * archive entry.
-     *
-     * @param input The InputStream from which to read entrys data
-     * @exception IOException when an IO error causes operation to fail
+     * Close an entry. This method MUST be called for all file
+     * entries that contain data. The reason is that we must
+     * buffer data written to the stream in order to satisfy
+     * the buffer's record based writes. Thus, there may be
+     * data fragments still being assembled that must be written
+     * to the output stream before this entry is closed and the
+     * next entry written.
+     * @throws IOException on error
      */
-    public void copyEntryContents( final InputStream input )
-        throws IOException
-    {
-        final byte[] buffer = new byte[ 32 * 1024 ];
-        while( true )
-        {
-            final int numRead = input.read( buffer, 0, buffer.length );
-            if( numRead == -1 )
-            {
-                break;
+    public void closeEntry() throws IOException {
+        if (assemLen > 0) {
+            for (int i = assemLen; i < assemBuf.length; ++i) {
+                assemBuf[i] = 0;
             }
 
-            write( buffer, 0, numRead );
+            buffer.writeRecord(assemBuf);
+
+            currBytes += assemLen;
+            assemLen = 0;
+        }
+
+        if (currBytes < currSize) {
+            throw new IOException("entry '" + currName + "' closed at '"
+                                  + currBytes
+                                  + "' before the '" + currSize
+                                  + "' bytes specified in the header were written");
         }
     }
 
     /**
-     * Writes a byte to the current tar archive entry. This method simply calls
-     * read( byte[], int, int ).
+     * Writes a byte to the current tar archive entry.
      *
-     * @param data The byte written.
-     * @exception IOException when an IO error causes operation to fail
+     * This method simply calls read( byte[], int, int ).
+     *
+     * @param b The byte written.
+     * @throws IOException on error
      */
-    public void write( final int data )
-        throws IOException
-    {
-        m_oneBuf[ 0 ] = (byte)data;
+    public void write(int b) throws IOException {
+        oneBuf[0] = (byte) b;
 
-        write( m_oneBuf, 0, 1 );
+        write(oneBuf, 0, 1);
     }
 
     /**
-     * Writes bytes to the current tar archive entry. This method simply calls
-     * write( byte[], int, int ).
+     * Writes bytes to the current tar archive entry.
      *
-     * @param buffer The buffer to write to the archive.
-     * @exception IOException when an IO error causes operation to fail
+     * This method simply calls write( byte[], int, int ).
+     *
+     * @param wBuf The buffer to write to the archive.
+     * @throws IOException on error
      */
-    public void write( final byte[] buffer )
-        throws IOException
-    {
-        write( buffer, 0, buffer.length );
+    public void write(byte[] wBuf) throws IOException {
+        write(wBuf, 0, wBuf.length);
     }
 
     /**
-     * Writes bytes to the current tar archive entry. This method is aware of
-     * the current entry and will throw an exception if you attempt to write
-     * bytes past the length specified for the current entry. The method is also
-     * (painfully) aware of the record buffering required by TarBuffer, and
-     * manages buffers that are not a multiple of recordsize in length,
-     * including assembling records from small buffers.
+     * Writes bytes to the current tar archive entry. This method
+     * is aware of the current entry and will throw an exception if
+     * you attempt to write bytes past the length specified for the
+     * current entry. The method is also (painfully) aware of the
+     * record buffering required by TarBuffer, and manages buffers
+     * that are not a multiple of recordsize in length, including
+     * assembling records from small buffers.
      *
-     * @param buffer The buffer to write to the archive.
-     * @param offset The offset in the buffer from which to get bytes.
-     * @param count The number of bytes to write.
-     * @exception IOException when an IO error causes operation to fail
+     * @param wBuf The buffer to write to the archive.
+     * @param wOffset The offset in the buffer from which to get bytes.
+     * @param numToWrite The number of bytes to write.
+     * @throws IOException on error
      */
-    public void write( final byte[] buffer,
-                       final int offset,
-                       final int count )
-        throws IOException
-    {
-        int position = offset;
-        int numToWrite = count;
-        if( ( m_currBytes + numToWrite ) > m_currSize )
-        {
-            final String message = "request to write '" + numToWrite +
-                "' bytes exceeds size in header of '" + m_currSize + "' bytes";
-            throw new IOException( message );
+    public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
+        if ((currBytes + numToWrite) > currSize) {
+            throw new IOException("request to write '" + numToWrite
+                                  + "' bytes exceeds size in header of '"
+                                  + currSize + "' bytes for entry '"
+                                  + currName + "'");
+
             //
             // We have to deal with assembly!!!
             // The programmer can be writing little 32 byte chunks for all
@@ -350,30 +286,26 @@
             //
         }
 
-        if( m_assemLen > 0 )
-        {
-            if( ( m_assemLen + numToWrite ) >= m_recordBuf.length )
-            {
-                final int length = m_recordBuf.length - m_assemLen;
+        if (assemLen > 0) {
+            if ((assemLen + numToWrite) >= recordBuf.length) {
+                int aLen = recordBuf.length - assemLen;
 
-                System.arraycopy( m_assemBuf, 0, m_recordBuf, 0,
-                                  m_assemLen );
-                System.arraycopy( buffer, position, m_recordBuf,
-                                  m_assemLen, length );
-                m_buffer.writeRecord( m_recordBuf );
+                System.arraycopy(assemBuf, 0, recordBuf, 0,
+                                 assemLen);
+                System.arraycopy(wBuf, wOffset, recordBuf,
+                                 assemLen, aLen);
+                buffer.writeRecord(recordBuf);
 
-                m_currBytes += m_recordBuf.length;
-                position += length;
-                numToWrite -= length;
-                m_assemLen = 0;
-            }
-            else
-            {
-                System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
-                                  numToWrite );
+                currBytes += recordBuf.length;
+                wOffset += aLen;
+                numToWrite -= aLen;
+                assemLen = 0;
+            } else {
+                System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
+                                 numToWrite);
 
-                position += numToWrite;
-                m_assemLen += numToWrite;
+                wOffset += numToWrite;
+                assemLen += numToWrite;
                 numToWrite -= numToWrite;
             }
         }
@@ -383,42 +315,35 @@
         // o An empty "assemble" buffer.
         // o No bytes to write (numToWrite == 0)
         //
-        while( numToWrite > 0 )
-        {
-            if( numToWrite < m_recordBuf.length )
-            {
-                System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
-                                  numToWrite );
+        while (numToWrite > 0) {
+            if (numToWrite < recordBuf.length) {
+                System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
+                                 numToWrite);
 
-                m_assemLen += numToWrite;
+                assemLen += numToWrite;
 
                 break;
             }
 
-            m_buffer.writeRecord( buffer, position );
+            buffer.writeRecord(wBuf, wOffset);
 
-            int num = m_recordBuf.length;
+            int num = recordBuf.length;
 
-            m_currBytes += num;
+            currBytes += num;
             numToWrite -= num;
-            position += num;
+            wOffset += num;
         }
     }
 
     /**
-     * Write an EOF (end of archive) record to the tar archive. An EOF record
-     * consists of a record of all zeros.
-     *
-     * @exception IOException when an IO error causes operation to fail
+     * Write an EOF (end of archive) record to the tar archive.
+     * An EOF record consists of a record of all zeros.
      */
-    private void writeEOFRecord()
-        throws IOException
-    {
-        for( int i = 0; i < m_recordBuf.length; ++i )
-        {
-            m_recordBuf[ i ] = 0;
+    private void writeEOFRecord() throws IOException {
+        for (int i = 0; i < recordBuf.length; ++i) {
+            recordBuf[i] = 0;
         }
 
-        m_buffer.writeRecord( m_recordBuf );
+        buffer.writeRecord(recordBuf);
     }
 }
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index 523f9f7..05faa64 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -20,142 +20,49 @@
 
 /**
  * This class provides static utility methods to work with byte streams.
+ *
  */
-class TarUtils
-{
-    /**
-     * Parse the checksum octal integer from a header buffer.
-     *
-     * @param offset The offset into the buffer from which to parse.
-     * @param length The number of header bytes to parse.
-     * @param value Description of Parameter
-     * @param buf Description of Parameter
-     * @return The integer value of the entry's checksum.
-     */
-    public static int getCheckSumOctalBytes( final long value,
-                                             final byte[] buf,
-                                             final int offset,
-                                             final int length )
-    {
-        getOctalBytes( value, buf, offset, length );
+// CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
+public class TarUtils {
 
-        buf[ offset + length - 1 ] = (byte)' ';
-        buf[ offset + length - 2 ] = 0;
-
-        return offset + length;
-    }
+    private static final int BYTE_MASK = 255;
 
     /**
-     * Parse an octal long integer from a header buffer.
+     * Parse an octal string from a header buffer. This is used for the
+     * file permission mode value.
      *
+     * @param header The header buffer from which to parse.
      * @param offset The offset into the buffer from which to parse.
      * @param length The number of header bytes to parse.
-     * @param value Description of Parameter
-     * @param buf Description of Parameter
-     * @return The long value of the octal bytes.
+     * @return The long value of the octal string.
      */
-    public static int getLongOctalBytes( final long value,
-                                         final byte[] buf,
-                                         final int offset,
-                                         final int length )
-    {
-        byte[] temp = new byte[ length + 1 ];
+    public static long parseOctal(byte[] header, int offset, int length) {
+        long    result = 0;
+        boolean stillPadding = true;
+        int     end = offset + length;
 
-        getOctalBytes( value, temp, 0, length + 1 );
-        System.arraycopy( temp, 0, buf, offset, length );
-
-        return offset + length;
-    }
-
-    /**
-     * Determine the number of bytes in an entry name.
-     *
-     * @param offset The offset into the buffer from which to parse.
-     * @param length The number of header bytes to parse.
-     * @param name Description of Parameter
-     * @param buffer Description of Parameter
-     * @return The number of bytes in a header's entry name.
-     */
-    public static int getNameBytes( final StringBuffer name,
-                                    final byte[] buffer,
-                                    final int offset,
-                                    final int length )
-    {
-        int i;
-
-        for( i = 0; i < length && i < name.length(); ++i )
-        {
-            buffer[ offset + i ] = (byte)name.charAt( i );
-        }
-
-        for( ; i < length; ++i )
-        {
-            buffer[ offset + i ] = 0;
-        }
-
-        return offset + length;
-    }
-
-    /**
-     * Parse an octal integer from a header buffer.
-     *
-     * @param offset The offset into the buffer from which to parse.
-     * @param length The number of header bytes to parse.
-     * @return The integer value of the octal bytes.
-     */
-    public static int getOctalBytes( final long value,
-                                     final byte[] buffer,
-                                     final int offset,
-                                     final int length )
-    {
-        int idx = length - 1;
-
-        buffer[ offset + idx ] = 0;
-        --idx;
-        buffer[ offset + idx ] = (byte)' ';
-        --idx;
-
-        if( value == 0 )
-        {
-            buffer[ offset + idx ] = (byte)'0';
-            --idx;
-        }
-        else
-        {
-            long val = value;
-            while( idx >= 0 && val > 0 )
-            {
-                buffer[ offset + idx ] = (byte)( (byte)'0' + (byte)( val & 7 ) );
-                val = val >> 3;
-                idx--;
+        for (int i = offset; i < end; ++i) {
+            if (header[i] == 0) {
+                break;
             }
+
+            if (header[i] == (byte) ' ' || header[i] == '0') {
+                if (stillPadding) {
+                    continue;
+                }
+
+                if (header[i] == (byte) ' ') {
+                    break;
+                }
+            }
+
+            stillPadding = false;
+            // CheckStyle:MagicNumber OFF
+            result = (result << 3) + (header[i] - '0');
+            // CheckStyle:MagicNumber ON
         }
 
-        while( idx >= 0 )
-        {
-            buffer[ offset + idx ] = (byte)' ';
-            idx--;
-        }
-
-        return offset + length;
-    }
-
-    /**
-     * Compute the checksum of a tar entry header.
-     *
-     * @param buffer The tar entry's header buffer.
-     * @return The computed checksum.
-     */
-    public static long computeCheckSum( final byte[] buffer )
-    {
-        long sum = 0;
-
-        for( int i = 0; i < buffer.length; ++i )
-        {
-            sum += 255 & buffer[ i ];
-        }
-
-        return sum;
+        return result;
     }
 
     /**
@@ -166,67 +73,129 @@
      * @param length The number of header bytes to parse.
      * @return The header's entry name.
      */
-    public static StringBuffer parseName( final byte[] header,
-                                          final int offset,
-                                          final int length )
-    {
-        StringBuffer result = new StringBuffer( length );
-        int end = offset + length;
+    public static StringBuffer parseName(byte[] header, int offset, int length) {
+        StringBuffer result = new StringBuffer(length);
+        int          end = offset + length;
 
-        for( int i = offset; i < end; ++i )
-        {
-            if( header[ i ] == 0 )
-            {
+        for (int i = offset; i < end; ++i) {
+            if (header[i] == 0) {
                 break;
             }
 
-            result.append( (char)header[ i ] );
+            result.append((char) header[i]);
         }
 
         return result;
     }
 
     /**
-     * Parse an octal string from a header buffer. This is used for the file
-     * permission mode value.
+     * Determine the number of bytes in an entry name.
      *
-     * @param header The header buffer from which to parse.
+     * @param name The header name from which to parse.
+     * @param buf The buffer from which to parse.
      * @param offset The offset into the buffer from which to parse.
      * @param length The number of header bytes to parse.
-     * @return The long value of the octal string.
+     * @return The number of bytes in a header's entry name.
      */
-    public static long parseOctal( final byte[] header,
-                                   final int offset,
-                                   final int length )
-    {
-        long result = 0;
-        boolean stillPadding = true;
-        int end = offset + length;
+    public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+        int i;
 
-        for( int i = offset; i < end; ++i )
-        {
-            if( header[ i ] == 0 )
-            {
-                break;
-            }
-
-            if( header[ i ] == (byte)' ' || header[ i ] == '0' )
-            {
-                if( stillPadding )
-                {
-                    continue;
-                }
-
-                if( header[ i ] == (byte)' ' )
-                {
-                    break;
-                }
-            }
-
-            stillPadding = false;
-            result = ( result << 3 ) + ( header[ i ] - '0' );
+        for (i = 0; i < length && i < name.length(); ++i) {
+            buf[offset + i] = (byte) name.charAt(i);
         }
 
-        return result;
+        for (; i < length; ++i) {
+            buf[offset + i] = 0;
+        }
+
+        return offset + length;
+    }
+
+    /**
+     * Parse an octal integer from a header buffer.
+     *
+     * @param value The header value
+     * @param buf The buffer from which to parse.
+     * @param offset The offset into the buffer from which to parse.
+     * @param length The number of header bytes to parse.
+     * @return The integer value of the octal bytes.
+     */
+    public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+        int    idx = length - 1;
+
+        buf[offset + idx] = 0;
+        --idx;
+        buf[offset + idx] = (byte) ' ';
+        --idx;
+
+        if (value == 0) {
+            buf[offset + idx] = (byte) '0';
+            --idx;
+        } else {
+            for (long val = value; idx >= 0 && val > 0; --idx) {
+                // CheckStyle:MagicNumber OFF
+                buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
+                val = val >> 3;
+                // CheckStyle:MagicNumber ON
+            }
+        }
+
+        for (; idx >= 0; --idx) {
+            buf[offset + idx] = (byte) ' ';
+        }
+
+        return offset + length;
+    }
+
+    /**
+     * Parse an octal long integer from a header buffer.
+     *
+     * @param value The header value
+     * @param buf The buffer from which to parse.
+     * @param offset The offset into the buffer from which to parse.
+     * @param length The number of header bytes to parse.
+     * @return The long value of the octal bytes.
+     */
+    public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+        byte[] temp = new byte[length + 1];
+
+        getOctalBytes(value, temp, 0, length + 1);
+        System.arraycopy(temp, 0, buf, offset, length);
+
+        return offset + length;
+    }
+
+    /**
+     * Parse the checksum octal integer from a header buffer.
+     *
+     * @param value The header value
+     * @param buf The buffer from which to parse.
+     * @param offset The offset into the buffer from which to parse.
+     * @param length The number of header bytes to parse.
+     * @return The integer value of the entry's checksum.
+     */
+    public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+        getOctalBytes(value, buf, offset, length);
+
+        buf[offset + length - 1] = (byte) ' ';
+        buf[offset + length - 2] = 0;
+
+        return offset + length;
+    }
+
+    /**
+     * Compute the checksum of a tar entry header.
+     *
+     * @param buf The tar entry's header buffer.
+     * @return The computed checksum.
+     */
+    public static long computeCheckSum(byte[] buf) {
+        long sum = 0;
+
+        for (int i = 0; i < buf.length; ++i) {
+            sum += BYTE_MASK & buf[i];
+        }
+
+        return sum;
     }
 }
diff --git a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
index 0187d03..2e37952 100644
--- a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
+++ b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
@@ -38,8 +38,8 @@
         final TarArchiveEntry entry = new TarArchiveEntry("testdata/test1.xml");
         entry.setModTime(0);
         entry.setSize(file1.length());
-        entry.setUserID(0);
-        entry.setGroupID(0);
+        entry.setUserId(0);
+        entry.setGroupId(0);
         entry.setUserName("avalon");
         entry.setGroupName("excalibur");
         entry.setMode(0100000);
@@ -61,8 +61,8 @@
         final TarArchiveEntry entry = new TarArchiveEntry(name);
         entry.setModTime(0);
         entry.setSize(file1.length());
-        entry.setUserID(0);
-        entry.setGroupID(0);
+        entry.setUserId(0);
+        entry.setGroupId(0);
         entry.setUserName("avalon");
         entry.setGroupName("excalibur");
         entry.setMode(0100000);
@@ -81,8 +81,8 @@
         	final TarArchiveEntry entry2 = new TarArchiveEntry(toLongName);
         	entry2.setModTime(0);
         	entry2.setSize(file1.length());
-        	entry2.setUserID(0);
-        	entry2.setGroupID(0);
+        	entry2.setUserId(0);
+        	entry2.setGroupId(0);
         	entry2.setUserName("avalon");
         	entry2.setGroupName("excalibur");
         	entry2.setMode(0100000);