added destination option for GetDep
authoradam <adam@megacz.com>
Fri, 25 Mar 2005 06:31:34 +0000 (06:31 +0000)
committeradam <adam@megacz.com>
Fri, 25 Mar 2005 06:31:34 +0000 (06:31 +0000)
darcs-hash:20050325063134-5007d-91143057b315f538ac156da065821859efc57aba.gz

src/org/ibex/util/GetDep.java [new file with mode: 0644]
src/org/ibex/util/Tar.java [new file with mode: 0644]

diff --git a/src/org/ibex/util/GetDep.java b/src/org/ibex/util/GetDep.java
new file mode 100644 (file)
index 0000000..9e0e67a
--- /dev/null
@@ -0,0 +1,63 @@
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.util;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.util.zip.*;
+
+public final class GetDep {
+
+    public static void main(String[] s) throws Exception {
+        if (s.length < 2) {
+            System.out.println("usage: java "+GetDep.class.getName()+" <url> <jarfile>");
+            return;
+        }
+        fetch(s[1], s[0]);
+    }
+
+    public static void fetch(String path, String url) throws Exception {
+        InputStream is = fetch(url);
+        FileOutputStream fos = new FileOutputStream(path);
+        while(true) {
+            byte[] buf = new byte[1024 * 16];
+            int numread = is.read(buf, 0, buf.length);
+            if (numread == -1) break;
+            fos.write(buf, 0, numread);
+        }
+        fos.close();
+    }
+
+    public static InputStream fetch(String url) throws Exception {
+        String scheme = url.substring(0, url.indexOf(':'));
+        if (scheme.equals("zip") || scheme.equals("tgz")) {
+            int bang = url.lastIndexOf('!');
+            String path = url.substring(bang + 1);
+            url = url.substring(url.indexOf(':')+1, bang);
+            InputStream rest = fetch(url);
+            if (scheme.equals("zip")) {
+                ZipInputStream zis = new ZipInputStream(rest);
+                while(true) {
+                    ZipEntry ze = zis.getNextEntry();
+                    if (ze == null) break;
+                    if (ze.getName().equals(path)) return zis;
+                }
+                return null;
+            } else {
+                Tar.TarInputStream tis = new Tar.TarInputStream(new GZIPInputStream(rest));
+                while(true) {
+                    Tar.TarEntry te = tis.getNextEntry();
+                    if (te == null) break;
+                    if (te.getName().equals(path)) return tis;
+                }
+                return null;
+            }
+        } else {
+            URL u = new URL(url);
+            return u.openConnection().getInputStream();
+        }
+    }
+
+}
diff --git a/src/org/ibex/util/Tar.java b/src/org/ibex/util/Tar.java
new file mode 100644 (file)
index 0000000..6828beb
--- /dev/null
@@ -0,0 +1,4923 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+package org.ibex.util;
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import java.util.zip.*;
+import javax.activation.*;
+
+
+public class Tar {
+/**
+ * Special class designed to parse a Tar archive VERY FAST.
+ * This class is not a general Tar archive solution because
+ * it does not accomodate TarBuffer, or blocking. It does not
+ * allow you to read the entries either. This would not be
+ * difficult to add in a subclass.
+ *
+ * The real purpose of this class is that there are folks out
+ * there who wish to parse an ENORMOUS tar archive, and maybe
+ * only want to know the filenames, or they wish to locate the
+ * offset of a particular entry so that can process that entry
+ * with special code.
+ *
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ *
+ */
+
+public static
+class          FastTarStream
+       {
+       private boolean                 debug = false;
+       private boolean                 hasHitEOF = false;
+       private TarEntry                currEntry = null;
+       private InputStream             inStream = null;
+       private int                             recordSize = TarBuffer.DEFAULT_RCDSIZE;
+
+
+       public
+       FastTarStream( InputStream in )
+               {
+               this( in, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       FastTarStream( InputStream in, int recordSize )
+               {
+               this.inStream = in;
+               this.hasHitEOF = false;
+               this.currEntry = null;
+               this.recordSize = recordSize;
+               }
+
+       public void
+       setDebug( boolean debug )
+               {
+               this.debug = debug;
+               }
+
+       public TarEntry
+       getNextEntry()
+               throws IOException
+               {
+               if ( this.hasHitEOF )
+                       return null;
+               
+               /**
+                * Here I have tried skipping the entry size, I have tried
+                * skipping entrysize - header size,
+         * entrysize + header, and all seem to skip to some bizzarelocation!
+         */
+               if ( this.currEntry != null && this.currEntry.getSize() > 0 )
+                       {
+                       // Need to round out the number of records to be read to skip entry...
+                       int numRecords =
+                               ( (int)this.currEntry.getSize() + (this.recordSize - 1) )
+                                       / this.recordSize;
+
+                       if ( numRecords > 0 )
+                               {
+                this.inStream.skip( numRecords * this.recordSize );
+               }
+               }
+
+               byte[] headerBuf = new byte[ this.recordSize ];
+
+               // NOTE Apparently (with GZIPInputStream maybe?) we are able to
+               //      read less then record size bytes in any given read(). So,
+               //      we have to be persistent.
+
+               int bufIndex = 0;
+               for ( int bytesNeeded = this.recordSize ; bytesNeeded > 0 ; )
+                       {
+                       int numRead = this.inStream.read( headerBuf, bufIndex, bytesNeeded );
+
+                       if ( numRead == -1 )
+                               {
+                               this.hasHitEOF = true;
+                               break;
+                               }
+
+                       bufIndex += numRead;
+                       bytesNeeded -= numRead;
+                       }
+
+               // Check for "EOF block" of all zeros
+               if ( ! this.hasHitEOF )
+                       {
+                       this.hasHitEOF = true;
+                       for ( int i = 0 ; i < headerBuf.length ; ++i )
+                               {
+                               if ( headerBuf[i] != 0 )
+                                       {
+                                       this.hasHitEOF = false;
+                                       break;
+                                       }
+                               }
+                       }
+
+               if ( this.hasHitEOF )
+                       {
+                       this.currEntry = null;
+                       }
+               else
+                       {
+                       try {
+                               this.currEntry = new TarEntry( headerBuf );
+
+                               if ( this.debug )
+                                       {
+                                       byte[] by = new byte[ headerBuf.length ];
+                                       for(int i = 0; i < headerBuf.length; i++)
+                                               {
+                                               by[i] = ( headerBuf[i] == 0? 20: headerBuf[i] );
+                                               }
+                                       String s = new String( by );
+                                       System.out.println( "\n" + s );
+                                       }
+
+                               if ( ! ( headerBuf[257] == 'u' &&headerBuf[258] == 's'
+                                               && headerBuf[259] == 't' &&headerBuf[260] == 'a'
+                                               && headerBuf[261] == 'r' ) )
+                                       {
+                                            throw new InvalidHeaderException
+                                               ( "header magic is not'ustar', but '"
+                                                       + headerBuf[257] +headerBuf[258] + headerBuf[259]
+                                                       + headerBuf[260] +headerBuf[261] + "', or (dec) "
+                                                       +((int)headerBuf[257]) + ", "
+                                                       +((int)headerBuf[258]) + ", "
+                                                       +((int)headerBuf[259]) + ", "
+                                                       +((int)headerBuf[260]) + ", "
+                                                       +((int)headerBuf[261]) );
+                                       }
+                               }
+                       catch ( InvalidHeaderException ex )
+                               {
+                               this.currEntry = null;
+                               throw ex;
+                               }
+                       }
+
+               return this.currEntry;
+               }
+
+       public static void
+       main( String[] args )
+               {
+               boolean debug = false;
+               InputStream in = null;
+
+               String fileName = args[0];
+
+               try {
+                       int idx = 0;
+                       if ( args.length > 0 )
+                               {
+                               if ( args[idx].equals( "-d" ) )
+                                       {
+                                       debug = true;
+                                       idx++;
+                                       }
+
+                               if ( args[idx].endsWith( ".gz" )
+                                               || args[idx].endsWith( ".tgz" ) )
+                                       {
+                                       in = new GZIPInputStream( new FileInputStream( args[idx] ) );
+                                       }
+                               else
+                                       {
+                                       in = new FileInputStream( args[idx] );
+                                       }
+                               }
+                       else
+                               {
+                               in = System.in;
+                               }
+
+                       FastTarStream fts = new FastTarStream( in );
+                       fts.setDebug( debug );
+
+                       int nameWidth = 56;
+                       int sizeWidth = 9;
+                       int userWidth = 8;
+                       StringBuffer padBuf = new StringBuffer(128);
+                       for ( ; ; )
+                               {
+                               TarEntry entry = fts.getNextEntry();
+                               if ( entry == null )
+                                       break;
+
+                               if ( entry.isDirectory() )
+                                       {
+                                       // TYPE
+                                       System.out.print( "D " );
+
+                                       // NAME
+                                       padBuf.setLength(0);
+                                       padBuf.append( entry.getName() );
+                                       padBuf.setLength( padBuf.length() - 1 ); // drop '/'
+                                       if ( padBuf.length() > nameWidth )
+                                               padBuf.setLength( nameWidth );
+                                       for ( ; padBuf.length() < nameWidth ; )
+                                               padBuf.append( '_' );
+
+                                       padBuf.append( '_' );
+                                       System.out.print( padBuf.toString() );
+
+                                       // SIZE
+                                       padBuf.setLength(0);
+                                       for ( ; padBuf.length() < sizeWidth ; )
+                                               padBuf.insert( 0, '_' );
+
+                                       padBuf.append( ' ' );
+                                       System.out.print( padBuf.toString() );
+
+                                       // USER
+                                       padBuf.setLength(0);
+                                       padBuf.append( entry.getUserName() );
+                                       if ( padBuf.length() > userWidth )
+                                               padBuf.setLength( userWidth );
+                                       for ( ; padBuf.length() < userWidth ; )
+                                               padBuf.append( ' ' );
+
+                                       System.out.print( padBuf.toString() );
+                                       }
+                               else
+                                       {
+                                       // TYPE
+                                       System.out.print( "F " );
+
+                                       // NAME
+                                       padBuf.setLength(0);
+                                       padBuf.append( entry.getName() );
+                                       if ( padBuf.length() > nameWidth )
+                                               padBuf.setLength( nameWidth );
+                                       for ( ; padBuf.length() < nameWidth ; )
+                                               padBuf.append( ' ' );
+
+                                       padBuf.append( ' ' );
+                                       System.out.print( padBuf.toString() );
+
+                                       // SIZE
+                                       padBuf.setLength(0);
+                                       padBuf.append( entry.getSize() );
+                                       if ( padBuf.length() > sizeWidth )
+                                               padBuf.setLength( sizeWidth );
+                                       for ( ; padBuf.length() < sizeWidth ; )
+                                               padBuf.insert( 0, ' ' );
+
+                                       padBuf.append( ' ' );
+                                       System.out.print( padBuf.toString() );
+
+                                       // USER
+                                       padBuf.setLength(0);
+                                       padBuf.append( entry.getUserName() );
+                                       if ( padBuf.length() > userWidth )
+                                               padBuf.setLength( userWidth );
+                                       for ( ; padBuf.length() < userWidth ; )
+                                               padBuf.append( ' ' );
+
+                                       System.out.print( padBuf.toString() );
+                                       }
+
+                               System.out.println( "" );
+                               }
+                       }
+               catch ( IOException ex )
+                       {
+                       ex.printStackTrace( System.err );
+                       }
+               }
+
+       }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * This exception is used to indicate that there is a problem
+ * with a TAR archive header.
+ */
+
+public static class
+InvalidHeaderException extends IOException
+       {
+
+       public
+       InvalidHeaderException()
+               {
+               super();
+               }
+
+       public
+       InvalidHeaderException( String msg )
+               {
+               super( msg );
+               }
+
+       }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * The TarArchive class implements the concept of a
+ * tar archive. A tar archive is a series of entries, each of
+ * which represents a file system object. Each entry in
+ * the archive consists of a header record. Directory entries
+ * consist only of the header record, and are followed by entries
+ * for the directory's contents. File entries consist of a
+ * header record followed by the number of records needed to
+ * contain the file's contents. All entries are written on
+ * record boundaries. Records are 512 bytes long.
+ *
+ * TarArchives are instantiated in either read or write mode,
+ * based upon whether they are instantiated with an InputStream
+ * or an OutputStream. Once instantiated TarArchives read/write
+ * mode can not be changed.
+ *
+ * There is currently no support for random access to tar archives.
+ * However, it seems that subclassing TarArchive, and using the
+ * TarBuffer.getCurrentRecordNum() and TarBuffer.getCurrentBlockNum()
+ * methods, this would be rather trvial.
+ *
+ * @version $Revision: 1.15 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public static class
+TarArchive extends Object
+       {
+       protected boolean                       verbose;
+       protected boolean                       debug;
+       protected boolean                       keepOldFiles;
+       protected boolean                       asciiTranslate;
+
+       protected int                           userId;
+       protected String                        userName;
+       protected int                           groupId;
+       protected String                        groupName;
+
+       protected String                        rootPath;
+       protected String                        tempPath;
+       protected String                        pathPrefix;
+
+       protected int                           recordSize;
+       protected byte[]                        recordBuf;
+
+       protected TarInputStream        tarIn;
+       protected TarOutputStream       tarOut;
+
+       protected TarTransFileTyper             transTyper;
+       protected TarProgressDisplay    progressDisplay;
+
+
+       /**
+        * The InputStream based constructors create a TarArchive for the
+        * purposes of e'x'tracting or lis't'ing a tar archive. Thus, use
+        * these constructors when you wish to extract files from or list
+        * the contents of an existing tar archive.
+        */
+
+       public
+       TarArchive( InputStream inStream )
+               {
+               this( inStream, TarBuffer.DEFAULT_BLKSIZE );
+               }
+
+       public
+       TarArchive( InputStream inStream, int blockSize )
+               {
+               this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarArchive( InputStream inStream, int blockSize, int recordSize )
+               {
+               this.tarIn = new TarInputStream( inStream, blockSize, recordSize );
+               this.initialize( recordSize );
+               }
+
+       /**
+        * The OutputStream based constructors create a TarArchive for the
+        * purposes of 'c'reating a tar archive. Thus, use these constructors
+        * when you wish to create a new tar archive and write files into it.
+        */
+
+       public
+       TarArchive( OutputStream outStream )
+               {
+               this( outStream, TarBuffer.DEFAULT_BLKSIZE );
+               }
+
+       public
+       TarArchive( OutputStream outStream, int blockSize )
+               {
+               this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarArchive( OutputStream outStream, int blockSize, int recordSize )
+               {
+               this.tarOut = new TarOutputStream( outStream, blockSize, recordSize );
+               this.initialize( recordSize );
+               }
+
+       /**
+        * Common constructor initialization code.
+        */
+
+       private void
+       initialize( int recordSize )
+               {
+               this.rootPath = null;
+               this.pathPrefix = null;
+               this.tempPath = System.getProperty( "user.dir" );
+
+               this.userId = 0;
+               this.userName = "";
+               this.groupId = 0;
+               this.groupName = "";
+
+               this.debug = false;
+               this.verbose = false;
+               this.keepOldFiles = false;
+               this.progressDisplay = null;
+
+               this.recordBuf =
+                       new byte[ this.getRecordSize() ];
+               }
+
+       /**
+        * Set the debugging flag.
+        *
+        * @param debugF The new debug setting.
+        */
+
+       public void
+       setDebug( boolean debugF )
+               {
+               this.debug = debugF;
+               if ( this.tarIn != null )
+                       this.tarIn.setDebug( debugF );
+               else if ( this.tarOut != null )
+                       this.tarOut.setDebug( debugF );
+               }
+
+       /**
+        * Returns the verbosity setting.
+        *
+        * @return The current verbosity setting.
+        */
+
+       public boolean
+       isVerbose()
+               {
+               return this.verbose;
+               }
+
+       /**
+        * Set the verbosity flag.
+        *
+        * @param verbose The new verbosity setting.
+        */
+
+       public void
+       setVerbose( boolean verbose )
+               {
+               this.verbose = verbose;
+               }
+
+       /**
+        * Set the current progress display interface. This allows the
+        * programmer to use a custom class to display the progress of
+        * the archive's processing.
+        *
+        * @param display The new progress display interface.
+        * @see TarProgressDisplay
+        */
+
+       public void
+       setTarProgressDisplay( TarProgressDisplay display )
+               {
+               this.progressDisplay = display;
+               }
+
+       /**
+        * Set the flag that determines whether existing files are
+        * kept, or overwritten during extraction.
+        *
+        * @param keepOldFiles If true, do not overwrite existing files.
+        */
+
+       public void
+       setKeepOldFiles( boolean keepOldFiles )
+               {
+               this.keepOldFiles = keepOldFiles;
+               }
+       
+       /**
+        * Set the ascii file translation flag. If ascii file translatio
+        * is true, then the MIME file type will be consulted to determine
+        * if the file is of type 'text/*'. If the MIME type is not found,
+        * then the TransFileTyper is consulted if it is not null. If
+        * either of these two checks indicates the file is an ascii text
+        * file, it will be translated. The translation converts the local
+        * operating system's concept of line ends into the UNIX line end,
+        * '\n', which is the defacto standard for a TAR archive. This makes
+        * text files compatible with UNIX, and since most tar implementations
+        * for other platforms, compatible with most other platforms.
+        *
+        * @param asciiTranslate If true, translate ascii text files.
+        */
+
+       public void
+       setAsciiTranslation( boolean asciiTranslate )
+               {
+               this.asciiTranslate = asciiTranslate;
+               }
+
+       /**
+        * Set the object that will determine if a file is of type
+        * ascii text for translation purposes.
+        *
+        * @param transTyper The new TransFileTyper object.
+        */
+
+       public void
+       setTransFileTyper( TarTransFileTyper transTyper )
+               {
+               this.transTyper = transTyper;
+               }
+
+       /**
+        * Set user and group information that will be used to fill in the
+        * tar archive's entry headers. Since Java currently provides no means
+        * of determining a user name, user id, group name, or group id for
+        * a given File, TarArchive allows the programmer to specify values
+        * to be used in their place.
+        *
+        * @param userId The user Id to use in the headers.
+        * @param userName The user name to use in the headers.
+        * @param groupId The group id to use in the headers.
+        * @param groupName The group name to use in the headers.
+        */
+
+       public void
+       setUserInfo(
+                       int userId, String userName,
+                       int groupId, String groupName )
+               {
+               this.userId = userId;
+               this.userName = userName;
+               this.groupId = groupId;
+               this.groupName = groupName;
+               }
+
+       /**
+        * Get the user id being used for archive entry headers.
+        *
+        * @return The current user id.
+        */
+
+       public int
+       getUserId()
+               {
+               return this.userId;
+               }
+
+       /**
+        * Get the user name being used for archive entry headers.
+        *
+        * @return The current user name.
+        */
+
+       public String
+       getUserName()
+               {
+               return this.userName;
+               }
+
+       /**
+        * Get the group id being used for archive entry headers.
+        *
+        * @return The current group id.
+        */
+
+       public int
+       getGroupId()
+               {
+               return this.groupId;
+               }
+
+       /**
+        * Get the group name being used for archive entry headers.
+        *
+        * @return The current group name.
+        */
+
+       public String
+       getGroupName()
+               {
+               return this.groupName;
+               }
+
+       /**
+        * Get the current temporary directory path. Because Java's
+        * File did not support temporary files until version 1.2,
+        * TarArchive manages its own concept of the temporary
+        * directory. The temporary directory defaults to the
+        * 'user.dir' System property.
+        *
+        * @return The current temporary directory path.
+        */
+
+       public String
+       getTempDirectory()
+               {
+               return this.tempPath;
+               }
+
+       /**
+        * Set the current temporary directory path.
+        *
+        * @param path The new temporary directory path.
+        */
+
+       public void
+       setTempDirectory( String path )
+               {
+               this.tempPath = path;
+               }
+
+       /**
+        * Get the archive's record size. Because of its history, tar
+        * supports the concept of buffered IO consisting of BLOCKS of
+        * RECORDS. This allowed tar to match the IO characteristics of
+        * the physical device being used. Of course, in the Java world,
+        * this makes no sense, WITH ONE EXCEPTION - archives are expected
+        * to be propertly "blocked". Thus, all of the horrible TarBuffer
+        * support boils down to simply getting the "boundaries" correct.
+        *
+        * @return The record size this archive is using.
+        */
+
+       public int
+       getRecordSize()
+               {
+               if ( this.tarIn != null )
+                       {
+                       return this.tarIn.getRecordSize();
+                       }
+               else if ( this.tarOut != null )
+                       {
+                       return this.tarOut.getRecordSize();
+                       }
+               
+               return TarBuffer.DEFAULT_RCDSIZE;
+               }
+
+       /**
+        * Get a path for a temporary file for a given File. The
+        * temporary file is NOT created. The algorithm attempts
+        * to handle filename collisions so that the name is
+        * unique.
+        *
+        * @return The temporary file's path.
+        */
+
+       private String
+       getTempFilePath( File eFile )
+               {
+               String pathStr =
+                       this.tempPath + File.separator
+                               + eFile.getName() + ".tmp";
+
+               for ( int i = 1 ; i < 5 ; ++i )
+                       {
+                       File f = new File( pathStr );
+
+                       if ( ! f.exists() )
+                               break;
+
+                       pathStr =
+                               this.tempPath + File.separator
+                                       + eFile.getName() + "-" + i + ".tmp";
+                       }
+
+               return pathStr;
+               }
+
+       /**
+        * Close the archive. This simply calls the underlying
+        * tar stream's close() method.
+        */
+
+       public void
+       closeArchive()
+               throws IOException
+               {
+               if ( this.tarIn != null )
+                       {
+                       this.tarIn.close();
+                       }
+               else if ( this.tarOut != null )
+                       {
+                       this.tarOut.close();
+                       }
+               }
+
+       /**
+        * Perform the "list" command and list the contents of the archive.
+        * NOTE That this method uses the progress display to actually list
+        * the conents. If the progress display is not set, nothing will be
+        * listed!
+        */
+
+       public void
+       listContents()
+               throws IOException, InvalidHeaderException
+               {
+               for ( ; ; )
+                       {
+                       TarEntry entry = this.tarIn.getNextEntry();
+
+
+                       if ( entry == null )
+                               {
+                               if ( this.debug )
+                                       {
+                                       System.err.println( "READ EOF RECORD" );
+                                       }
+                               break;
+                               }
+
+                       if ( this.progressDisplay != null )
+                               this.progressDisplay.showTarProgressMessage
+                                       ( entry.getName() );
+                       }
+               }
+
+       /**
+        * Perform the "extract" command and extract the contents of the archive.
+        *
+        * @param destDir The destination directory into which to extract.
+        */
+
+       public void
+       extractContents( File destDir )
+               throws IOException, InvalidHeaderException
+               {
+               for ( ; ; )
+                       {
+                       TarEntry entry = this.tarIn.getNextEntry();
+
+                       if ( entry == null )
+                               {
+                               if ( this.debug )
+                                       {
+                                       System.err.println( "READ EOF RECORD" );
+                                       }
+                               break;
+                               }
+
+                       this.extractEntry( destDir, entry );
+                       }
+               }
+
+       /**
+        * Extract an entry from the archive. This method assumes that the
+        * tarIn stream has been properly set with a call to getNextEntry().
+        *
+        * @param destDir The destination directory into which to extract.
+        * @param entry The TarEntry returned by tarIn.getNextEntry().
+        */
+
+       private void
+       extractEntry( File destDir, TarEntry entry )
+               throws IOException
+               {
+               if ( this.verbose )
+                       {
+                       if ( this.progressDisplay != null )
+                               this.progressDisplay.showTarProgressMessage
+                                       ( entry.getName() );
+                       }
+
+               String name = entry.getName();
+               name = name.replace( '/', File.separatorChar );
+
+               File destFile = new File( destDir, name );
+
+               if ( entry.isDirectory() )
+                       {
+                       if ( ! destFile.exists() )
+                               {
+                               if ( ! destFile.mkdirs() )
+                                       {
+                                       throw new IOException
+                                               ( "error making directory path '"
+                                                       + destFile.getPath() + "'" );
+                                       }
+                               }
+                       }
+               else
+                       {
+                       File subDir = new File( destFile.getParent() );
+
+                       if ( ! subDir.exists() )
+                               {
+                               if ( ! subDir.mkdirs() )
+                                       {
+                                       throw new IOException
+                                               ( "error making directory path '"
+                                                       + subDir.getPath() + "'" );
+                                       }
+                               }
+
+                       if ( this.keepOldFiles && destFile.exists() )
+                               {
+                               if ( this.verbose )
+                                       {
+                                       if ( this.progressDisplay != null )
+                                               this.progressDisplay.showTarProgressMessage
+                                                       ( "not overwriting " + entry.getName() );
+                                       }
+                               }
+                       else
+                               {
+                               boolean asciiTrans = false;
+
+                               FileOutputStream out =
+                                       new FileOutputStream( destFile );
+
+
+                               PrintWriter outw = null;
+                               if ( asciiTrans )
+                                       {
+                                       outw = new PrintWriter( out );
+                                       }
+
+                               byte[] rdbuf = new byte[32 * 1024];
+
+                               for ( ; ; )
+                                       {
+                                       int numRead = this.tarIn.read( rdbuf );
+
+                                       if ( numRead == -1 )
+                                               break;
+                                       
+                                       if ( asciiTrans )
+                                               {
+                                               for ( int off = 0, b = 0 ; b < numRead ; ++b )
+                                                       {
+                                                       if ( rdbuf[ b ] == 10 )
+                                                               {
+                                                               String s = new String
+                                                                       ( rdbuf, off, (b - off) );
+
+                                                               outw.println( s );
+
+                                                               off = b + 1;
+                                                               }
+                                                       }
+                                               }
+                                       else
+                                               {
+                                               out.write( rdbuf, 0, numRead );
+                                               }
+                                       }
+
+                               if ( asciiTrans )
+                                       outw.close();
+                               else
+                                       out.close();
+                               }
+                       }
+               }
+
+       /**
+        * Write an entry to the archive. This method will call the putNextEntry()
+        * and then write the contents of the entry, and finally call closeEntry()
+        * for entries that are files. For directories, it will call putNextEntry(),
+        * and then, if the recurse flag is true, process each entry that is a
+        * child of the directory.
+        *
+        * @param entry The TarEntry representing the entry to write to the archive.
+        * @param recurse If true, process the children of directory entries.
+        */
+
+       public void
+       writeEntry( TarEntry oldEntry, boolean recurse )
+               throws IOException
+               {
+               boolean asciiTrans = false;
+               boolean unixArchiveFormat = oldEntry.isUnixTarFormat();
+
+               File tFile = null;
+               File eFile = oldEntry.getFile();
+
+               // Work on a copy of the entry so we can manipulate it.
+               // Note that we must distinguish how the entry was constructed.
+               //
+               TarEntry entry = (TarEntry) oldEntry.clone();
+
+               if ( this.verbose )
+                       {
+                       if ( this.progressDisplay != null )
+                               this.progressDisplay.showTarProgressMessage
+                                       ( entry.getName() );
+                       }
+
+
+               String newName = null;
+
+               if ( this.rootPath != null )
+                       {
+                       if ( entry.getName().startsWith( this.rootPath ) )
+                               {
+                               newName =
+                                       entry.getName().substring
+                                               ( this.rootPath.length() + 1 );
+                               }
+                       }
+
+               if ( this.pathPrefix != null )
+                       {
+                       newName = (newName == null)
+                               ? this.pathPrefix + "/" + entry.getName()
+                               : this.pathPrefix + "/" + newName;
+                       }
+
+               if ( newName != null )
+                       {
+                       entry.setName( newName );
+                       }
+
+               this.tarOut.putNextEntry( entry );
+
+               if ( entry.isDirectory() )
+                       {
+                       if ( recurse )
+                               {
+                               TarEntry[] list = entry.getDirectoryEntries();
+
+                               for ( int i = 0 ; i < list.length ; ++i )
+                                       {
+                                       TarEntry dirEntry = list[i];
+
+                                       if ( unixArchiveFormat )
+                                               dirEntry.setUnixTarFormat();
+
+                                       this.writeEntry( dirEntry, recurse );
+                                       }
+                               }
+                       }
+               else
+                       {
+                       FileInputStream in =
+                               new FileInputStream( eFile );
+
+                       byte[] eBuf = new byte[ 32 * 1024 ];
+                       for ( ; ; )
+                               {
+                               int numRead = in.read( eBuf, 0, eBuf.length );
+
+                               if ( numRead == -1 )
+                                       break;
+
+                               this.tarOut.write( eBuf, 0, numRead );
+                               }
+
+                       in.close();
+
+                       if ( tFile != null )
+                               {
+                               tFile.delete();
+                               }
+                       
+                       this.tarOut.closeEntry();
+                       }
+               }
+
+       }
+
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * 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.
+ *
+ * @version $Revision: 1.10 $
+ * @author Timothy Gerard Endres,
+ *  <a href="mailto:time@gjt.org">time@trustice.com</a>.
+ * @see TarArchive
+ */
+
+public
+static class           TarBuffer
+extends                Object
+       {
+       public static final int         DEFAULT_RCDSIZE = ( 512 );
+       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;
+
+
+       public
+       TarBuffer( InputStream inStream )
+               {
+               this( inStream, TarBuffer.DEFAULT_BLKSIZE );
+               }
+
+       public
+       TarBuffer( InputStream inStream, int blockSize )
+               {
+               this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarBuffer( InputStream inStream, int blockSize, int recordSize )
+               {
+               this.inStream = inStream;
+               this.outStream = null;
+               this.initialize( blockSize, recordSize );
+               }
+
+       public
+       TarBuffer( OutputStream outStream )
+               {
+               this( outStream, TarBuffer.DEFAULT_BLKSIZE );
+               }
+
+       public
+       TarBuffer( OutputStream outStream, int blockSize )
+               {
+               this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       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.
+        */
+       public int
+       getBlockSize()
+               {
+               return this.blockSize;
+               }
+
+       /**
+        * Get the TAR Buffer's record size.
+        */
+       public int
+       getRecordSize()
+               {
+               return this.recordSize;
+               }
+
+       /**
+        * Set the debugging flag for the buffer.
+        *
+        * @param debug If true, print debugging output.
+        */
+       public void
+       setDebug( boolean debug )
+               {
+               this.debug = debug;
+               }
+
+       /**
+        * 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.
+        */
+       public boolean
+       isEOFRecord( byte[] record )
+               {
+               for ( int i = 0, sz = this.getRecordSize() ; i < sz ; ++i )
+                       if ( record[i] != 0 )
+                               return false;
+
+               return true;
+               }
+
+       /**
+        * Skip over a record on the input stream.
+        */
+
+       public void
+       skipRecord()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "SkipRecord: recIdx = " + this.currRecIdx
+                                       + " blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.inStream == null )
+                       throw new IOException
+                               ( "reading (via skip) from an output buffer" );
+
+               if ( this.currRecIdx >= this.recsPerBlock )
+                       {
+                       if ( ! this.readBlock() )
+                               return; // UNDONE
+                       }
+
+               this.currRecIdx++;
+               }
+
+       /**
+        * Read a record from the input stream and return the data.
+        *
+        * @return The record data.
+        */
+
+       public byte[]
+       readRecord()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "ReadRecord: recIdx = " + this.currRecIdx
+                                       + " blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.inStream == null )
+                       throw new IOException
+                               ( "reading from an output buffer" );
+
+               if ( this.currRecIdx >= this.recsPerBlock )
+                       {
+                       if ( ! this.readBlock() )
+                               return null;
+                       }
+
+               byte[] result = new byte[ this.recordSize ];
+
+               System.arraycopy(
+                       this.blockBuffer, (this.currRecIdx * this.recordSize),
+                       result, 0, this.recordSize );
+
+               this.currRecIdx++;
+
+               return result;
+               }
+
+       /**
+        * @return false if End-Of-File, else true
+        */
+
+       private boolean
+       readBlock()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "ReadBlock: blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.inStream == null )
+                       throw new IOException
+                               ( "reading from an output buffer" );
+
+               this.currRecIdx = 0;
+
+               int offset = 0;
+               int bytesNeeded = this.blockSize;
+               for ( ; bytesNeeded > 0 ; )
+                       {
+                       long numBytes =
+                               this.inStream.read
+                                       ( this.blockBuffer, offset, bytesNeeded );
+
+                       //
+                       // NOTE
+                       // We have fit EOF, and the block is not full!
+                       //
+                       // This is a broken archive. It does not follow the standard
+                       // blocking algorithm. However, because we are generous, and
+                       // it requires little effort, we will simply ignore the error
+                       // and continue as if the entire block were read. This does
+                       // not appear to break anything upstream. We used to return
+                       // false in this case.
+                       //
+                       // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
+                       //
+
+                       if ( numBytes == -1 )
+                               break;
+
+                       offset += numBytes;
+                       bytesNeeded -= numBytes;
+                       if ( numBytes != this.blockSize )
+                               {
+                               if ( this.debug )
+                                       {
+                                       System.err.println
+                                               ( "ReadBlock: INCOMPLETE READ " + numBytes
+                                                       + " of " + this.blockSize + " bytes read." );
+                                       }
+                               }
+                       }
+
+               this.currBlkIdx++;
+
+               return true;
+               }
+
+       /**
+        * Get the current block number, zero based.
+        *
+        * @return The current zero based block number.
+        */
+       public int
+       getCurrentBlockNum()
+               {
+               return this.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 this.currRecIdx - 1;
+               }
+
+       /**
+        * Write an archive record to the archive.
+        *
+        * @param record The record data to write to the archive.
+        */
+
+       public void
+       writeRecord( byte[] record )
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "WriteRecord: recIdx = " + this.currRecIdx
+                                       + " blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.outStream == null )
+                       throw new IOException
+                               ( "writing to an input buffer" );
+
+               if ( record.length != this.recordSize )
+                       throw new IOException
+                               ( "record to write has length '" + record.length
+                                       + "' which is not the record size of '"
+                                       + this.recordSize + "'" );
+
+               if ( this.currRecIdx >= this.recsPerBlock )
+                       {
+                       this.writeBlock();
+                       }
+
+               System.arraycopy(
+                       record, 0,
+                       this.blockBuffer, (this.currRecIdx * this.recordSize),
+                       this.recordSize );
+
+               this.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.
+        */
+
+       public void
+       writeRecord( byte[] buf, int offset )
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "WriteRecord: recIdx = " + this.currRecIdx
+                                       + " blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.outStream == null )
+                       throw new IOException
+                               ( "writing to an input buffer" );
+
+               if ( (offset + this.recordSize) > buf.length )
+                       throw new IOException
+                               ( "record has length '" + buf.length
+                                       + "' with offset '" + offset
+                                       + "' which is less than the record size of '"
+                                       + this.recordSize + "'" );
+
+               if ( this.currRecIdx >= this.recsPerBlock )
+                       {
+                       this.writeBlock();
+                       }
+
+               System.arraycopy(
+                       buf, offset,
+                       this.blockBuffer, (this.currRecIdx * this.recordSize),
+                       this.recordSize );
+
+               this.currRecIdx++;
+               }
+
+       /**
+        * Write a TarBuffer block to the archive.
+        */
+       private void
+       writeBlock()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println
+                               ( "WriteBlock: blkIdx = " + this.currBlkIdx );
+                       }
+
+               if ( this.outStream == null )
+                       throw new IOException
+                               ( "writing to an input buffer" );
+
+               this.outStream.write( this.blockBuffer, 0, this.blockSize );
+               this.outStream.flush();
+
+               this.currRecIdx = 0;
+               this.currBlkIdx++;
+               }
+
+       /**
+        * Flush the current data block if it has any data in it.
+        */
+
+       private void
+       flushBlock()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println( "TarBuffer.flushBlock() called." );
+                       }
+
+               if ( this.outStream == null )
+                       throw new IOException
+                               ( "writing to an input buffer" );
+
+               // Thanks to 'Todd Kofford <tkofford@bigfoot.com>' for this patch.
+               // Use a buffer initialized with 0s to initialize everything in the
+               // blockBuffer after the last current, complete record. This prevents
+               // any previous data that might have previously existed in the
+               // blockBuffer from being written to the file.
+
+               if ( this.currRecIdx > 0 )
+                       {
+                       int offset = this.currRecIdx * this.recordSize;
+                       byte[]  zeroBuffer = new byte[ this.blockSize - offset ];
+
+                       System.arraycopy
+                               ( zeroBuffer, 0, this.blockBuffer, offset, zeroBuffer.length );
+
+                       this.writeBlock();
+                       }
+               }
+
+       /**
+        * Close the TarBuffer. If this is an output buffer, also flush the
+        * current block before closing.
+        */
+       public void
+       close()
+               throws IOException
+               {
+               if ( this.debug )
+                       {
+                       System.err.println( "TarBuffer.closeBuffer()." );
+                       }
+
+               if ( this.outStream != null )
+                       {
+                       this.flushBlock();
+
+                       if ( this.outStream != System.out
+                                       && this.outStream != System.err )
+                               {
+                               this.outStream.close();
+                               this.outStream = null;
+                               }
+                       }
+               else if ( this.inStream != null )
+                       {
+                       if ( this.inStream != System.in )
+                               {
+                               this.inStream.close();
+                               this.inStream = null;
+                               }
+                       }
+               }
+
+       }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+
+/**
+ *
+ * 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.
+ *
+ * <pre>
+ *
+ * Original Unix Tar Header:
+ *
+ * Field  Field     Field
+ * Width  Name      Meaning
+ * -----  --------- ---------------------------
+ *   100  name      name of file
+ *     8  mode      file mode
+ *     8  uid       owner user ID
+ *     8  gid       owner group ID
+ *    12  size      length of file in bytes
+ *    12  mtime     modify time of file
+ *     8  chksum    checksum for header
+ *     1  link      indicator for links
+ *   100  linkname  name of linked file
+ *
+ * </pre>
+ *
+ * <pre>
+ *
+ * POSIX "ustar" Style Tar Header:
+ *
+ * Field  Field     Field
+ * Width  Name      Meaning
+ * -----  --------- ---------------------------
+ *   100  name      name of file
+ *     8  mode      file mode
+ *     8  uid       owner user ID
+ *     8  gid       owner group ID
+ *    12  size      length of file in bytes
+ *    12  mtime     modify time of file
+ *     8  chksum    checksum for header
+ *     1  typeflag  type of file
+ *   100  linkname  name of linked file
+ *     6  magic     USTAR indicator
+ *     2  version   USTAR version
+ *    32  uname     owner user name
+ *    32  gname     owner group name
+ *     8  devmajor  device major number
+ *     8  devminor  device minor number
+ *   155  prefix    prefix for file name
+ *
+ * struct posix_header
+ *   {                     byte offset
+ *   char name[100];            0
+ *   char mode[8];            100
+ *   char uid[8];             108
+ *   char gid[8];             116
+ *   char size[12];           124
+ *   char mtime[12];          136
+ *   char chksum[8];          148
+ *   char typeflag;           156
+ *   char linkname[100];      157
+ *   char magic[6];           257
+ *   char version[2];         263
+ *   char uname[32];          265
+ *   char gname[32];          297
+ *   char devmajor[8];        329
+ *   char devminor[8];        337
+ *   char prefix[155];        345
+ *   };                       500
+ *
+ * </pre>
+ *
+ * Note that while the class does recognize GNU formatted headers,
+ * it does not perform proper processing of GNU archives. I hope
+ * to add the GNU support someday.
+ *
+ * Directory "size" fix contributed by:
+ * Bert Becker <becker@informatik.hu-berlin.de>
+ *
+ * @see TarHeader
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ */
+
+public
+static class           TarEntry
+extends                Object
+implements     Cloneable
+       {
+       /**
+        * If this entry represents a File, this references it.
+        */
+       protected File                          file;
+
+       /**
+        * This is the entry's header information.
+        */
+       protected TarHeader                     header;
+
+       /**
+        * Set to true if this is a "old-unix" format entry.
+        */
+       protected boolean                       unixFormat;
+
+       /**
+        * Set to true if this is a 'ustar' format entry.
+        */
+       protected boolean                       ustarFormat;
+
+       /**
+        * Set to true if this is a GNU 'ustar' format entry.
+        */
+       protected boolean                       gnuFormat;
+
+
+       /**
+        * The default constructor is protected for use only by subclasses.
+        */
+       protected
+       TarEntry()
+               {
+               }
+
+       /**
+        * Construct an entry with only a name. This allows the programmer
+        * to construct the entry's header "by hand". File is set to null.
+        */
+       public
+       TarEntry( String name )
+               {
+               this.initialize();
+               this.nameTarHeader( this.header, name );
+               }
+
+       /**
+        * 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
+       TarEntry( File file )
+               throws InvalidHeaderException
+               {
+               this.initialize();
+               this.getFileTarHeader( this.header, file );
+               }
+
+       /**
+        * Construct an entry from an archive's header bytes. File is set
+        * to null.
+        *
+        * @param headerBuf The header bytes from a tar archive entry.
+        */
+       public
+       TarEntry( byte[] headerBuf )
+               throws InvalidHeaderException
+               {
+               this.initialize();
+               this.parseTarHeader( this.header, headerBuf );
+               }
+
+       /**
+        * Initialization code common to all constructors.
+        */
+       private void
+       initialize()
+               {
+               this.file = null;
+               this.header = new TarHeader();
+
+               this.gnuFormat = false;
+               this.ustarFormat = true; // REVIEW What we prefer to use...
+               this.unixFormat = false;
+               }
+
+       /**
+        * Clone the entry.
+        */
+       public Object
+       clone()
+               {
+               TarEntry entry = null;
+
+               try {
+                       entry = (TarEntry) super.clone();
+
+                       if ( this.header != null )
+                               {
+                               entry.header = (TarHeader) this.header.clone();
+                               }
+
+                       if ( this.file != null )
+                               {
+                               entry.file = new File( this.file.getAbsolutePath() );
+                               }
+                       }
+               catch ( CloneNotSupportedException ex )
+                       {
+                       ex.printStackTrace( System.err );
+                       }
+
+               return entry;
+               }
+
+       /**
+        * Returns true if this entry's header is in "ustar" format.
+        *
+        * @return True if the entry's header is in "ustar" format.
+        */
+       public boolean
+       isUSTarFormat()
+               {
+               return this.ustarFormat;
+               }
+
+       /**
+        * Sets this entry's header format to "ustar".
+        */
+       public void
+       setUSTarFormat()
+               {
+               this.ustarFormat = true;
+               this.gnuFormat = false;
+               this.unixFormat = false;
+               }
+
+       /**
+        * Returns true if this entry's header is in the GNU 'ustar' format.
+        *
+        * @return True if the entry's header is in the GNU 'ustar' format.
+        */
+       public boolean
+       isGNUTarFormat()
+               {
+               return this.gnuFormat;
+               }
+
+       /**
+        * Sets this entry's header format to GNU "ustar".
+        */
+       public void
+       setGNUTarFormat()
+               {
+               this.gnuFormat = true;
+               this.ustarFormat = false;
+               this.unixFormat = false;
+               }
+
+       /**
+        * Returns true if this entry's header is in the old "unix-tar" format.
+        *
+        * @return True if the entry's header is in the old "unix-tar" format.
+        */
+       public boolean
+       isUnixTarFormat()
+               {
+               return this.unixFormat;
+               }
+
+       /**
+        * Sets this entry's header format to "unix-style".
+        */
+       public void
+       setUnixTarFormat()
+               {
+               this.unixFormat = true;
+               this.ustarFormat = false;
+               this.gnuFormat = false;
+               }
+
+       /**
+        * Determine if the two entries are equal. Equality is determined
+        * by the header names being equal.
+        *
+        * @return it Entry to be checked for equality.
+        * @return True if the entries are equal.
+        */
+       public boolean
+       equals( TarEntry it )
+               {
+               return
+                       this.header.name.toString().equals
+                               ( it.header.name.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.
+        *
+        * @param desc Entry to be checked as a descendent of this.
+        * @return True if entry is a descendant of this.
+        */
+       public boolean
+       isDescendent( TarEntry desc )
+               {
+               return
+                       desc.header.name.toString().startsWith
+                               ( this.header.name.toString() );
+               }
+
+       /**
+        * Get this entry's header.
+        *
+        * @return This entry's TarHeader.
+        */
+       public TarHeader
+       getHeader()
+               {
+               return this.header;
+               }
+
+       /**
+        * Get this entry's name.
+        *
+        * @return This entry's name.
+        */
+       public String
+       getName()
+               {
+               return this.header.name.toString();
+               }
+
+       /**
+        * Set this entry's name.
+        *
+        * @param name This entry's new name.
+        */
+       public void
+       setName( String name )
+               {
+               this.header.name =
+                       new StringBuffer( name );
+               }
+
+       /**
+        * Get this entry's user id.
+        *
+        * @return This entry's user id.
+        */
+       public int
+       getUserId()
+               {
+               return this.header.userId;
+               }
+
+       /**
+        * Set this entry's user id.
+        *
+        * @param userId This entry's new user id.
+        */
+       public void
+       setUserId( int userId )
+               {
+               this.header.userId = userId;
+               }
+
+       /**
+        * Get this entry's group id.
+        *
+        * @return This entry's group id.
+        */
+       public int
+       getGroupId()
+               {
+               return this.header.groupId;
+               }
+
+       /**
+        * Set this entry's group id.
+        *
+        * @param groupId This entry's new group id.
+        */
+       public void
+       setGroupId( int groupId )
+               {
+               this.header.groupId = groupId;
+               }
+
+       /**
+        * Get this entry's user name.
+        *
+        * @return This entry's user name.
+        */
+       public String
+       getUserName()
+               {
+               return this.header.userName.toString();
+               }
+
+       /**
+        * Set this entry's user name.
+        *
+        * @param userName This entry's new user name.
+        */
+       public void
+       setUserName( String userName )
+               {
+               this.header.userName =
+                       new StringBuffer( userName );
+               }
+
+       /**
+        * Get this entry's group name.
+        *
+        * @return This entry's group name.
+        */
+       public String
+       getGroupName()
+               {
+               return this.header.groupName.toString();
+               }
+
+       /**
+        * Set this entry's group name.
+        *
+        * @param groupName This entry's new group name.
+        */
+       public void
+       setGroupName( String groupName )
+               {
+               this.header.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 )
+               {
+               this.setUserId( userId );
+               this.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 )
+               {
+               this.setUserName( userName );
+               this.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 )
+               {
+               this.header.modTime = time / 1000;
+               }
+
+       /**
+        * Set this entry's modification time.
+        *
+        * @param time This entry's new modification time.
+        */
+       public void
+       setModTime( Date time )
+               {
+               this.header.modTime = time.getTime() / 1000;
+               }
+
+       /**
+        * Set this entry's modification time.
+        *
+        * @param time This entry's new modification time.
+        */
+       public Date
+       getModTime()
+               {
+               return new Date( this.header.modTime * 1000 );
+               }
+
+       /**
+        * Get this entry's file.
+        *
+        * @return This entry's file.
+        */
+       public File
+       getFile()
+               {
+               return this.file;
+               }
+
+       /**
+        * Get this entry's file size.
+        *
+        * @return This entry's file size.
+        */
+       public long
+       getSize()
+               {
+               return this.header.size;
+               }
+
+       /**
+        * Set this entry's file size.
+        *
+        * @param size This entry's new file size.
+        */
+       public void
+       setSize( long size )
+               {
+               this.header.size = size;
+               }
+
+       /**
+        * Return whether or not this entry represents a directory.
+        *
+        * @return True if this entry is a directory.
+        */
+       public boolean
+       isDirectory()
+               {
+               if ( this.file != null )
+                       return this.file.isDirectory();
+
+               if ( this.header != null )
+                       {
+                       if ( this.header.linkFlag == TarHeader.LF_DIR )
+                               return true;
+
+                       if ( this.header.name.toString().endsWith( "/" ) )
+                               return true;
+                       }
+
+               return false;
+               }
+
+       /**
+        * Fill in a TarHeader with information from a File.
+        *
+        * @param hdr The TarHeader to fill in.
+        * @param file The file from which to get the header information.
+        */
+       public void
+       getFileTarHeader( TarHeader hdr, File file )
+               throws InvalidHeaderException
+               {
+               this.file = file;
+
+               String name = file.getPath();
+               String osname = System.getProperty( "os.name" );
+               if ( osname != null )
+                       {
+                       // Strip off drive letters!
+                       // REVIEW Would a better check be "(File.separator == '\')"?
+
+                       // String Win32Prefix = "Windows";
+                       // String prefix = osname.substring( 0, Win32Prefix.length() );
+                       // if ( prefix.equalsIgnoreCase( Win32Prefix ) )
+
+                       // if ( File.separatorChar == '\\' )
+
+                       // Windows OS check was contributed by
+                       // Patrick Beard <beard@netscape.com>
+                       String Win32Prefix = "windows";
+                       if ( osname.toLowerCase().startsWith( Win32Prefix ) )
+                               {
+                               if ( name.length() > 2 )
+                                       {
+                                       char ch1 = name.charAt(0);
+                                       char ch2 = name.charAt(1);
+                                       if ( ch2 == ':'
+                                               && ( (ch1 >= 'a' && ch1 <= 'z')
+                                                       || (ch1 >= 'A' && ch1 <= 'Z') ) )
+                                               {
+                                               name = name.substring( 2 );
+                                               }
+                                       }
+                               }
+                       }
+
+               name = name.replace( File.separatorChar, '/' );
+
+               // No absolute pathnames
+               // Windows (and Posix?) paths can start with "\\NetworkDrive\",
+               // so we loop on starting /'s.
+               
+               for ( ; name.startsWith( "/" ) ; )
+                       name = name.substring( 1 );
+
+               hdr.linkName = new StringBuffer( "" );
+
+               hdr.name = new StringBuffer( name );
+
+               if ( file.isDirectory() )
+                       {
+                       hdr.size = 0;
+                       hdr.mode = 040755;
+                       hdr.linkFlag = TarHeader.LF_DIR;
+                       if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
+                               hdr.name.append( "/" );
+                       }
+               else
+                       {
+                       hdr.size = file.length();
+                       hdr.mode = 0100644;
+                       hdr.linkFlag = TarHeader.LF_NORMAL;
+                       }
+
+               // UNDONE When File lets us get the userName, use it!
+
+               hdr.modTime = file.lastModified() / 1000;
+               hdr.checkSum = 0;
+               hdr.devMajor = 0;
+               hdr.devMinor = 0;
+               }
+
+       /**
+        * 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 TarEntry[]
+       getDirectoryEntries()
+               throws InvalidHeaderException
+               {
+               if ( this.file == null
+                               || ! this.file.isDirectory() )
+                       {
+                       return new TarEntry[0];
+                       }
+
+               String[] list = this.file.list();
+
+               TarEntry[] result = new TarEntry[ list.length ];
+
+               for ( int i = 0 ; i < list.length ; ++i )
+                       {
+                       result[i] =
+                               new TarEntry
+                                       ( new File( this.file, list[i] ) );
+                       }
+
+               return result;
+               }
+
+       /**
+        * Compute the checksum of a tar entry header.
+        *
+        * @param buf The tar entry's header buffer.
+        * @return The computed checksum.
+        */
+       public long
+       computeCheckSum( byte[] buf )
+               {
+               long sum = 0;
+
+               for ( int i = 0 ; i < buf.length ; ++i )
+                       {
+                       sum += 255 & buf[ i ];
+                       }
+
+               return sum;
+               }
+
+       /**
+        * Write an entry's header information to a header buffer.
+        * This method can throw an InvalidHeaderException
+        *
+        * @param outbuf The tar entry header buffer to fill in.
+        * @throws InvalidHeaderException If the name will not fit in the header.
+        */
+       public void
+       writeEntryHeader( byte[] outbuf )
+               throws InvalidHeaderException
+               {
+               int offset = 0;
+
+               if ( this.isUnixTarFormat() )
+                       {
+                       if ( this.header.name.length() > 100 )
+                               throw new InvalidHeaderException
+                                       ( "file path is greater than 100 characters, "
+                                               + this.header.name );
+                       }
+
+               offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
+
+               offset = TarHeader.getOctalBytes
+                       ( this.header.mode, outbuf, offset, TarHeader.MODELEN );
+
+               offset = TarHeader.getOctalBytes
+                       ( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
+
+               offset = TarHeader.getOctalBytes
+                       ( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
+
+               long size = this.header.size;
+
+               offset = TarHeader.getLongOctalBytes
+                       ( size, outbuf, offset, TarHeader.SIZELEN );
+
+               offset = TarHeader.getLongOctalBytes
+                       ( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
+
+               int csOffset = offset;
+               for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
+                       outbuf[ offset++ ] = (byte) ' ';
+
+               outbuf[ offset++ ] = this.header.linkFlag;
+
+               offset = TarHeader.getNameBytes
+                       ( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
+
+               if ( this.unixFormat )
+                       {
+                       for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
+                               outbuf[ offset++ ] = 0;
+                       }
+               else
+                       {
+                       offset = TarHeader.getNameBytes
+                               ( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
+                       }
+
+               offset = TarHeader.getNameBytes
+                       ( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
+
+               offset = TarHeader.getNameBytes
+                       ( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
+
+               offset = TarHeader.getOctalBytes
+                       ( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
+
+               offset = TarHeader.getOctalBytes
+                       ( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
+
+               for ( ; offset < outbuf.length ; )
+                       outbuf[ offset++ ] = 0;
+
+               long checkSum = this.computeCheckSum( outbuf );
+
+               TarHeader.getCheckSumOctalBytes
+                       ( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
+               }
+
+       /**
+        * Parse an entry's TarHeader information from a header buffer.
+        *
+        * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
+        *
+        * @param hdr The TarHeader to fill in from the buffer information.
+        * @param header The tar entry header buffer to get information from.
+        */
+       public void
+       parseTarHeader( TarHeader hdr, byte[] headerBuf )
+               throws InvalidHeaderException
+               {
+               int offset = 0;
+
+               //
+               // NOTE Recognize archive header format.
+               //
+               if (       headerBuf[257] == 0
+                               && headerBuf[258] == 0
+                               && headerBuf[259] == 0
+                               && headerBuf[260] == 0
+                               && headerBuf[261] == 0 )
+                       {
+                       this.unixFormat = true;
+                       this.ustarFormat = false;
+                       this.gnuFormat = false;
+                       }
+               else if (  headerBuf[257] == 'u'
+                               && headerBuf[258] == 's'
+                               && headerBuf[259] == 't'
+                               && headerBuf[260] == 'a'
+                               && headerBuf[261] == 'r'
+                               && headerBuf[262] == 0 )
+                       {
+                       this.ustarFormat = true;
+                       this.gnuFormat = false;
+                       this.unixFormat = false;
+                       }
+               else if (  headerBuf[257] == 'u'
+                               && headerBuf[258] == 's'
+                               && headerBuf[259] == 't'
+                               && headerBuf[260] == 'a'
+                               && headerBuf[261] == 'r'
+                               && headerBuf[262] != 0
+                               && headerBuf[263] != 0 )
+                       {
+                       // REVIEW
+                       this.gnuFormat = true;
+                       this.unixFormat = false;
+                       this.ustarFormat = false;
+                       }
+               else
+                       {
+                       StringBuffer buf = new StringBuffer( 128 );
+
+                       buf.append( "header magic is not 'ustar' or unix-style zeros, it is '" );
+                       buf.append( headerBuf[257] );
+                       buf.append( headerBuf[258] );
+                       buf.append( headerBuf[259] );
+                       buf.append( headerBuf[260] );
+                       buf.append( headerBuf[261] );
+                       buf.append( headerBuf[262] );
+                       buf.append( headerBuf[263] );
+                       buf.append( "', or (dec) " );
+                       buf.append( (int)headerBuf[257] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[258] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[259] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[260] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[261] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[262] );
+                       buf.append( ", " );
+                       buf.append( (int)headerBuf[263] );
+
+                       throw new InvalidHeaderException( buf.toString() );
+                       }
+
+               hdr.name = TarHeader.parseFileName( headerBuf );
+
+               offset = TarHeader.NAMELEN;
+
+               hdr.mode = (int)
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
+
+               offset += TarHeader.MODELEN;
+
+               hdr.userId = (int)
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
+
+               offset += TarHeader.UIDLEN;
+
+               hdr.groupId = (int)
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
+
+               offset += TarHeader.GIDLEN;
+
+               hdr.size =
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
+
+               offset += TarHeader.SIZELEN;
+
+               hdr.modTime =
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
+
+               offset += TarHeader.MODTIMELEN;
+
+               hdr.checkSum = (int)
+                       TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
+
+               offset += TarHeader.CHKSUMLEN;
+
+               hdr.linkFlag = headerBuf[ offset++ ];
+
+               hdr.linkName =
+                       TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
+
+               offset += TarHeader.NAMELEN;
+
+               if ( this.ustarFormat )
+                       {
+                       hdr.magic =
+                               TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
+
+                       offset += TarHeader.MAGICLEN;
+
+                       hdr.userName =
+                               TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
+
+                       offset += TarHeader.UNAMELEN;
+
+                       hdr.groupName =
+                               TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
+
+                       offset += TarHeader.GNAMELEN;
+
+                       hdr.devMajor = (int)
+                               TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
+
+                       offset += TarHeader.DEVLEN;
+
+                       hdr.devMinor = (int)
+                               TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
+                       }
+               else
+                       {
+                       hdr.devMajor = 0;
+                       hdr.devMinor = 0;
+                       hdr.magic = new StringBuffer( "" );
+                       hdr.userName = new StringBuffer( "" );
+                       hdr.groupName = new StringBuffer( "" );
+                       }
+               }
+
+       /**
+        * Fill in a TarHeader given only the entry's name.
+        *
+        * @param hdr The TarHeader to fill in.
+        * @param name The tar entry name.
+        */
+       public void
+       nameTarHeader( TarHeader hdr, String name )
+               {
+               boolean isDir = name.endsWith( "/" );
+
+               this.gnuFormat = false;
+               this.ustarFormat = true;
+               this.unixFormat = false;
+
+               hdr.checkSum = 0;
+               hdr.devMajor = 0;
+               hdr.devMinor = 0;
+
+               hdr.name = new StringBuffer( name );
+               hdr.mode = isDir ? 040755 : 0100644;
+               hdr.userId = 0;
+               hdr.groupId = 0;
+               hdr.size = 0;
+               hdr.checkSum = 0;
+
+               hdr.modTime =
+                       (new java.util.Date()).getTime() / 1000;
+
+               hdr.linkFlag =
+                       isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
+
+               hdr.linkName = new StringBuffer( "" );
+               hdr.userName = new StringBuffer( "" );
+               hdr.groupName = new StringBuffer( "" );
+
+               hdr.devMajor = 0;
+               hdr.devMinor = 0;
+               }
+
+       public String
+       toString()
+               {
+               StringBuffer result = new StringBuffer( 128 );
+               return result.
+                       append( "[TarEntry name=" ).
+                       append( this.getName() ).
+                       append( ", isDir=" ).
+                       append( this.isDirectory() ).
+                       append( ", size=" ).
+                       append( this.getSize() ).
+                       append( ", userId=" ).
+                       append( this.getUserId() ).
+                       append( ", user=" ).
+                       append( this.getUserName() ).
+                       append( ", groupId=" ).
+                       append( this.getGroupId() ).
+                       append( ", group=" ).
+                       append( this.getGroupName() ).
+                       append( "]" ).
+                       toString();
+               }
+
+       }
+
+/*
+** Tim feel free to integrate this code here.
+**
+** This code has been placed into the Public Domain.
+** This code was written by David M. Gaskin in 1999.
+**
+*/
+
+/**
+ * Enumerate the contents of a "tar" file.
+ *
+ * Last updated 26th Mar 1999.
+ *
+ * @author  David. M. Gaskin.
+ * @version Version 1.0 Mar 1999
+ * @since    Version 1.0
+ */
+
+public
+static class           TarEntryEnumerator
+implements     Enumeration
+       {
+       /**
+        * The instance on which the enumeration works.
+        */
+       private TarInputStream  tis = null;
+
+       /**
+        * Has EndOfFile been reached?
+        */
+       private boolean                 eof = false;
+
+       /**
+        * The read ahead entry (or <B><I>null</I></B> if no read ahead exists)
+        */
+       private TarEntry                readAhead = null;
+
+       /**
+        * Construct an instance given a TarInputStream. This method is package
+        * private because it is not initially forseen that an instance of this class
+        * should be constructed from outside the package. Should it become necessary
+        * to construct an instance of this class from outside the package in which it
+        * exists then the constructor should be made <B>protected</B> and an empty
+        * subclass should be written in the other package.
+        *
+        * @param <B>tis</B> the <B>TarInputStream</B> on which this enumeration has
+        *  to be based.
+        */
+       public
+       TarEntryEnumerator( TarInputStream tis )
+               {
+               this.tis      = tis;
+               eof           = false;
+               }
+
+       /**
+        * Return the next element in the enumeration. This is a required method
+        * for implementing <B>java.util.Enumeration</B>.
+        *
+        * @return the next Object in the enumeration
+        * @exception <B>NoSuchElementException</B> should an attempt be made to
+        *  read beyond EOF
+        */
+       public Object
+       nextElement()
+               throws NoSuchElementException
+               {
+               if ( eof && ( readAhead == null ) )
+                       throw new NoSuchElementException();
+
+               TarEntry rc = null;
+               if ( readAhead != null )
+                       {
+                       rc        = readAhead;
+                       readAhead = null;
+                       }
+               else
+                       {
+                       rc = getNext();
+                       }
+
+               return rc;
+               }
+
+       /**
+        * Return <B>true</B> if there are more elements in the enumeration.
+        *
+        * @return <B>true</B> if there are more elements in the enumeration.
+        */
+       public boolean
+       hasMoreElements()
+               {
+               if (eof)
+                       return false;
+
+               boolean rc = false;
+               readAhead = getNext();
+               if ( readAhead != null )
+                       rc = true;
+
+               return rc;
+               }
+
+       /**
+        * Return the next element of <B>null</B> if there is no next element or
+        * if an error occured.
+        *
+        * @return the next element of <B>null</B> if there is no next element or
+        * if an error occured.
+        */
+       private TarEntry
+       getNext()
+               {
+               TarEntry rc = null;
+               try {
+                       rc = tis.getNextEntry();
+                       }
+               catch ( IOException ex )
+                       {
+                       // null will be returned but should not occur
+                       ex.printStackTrace();
+                       }
+
+               if ( rc == null )
+                       eof = true;
+
+               return rc;
+               }
+       }
+/*
+** Contributed by "Bay" <bayard@generationjava.com>
+**
+** This code has been placed into the public domain.
+*/
+
+
+// we extend TarOutputStream to have the same type, 
+// BUT, we don't use ANY methods. It's all about 
+// typing.
+
+/**
+ * Outputs tar.gz files. Added functionality that it 
+ * doesn't need to know the size of an entry. If an 
+ * entry has zero size when it is put in the Tar, then 
+ * it buffers it until it's closed and it knows the size.
+ *
+ * @author "Bay" <bayard@generationjava.com>
+ */
+
+public
+static class           TarGzOutputStream
+extends                TarOutputStream
+       {
+    private TarOutputStream                    tos = null;
+    private GZIPOutputStream           gzip = null;
+    private ByteArrayOutputStream      bos = null;
+    private TarEntry                           currentEntry = null;
+
+       public
+       TarGzOutputStream( OutputStream out )
+               throws IOException
+               {
+               super( null );
+               this.gzip = new GZIPOutputStream( out );
+               this.tos = new TarOutputStream( this.gzip );
+               this.bos = new ByteArrayOutputStream();
+               }
+
+       // proxy all methods, but buffer if unknown size
+
+       public void
+       setDebug( boolean b )
+               {
+               this.tos.setDebug(b);
+               }
+
+       public void
+       setBufferDebug( boolean b )
+               {
+               this.tos.setBufferDebug(b);
+               }
+
+       public void
+       finish()
+               throws IOException
+               {
+               if ( this.currentEntry != null )
+                       {
+                       closeEntry();
+                       }
+
+               this.tos.finish();
+               }
+
+       public void
+       close()
+               throws IOException
+               {
+               this.tos.close();
+               this.gzip.finish();
+               }
+
+       public int
+       getRecordSize()
+               {
+               return this.tos.getRecordSize();
+               }
+
+       public void
+       putNextEntry(TarEntry entry)
+               throws IOException
+               {
+               if ( entry.getSize() != 0 )
+                       {
+                       this.tos.putNextEntry( entry );
+                       }
+               else
+                       {
+                       this.currentEntry = entry;
+                       }
+               }
+
+       public void
+       closeEntry()
+               throws IOException
+               {
+               if(this.currentEntry == null)
+                       {
+                       this.tos.closeEntry();
+                       }
+               else
+                       {
+                       this.currentEntry.setSize( bos.size() );
+                       this.tos.putNextEntry( this.currentEntry );
+                       this.bos.writeTo( this.tos );
+                       this.tos.closeEntry();
+                       this.currentEntry = null;
+                       this.bos = new ByteArrayOutputStream();
+                       }
+               }
+
+       public void
+       write( int b )
+               throws IOException
+               {
+               if ( this.currentEntry == null )
+                       {
+                       this.tos.write( b );
+                       }
+               else
+                       {
+                       this.bos.write( b );
+                       }
+               }
+
+       public void
+       write( byte[] b )
+               throws IOException
+               {
+               if ( this.currentEntry == null )
+                       {
+                       this.tos.write( b );
+                       }
+               else
+                       {
+                       this.bos.write( b );
+                       }
+               }
+
+       public void
+       write( byte[] b, int start, int length )
+               throws IOException
+               {
+               if ( this.currentEntry == null )
+                       {
+                       this.tos.write( b, start, length );
+                       }
+               else
+                       {
+                       this.bos.write( b, start, length );
+                       }
+               }
+
+       }
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * This class encapsulates the Tar Entry Header used in Tar Archives.
+ * The class also holds a number of tar constants, used mostly in headers.
+ *
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ */
+
+public
+static class           TarHeader
+extends                Object
+implements     Cloneable
+       {
+       /**
+        * The length of the name field in a header buffer.
+        */
+       public static final int         NAMELEN = 100;
+       /**
+        * The offset of the name field in a header buffer.
+        */
+       public static final int         NAMEOFFSET = 0;
+       /**
+        * The length of the name prefix field in a header buffer.
+        */
+       public static final int         PREFIXLEN = 155;
+       /**
+        * The offset of the name prefix field in a header buffer.
+        */
+       public static final int         PREFIXOFFSET = 345;
+       /**
+        * The length of the mode field in a header buffer.
+        */
+       public static final int         MODELEN = 8;
+       /**
+        * The length of the user id field in a header buffer.
+        */
+       public static final int         UIDLEN = 8;
+       /**
+        * The length of the group id field in a header buffer.
+        */
+       public static final int         GIDLEN = 8;
+       /**
+        * The length of the checksum field in a header buffer.
+        */
+       public static final int         CHKSUMLEN = 8;
+       /**
+        * The length of the size field in a header buffer.
+        */
+       public static final int         SIZELEN = 12;
+       /**
+        * The length of the magic field in a header buffer.
+        */
+       public static final int         MAGICLEN = 8;
+       /**
+        * The length of the modification time field in a header buffer.
+        */
+       public static final int         MODTIMELEN = 12;
+       /**
+        * The length of the user name field in a header buffer.
+        */
+       public static final int         UNAMELEN = 32;
+       /**
+        * The length of the group name field in a header buffer.
+        */
+       public static final int         GNAMELEN = 32;
+       /**
+        * The length of the devices field in a header buffer.
+        */
+       public static final 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.
+        */
+       public static final byte        LF_OLDNORM      = 0;
+       /**
+        * Normal file type.
+        */
+       public static final byte        LF_NORMAL       = (byte) '0';
+       /**
+        * Link file type.
+        */
+       public static final byte        LF_LINK         = (byte) '1';
+       /**
+        * Symbolic link file type.
+        */
+       public static final byte        LF_SYMLINK      = (byte) '2';
+       /**
+        * Character device file type.
+        */
+       public static final byte        LF_CHR          = (byte) '3';
+       /**
+        * Block device file type.
+        */
+       public static final byte        LF_BLK          = (byte) '4';
+       /**
+        * Directory file type.
+        */
+       public static final byte        LF_DIR          = (byte) '5';
+       /**
+        * FIFO (pipe) file type.
+        */
+       public static final byte        LF_FIFO         = (byte) '6';
+       /**
+        * Contiguous file type.
+        */
+       public static final byte        LF_CONTIG       = (byte) '7';
+
+       /**
+        * The magic tag representing a POSIX tar archive.
+        */
+       public static final String      TMAGIC          = "ustar";
+
+       /**
+        * The magic tag representing a GNU tar archive.
+        */
+       public static final String      GNU_TMAGIC      = "ustar  ";
+
+       /**
+        * The entry's name.
+        */
+       public StringBuffer             name;
+       /**
+        * The entry's permission mode.
+        */
+       public int                              mode;
+       /**
+        * The entry's user id.
+        */
+       public int                              userId;
+       /**
+        * The entry's group id.
+        */
+       public int                              groupId;
+       /**
+        * The entry's size.
+        */
+       public long                             size;
+       /**
+        * The entry's modification time.
+        */
+       public long                             modTime;
+       /**
+        * The entry's checksum.
+        */
+       public int                              checkSum;
+       /**
+        * The entry's link flag.
+        */
+       public byte                             linkFlag;
+       /**
+        * The entry's link name.
+        */
+       public StringBuffer             linkName;
+       /**
+        * The entry's magic tag.
+        */
+       public StringBuffer             magic;
+       /**
+        * The entry's user name.
+        */
+       public StringBuffer             userName;
+       /**
+        * The entry's group name.
+        */
+       public StringBuffer             groupName;
+       /**
+        * The entry's major device number.
+        */
+       public int                              devMajor;
+       /**
+        * The entry's minor device number.
+        */
+       public int                              devMinor;
+
+
+       public
+       TarHeader()
+               {
+               this.magic = new StringBuffer( TarHeader.TMAGIC );
+
+               this.name = new StringBuffer();
+               this.linkName = new StringBuffer();
+
+               String user =
+                       System.getProperty( "user.name", "" );
+
+               if ( user.length() > 31 )
+                       user = user.substring( 0, 31 );
+
+               this.userId = 0;
+               this.groupId = 0;
+               this.userName = new StringBuffer( user );
+               this.groupName = new StringBuffer( "" );
+               }
+
+       /**
+        * TarHeaders can be cloned.
+        */
+       public Object
+       clone()
+               {
+               TarHeader hdr = null;
+
+               try {
+                       hdr = (TarHeader) super.clone();
+
+                       hdr.name =
+                               (this.name == null ) ? null
+                                       : new StringBuffer( this.name.toString() );
+                       hdr.mode = this.mode;
+                       hdr.userId = this.userId;
+                       hdr.groupId = this.groupId;
+                       hdr.size = this.size;
+                       hdr.modTime = this.modTime;
+                       hdr.checkSum = this.checkSum;
+                       hdr.linkFlag = this.linkFlag;
+                       hdr.linkName =
+                               (this.linkName == null ) ? null
+                                       : new StringBuffer( this.linkName.toString() );
+                       hdr.magic =
+                               (this.magic == null ) ? null
+                                       : new StringBuffer( this.magic.toString() );
+                       hdr.userName =
+                               (this.userName == null ) ? null
+                                       : new StringBuffer( this.userName.toString() );
+                       hdr.groupName =
+                               (this.groupName == null ) ? null
+                                       : new StringBuffer( this.groupName.toString() );
+                       hdr.devMajor = this.devMajor;
+                       hdr.devMinor = this.devMinor;
+                       }
+               catch ( CloneNotSupportedException ex )
+                       {
+                       ex.printStackTrace( System.err );
+                       }
+
+               return hdr;
+               }
+
+       /**
+        * Get the name of this entry.
+        *
+        * @return Teh entry's name.
+        */
+       public String
+       getName()
+               {
+               return this.name.toString();
+               }
+
+       /**
+        * 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.
+        * @return The long value of the octal string.
+        */
+       public static long
+       parseOctal( byte[] header, int offset, int length )
+               throws InvalidHeaderException
+               {
+               long result = 0;
+               boolean stillPadding = true;
+
+               int end = offset + length;
+               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');
+                       }
+
+               return result;
+               }
+
+       /**
+        * Parse a file name from a header buffer. This is different from
+        * parseName() in that is recognizes 'ustar' names and will handle
+        * adding on the "prefix" field to the name.
+        *
+        * Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
+        *
+        * @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.
+        * @return The header's entry name.
+        */
+       public static StringBuffer
+       parseFileName( byte[] header )
+               {
+               StringBuffer result = new StringBuffer( 256 );
+
+               // If header[345] is not equal to zero, then it is the "prefix"
+               // that 'ustar' defines. It must be prepended to the "normal"
+               // name field. We are responsible for the separating '/'.
+               //
+               if ( header[345] != 0 )
+                       {
+                       for ( int i = 345 ; i < 500 && header[i] != 0 ; ++i )
+                               {
+                               result.append( (char)header[i] );
+                               }
+
+                       result.append( "/" );
+                       }
+
+               for ( int i = 0 ; i < 100 && header[i] != 0 ; ++i )
+                       {
+                       result.append( (char)header[i] );
+                       }
+
+               return result;
+               }
+
+       /**
+        * Parse an entry name from a header buffer.
+        *
+        * @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.
+        * @return The header's entry name.
+        */
+       public static StringBuffer
+       parseName( byte[] header, int offset, int length )
+               throws InvalidHeaderException
+               {
+               StringBuffer result = new StringBuffer( length );
+
+               int end = offset + length;
+               for ( int i = offset ; i < end ; ++i )
+                       {
+                       if ( header[i] == 0 )
+                               break;
+                       result.append( (char)header[i] );
+                       }
+
+               return result;
+               }
+
+       /**
+        * This method, like getNameBytes(), is intended to place a name
+        * into a TarHeader's buffer. However, this method is sophisticated
+        * enough to recognize long names (name.length() > NAMELEN). In these
+        * cases, the method will break the name into a prefix and suffix and
+        * place the name in the header in 'ustar' format. It is up to the
+        * TarEntry to manage the "entry header format". This method assumes
+        * the name is valid for the type of archive being generated.
+        *
+        * @param outbuf The buffer containing the entry header to modify.
+        * @param newName The new name to place into the header buffer.
+        * @return The current offset in the tar header (always TarHeader.NAMELEN).
+        * @throws InvalidHeaderException If the name will not fit in the header.
+        */
+       public static int
+       getFileNameBytes( String newName, byte[] outbuf )
+               throws InvalidHeaderException
+               {
+               if ( newName.length() > 100 )
+                       {
+                       // Locate a pathname "break" prior to the maximum name length...
+                       int index = newName.indexOf( "/", newName.length() - 100 );
+                       if ( index == -1 )
+                               throw new InvalidHeaderException
+                                       ( "file name is greater than 100 characters, " + newName );
+
+                       // Get the "suffix subpath" of the name.
+                       String name = newName.substring( index + 1 );
+
+                       // Get the "prefix subpath", or "prefix", of the name.
+                       String prefix = newName.substring( 0, index );
+                       if ( prefix.length() > TarHeader.PREFIXLEN )
+                               throw new InvalidHeaderException
+                                       ( "file prefix is greater than 155 characters" );
+
+                       TarHeader.getNameBytes
+                               ( new StringBuffer( name ), outbuf,
+                                       TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
+
+                       TarHeader.getNameBytes
+                               ( new StringBuffer( prefix ), outbuf,
+                                       TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN );
+                       }
+               else
+                       {
+                       TarHeader.getNameBytes
+                               ( new StringBuffer( newName ), outbuf,
+                                       TarHeader.NAMEOFFSET, TarHeader.NAMELEN );
+                       }
+
+               // The offset, regardless of the format, is now the end of the
+               // original name field.
+               //
+               return TarHeader.NAMELEN;
+               }
+
+       /**
+        * Move the bytes from the name StringBuffer into the header's buffer.
+        *
+        * @param header The header buffer into which to copy the name.
+        * @param offset The offset into the buffer at which to store.
+        * @param length The number of header bytes to store.
+        * @return The new offset (offset + length).
+        */
+       public static int
+       getNameBytes( StringBuffer name, byte[] buf, int offset, int length )
+               {
+               int i;
+
+               for ( i = 0 ; i < length && i < name.length() ; ++i )
+                       {
+                       buf[ offset + i ] = (byte) name.charAt( i );
+                       }
+
+               for ( ; i < length ; ++i )
+                       {
+                       buf[ offset + i ] = 0;
+                       }
+
+               return offset + length;
+               }
+
+       /**
+        * Parse an octal integer from a header buffer.
+        *
+        * @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.
+        * @return The integer value of the octal bytes.
+        */
+       public static int
+       getOctalBytes( long value, byte[] buf, int offset, int length )
+               {
+               byte[] result = new byte[ 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 )
+                               {
+                               buf[ offset + idx ] = (byte)
+                                       ( (byte) '0' + (byte) (val & 7) );
+                               val = val >> 3;
+                               }
+                       }
+
+               for ( ; idx >= 0 ; --idx )
+                       {
+                       buf[ offset + idx ] = (byte) ' ';
+                       }
+
+               return offset + length;
+               }
+
+       /**
+        * Parse an octal long integer from a header buffer.
+        *
+        * @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.
+        * @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 ];
+               TarHeader.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 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.
+        * @return The integer value of the entry's checksum.
+        */
+       public static int
+       getCheckSumOctalBytes( long value, byte[] buf, int offset, int length )
+               {
+               TarHeader.getOctalBytes( value, buf, offset, length );
+               buf[ offset + length - 1 ] = (byte) ' ';
+               buf[ offset + length - 2 ] = 0;
+               return offset + length;
+               }
+
+       }
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+
+/**
+ * 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().
+ *
+ * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
+ * file sizes greater than 2GB (longs versus ints).
+ *
+ *
+ * @version $Revision: 1.9 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+static class           TarInputStream
+extends                FilterInputStream
+       {
+       protected boolean                       debug;
+       protected boolean                       hasHitEOF;
+
+       protected long                          entrySize;
+       protected long                          entryOffset;
+
+       protected byte[]                        oneBuf;
+       protected byte[]                        readBuf;
+
+       protected TarBuffer                     buffer;
+
+       protected TarEntry                      currEntry;
+
+       protected EntryFactory          eFactory;
+
+
+       public
+       TarInputStream( InputStream is )
+               {
+               this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarInputStream( InputStream is, int blockSize )
+               {
+               this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarInputStream( InputStream is, int blockSize, int recordSize )
+               {
+               super( is );
+
+               this.buffer = new TarBuffer( is, blockSize, recordSize );
+
+               this.readBuf = null;
+               this.oneBuf = new byte[1];
+               this.debug = false;
+               this.hasHitEOF = false;
+               this.eFactory = null;
+               }
+
+       /**
+        * Sets the debugging flag.
+        *
+        * @param debugF True to turn on debugging.
+        */
+       public void
+       setDebug( boolean debugF )
+               {
+               this.debug = debugF;
+               }
+
+       /**
+        * Sets the debugging flag.
+        *
+        * @param debugF True to turn on debugging.
+        */
+       public void
+       setEntryFactory( EntryFactory factory )
+               {
+               this.eFactory = factory;
+               }
+
+       /**
+        * Sets the debugging flag in this stream's TarBuffer.
+        *
+        * @param debugF True to turn on debugging.
+        */
+       public void
+       setBufferDebug( boolean debug )
+               {
+               this.buffer.setDebug( debug );
+               }
+
+       /**
+        * Closes this stream. Calls the TarBuffer's close() method.
+        */
+       public void
+       close()
+               throws IOException
+               {
+               this.buffer.close();
+               }
+
+       /**
+        * Get the record size being used by this stream's TarBuffer.
+        *
+        * @return The TarBuffer record size.
+        */
+       public int
+       getRecordSize()
+               {
+               return this.buffer.getRecordSize();
+               }
+
+       /**
+        * Get the available data that can be read from the current
+        * entry in the archive. This does not indicate how much data
+        * is left in the entire archive, only in the current entry.
+        * This value is determined from the entry's size header field
+        * and the amount of data already read from the current entry.
+        * 
+        *
+        * @return The number of available bytes for the current entry.
+        */
+       public int
+       available()
+               throws IOException
+               {
+               return (int)(this.entrySize - this.entryOffset);
+               }
+
+       /**
+        * Skip bytes in the input buffer. This skips bytes in the
+        * current entry's data, not the entire archive, and will
+        * stop at the end of the current entry's data if the number
+        * to skip extends beyond that point.
+        *
+        * @param numToSkip The number of bytes to skip.
+        * @return The actual number of bytes skipped.
+        */
+       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[ 8 * 1024 ];
+        long num = numToSkip;
+               for ( ; num > 0 ; )
+                       {
+                       int numRead =
+                               this.read( skipBuf, 0,
+                                       ( num > skipBuf.length ? skipBuf.length : (int) num ) );
+
+                       if ( numRead == -1 )
+                               break;
+
+                       num -= numRead;
+                       }
+
+               return ( numToSkip - num );
+               }
+
+       /**
+        * Since we do not support marking just yet, we return false.
+        *
+        * @return False.
+        */
+       public boolean
+       markSupported()
+               {
+               return false;
+               }
+
+       /**
+        * Since we do not support marking just yet, we do nothing.
+        *
+        * @param markLimit The limit to mark.
+        */
+       public void
+       mark( int markLimit )
+               {
+               }
+
+       /**
+        * Since we do not support marking just yet, we do nothing.
+        */
+       public void
+       reset()
+               {
+               }
+
+       /**
+        * Get the number of bytes into the current TarEntry.
+        * This method returns the number of bytes that have been read
+        * from the current TarEntry's data.
+        *
+        * @returns The current entry offset.
+        */
+
+       public long
+       getEntryPosition()
+               {
+               return this.entryOffset;
+               }
+
+       /**
+        * Get the number of bytes into the stream we are currently at.
+        * This method accounts for the blocking stream that tar uses,
+        * so it represents the actual position in input stream, as
+        * opposed to the place where the tar archive parsing is.
+        *
+        * @returns The current file pointer.
+        */
+
+       public long
+       getStreamPosition()
+               {
+               return ( buffer.getBlockSize() * buffer.getCurrentBlockNum() )
+                                       + buffer.getCurrentRecordNum();
+               }
+
+       /**
+        * 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.
+        */
+       public TarEntry
+       getNextEntry()
+               throws IOException
+               {
+               if ( this.hasHitEOF )
+                       return null;
+
+               if ( this.currEntry != null )
+                       {
+                       long numToSkip = (this.entrySize - this.entryOffset);
+
+                       if ( this.debug )
+                       System.err.println
+                               ( "TarInputStream: SKIP currENTRY '"
+                               + this.currEntry.getName() + "' SZ "
+                               + this.entrySize + " OFF " + this.entryOffset
+                               + "  skipping " + numToSkip + " bytes" );
+
+                       if ( numToSkip > 0 )
+                               {
+                               this.skip( numToSkip );
+                               }
+
+                       this.readBuf = null;
+                       }
+
+               byte[] headerBuf = this.buffer.readRecord();
+
+               if ( headerBuf == null )
+                       {
+                       if ( this.debug )
+                               {
+                               System.err.println( "READ NULL RECORD" );
+                               }
+
+                       this.hasHitEOF = true;
+                       }
+               else if ( this.buffer.isEOFRecord( headerBuf ) )
+                       {
+                       if ( this.debug )
+                               {
+                               System.err.println( "READ EOF RECORD" );
+                               }
+
+                       this.hasHitEOF = true;
+                       }
+
+               if ( this.hasHitEOF )
+                       {
+                       this.currEntry = null;
+                       }
+               else
+                       {
+                       try {
+                               if ( this.eFactory == null )
+                                       {
+                                       this.currEntry = new TarEntry( headerBuf );
+                                       }
+                               else
+                                       {
+                                       this.currEntry =
+                                               this.eFactory.createEntry( headerBuf );
+                                       }
+
+                               if ( this.debug )
+                               System.err.println
+                                       ( "TarInputStream: SET CURRENTRY '"
+                                               + this.currEntry.getName()
+                                               + "' size = " + this.currEntry.getSize() );
+
+                               this.entryOffset = 0;
+                               this.entrySize = this.currEntry.getSize();
+                               }
+                       catch ( InvalidHeaderException ex )
+                               {
+                               this.entrySize = 0;
+                               this.entryOffset = 0;
+                               this.currEntry = null;
+                               throw new InvalidHeaderException
+                                       ( "bad header in block "
+                                               + this.buffer.getCurrentBlockNum()
+                                               + " record "
+                                               + this.buffer.getCurrentRecordNum()
+                                               + ", " + ex.getMessage() );
+                               }
+                       }
+
+               return this.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.
+        */
+       public int
+       read()
+               throws IOException
+               {
+               int num = this.read( this.oneBuf, 0, 1 );
+               if ( num == -1 )
+                       return num;
+               else
+                       return (int) this.oneBuf[0];
+               }
+
+       /**
+        * Reads bytes from the current tar archive entry.
+        *
+        * This method simply calls read( byte[], int, int ).
+        *
+        * @param buf The buffer into which to place bytes read.
+        * @return The number of bytes read, or -1 at EOF.
+        */
+       public int
+       read( byte[] buf )
+               throws IOException
+               {
+               return this.read( buf, 0, buf.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 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.
+        */
+       public int
+       read( byte[] buf, int offset, int numToRead )
+               throws IOException
+               {
+               int totalRead = 0;
+
+               if ( this.entryOffset >= this.entrySize )
+                       return -1;
+
+               if ( (numToRead + this.entryOffset) > this.entrySize )
+                       {
+                       numToRead = (int) (this.entrySize - this.entryOffset);
+                       }
+
+               if ( this.readBuf != null )
+                       {
+                       int sz = ( numToRead > this.readBuf.length )
+                                               ? this.readBuf.length : numToRead;
+
+                       System.arraycopy( this.readBuf, 0, buf, offset, sz );
+
+                       if ( sz >= this.readBuf.length )
+                               {
+                               this.readBuf = null;
+                               }
+                       else
+                               {
+                               int newLen = this.readBuf.length - sz;
+                               byte[] newBuf = new byte[ newLen ];
+                               System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
+                               this.readBuf = newBuf;
+                               }
+
+                       totalRead += sz;
+                       numToRead -= sz;
+                       offset += sz;
+                       }
+
+               for ( ; numToRead > 0 ; )
+                       {
+                       byte[] rec = this.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 );
+                               this.readBuf = new byte[ recLen - sz ];
+                               System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
+                               }
+                       else
+                               {
+                               sz = recLen;
+                               System.arraycopy( rec, 0, buf, offset, recLen );
+                               }
+
+                       totalRead += sz;
+                       numToRead -= sz;
+                       offset += sz;
+                       }
+
+               this.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.
+        */
+       public void
+       copyEntryContents( OutputStream out )
+               throws IOException
+               {
+               byte[] buf = new byte[ 32 * 1024 ];
+
+               for ( ; ; )
+                       {
+                       int numRead = this.read( buf, 0, buf.length );
+                       if ( numRead == -1 )
+                               break;
+                       out.write( buf, 0, numRead );
+                       }
+               }
+
+       /**
+        * This interface is provided, with the method setEntryFactory(), to allow
+        * the programmer to have their own TarEntry subclass instantiated for the
+        * entries return from getNextEntry().
+        */
+
+       public
+       interface       EntryFactory
+               {
+               public TarEntry
+                       createEntry( String name );
+
+               public TarEntry
+                       createEntry( File path )
+                               throws InvalidHeaderException;
+
+               public TarEntry
+                       createEntry( byte[] headerBuf )
+                               throws InvalidHeaderException;
+               }
+
+       public
+       class           EntryAdapter
+       implements      EntryFactory
+               {
+               public TarEntry
+               createEntry( String name )
+                       {
+                       return new TarEntry( name );
+                       }
+
+               public TarEntry
+               createEntry( File path )
+                       throws InvalidHeaderException
+                       {
+                       return new TarEntry( path );
+                       }
+
+               public TarEntry
+               createEntry( byte[] headerBuf )
+                       throws InvalidHeaderException
+                       {
+                       return new TarEntry( headerBuf );
+                       }
+               }
+
+       }
+
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+
+/**
+ * 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().
+ *
+ * Kerry Menzel <kmenzel@cfl.rr.com> Contributed the code to support
+ * file sizes greater than 2GB (longs versus ints).
+ *
+ * @version $Revision: 1.8 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+static class           TarOutputStream
+extends                FilterOutputStream
+       {
+       protected boolean                       debug;
+       protected long                          currSize;
+       protected long                          currBytes;
+       protected byte[]                        oneBuf;
+       protected byte[]                        recordBuf;
+       protected int                           assemLen;
+       protected byte[]                        assemBuf;
+       protected TarBuffer                     buffer;
+
+
+       public
+       TarOutputStream( OutputStream os )
+               {
+               this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarOutputStream( OutputStream os, int blockSize )
+               {
+               this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE );
+               }
+
+       public
+       TarOutputStream( OutputStream os, int blockSize, int recordSize )
+               {
+               super( os );
+
+               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];
+               }
+
+       /**
+        * 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 debugF True to turn on debugging.
+        */
+       public void
+       setBufferDebug( boolean debug )
+               {
+               this.buffer.setDebug( debug );
+               }
+
+       /**
+        * Ends the TAR archive without closing the underlying OutputStream.
+        * The result is that the EOF record of nulls is written.
+        */
+
+       public void
+       finish()
+               throws IOException
+               {
+               this.writeEOFRecord();
+               }
+
+       /**
+        * Ends the TAR archive and closes the underlying OutputStream.
+        * This means that finish() is called followed by calling the
+        * TarBuffer's close().
+        */
+
+       public void
+       close()
+               throws IOException
+               {
+               this.finish();
+               this.buffer.close();
+               }
+
+       /**
+        * Get the record size being used by this stream's TarBuffer.
+        *
+        * @return The TarBuffer record size.
+        */
+       public int
+       getRecordSize()
+               {
+               return this.buffer.getRecordSize();
+               }
+
+       /**
+        * 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.
+        *
+        * @param entry The TarEntry to be written to the archive.
+        */
+       public void
+       putNextEntry( TarEntry entry )
+               throws IOException
+               {
+               StringBuffer name = entry.getHeader().name;
+
+               // NOTE
+               // This check is not adequate, because the maximum file length that
+               // can be placed into a POSIX (ustar) header depends on the precise
+               // locations of the path elements (slashes) within the file's full
+               // pathname. For this reason, writeEntryHeader() can still throw an
+               // InvalidHeaderException if the file's full pathname will not fit
+               // in the header.
+
+               if (    ( entry.isUnixTarFormat()
+                                       && name.length() > TarHeader.NAMELEN )
+                       ||
+                               ( ! entry.isUnixTarFormat()
+                                       && name.length() > (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
+                       )
+                       {
+                       throw new InvalidHeaderException
+                               ( "file name '"
+                                       + name
+                                       + "' is too long ( "
+                                       + name.length()
+                                       + " > "
+                                       + ( entry.isUnixTarFormat()
+                                               ? TarHeader.NAMELEN
+                                               : (TarHeader.NAMELEN + TarHeader.PREFIXLEN) )
+                                       + " bytes )" );
+                       }
+
+               entry.writeEntryHeader( this.recordBuf );
+
+               this.buffer.writeRecord( this.recordBuf );
+
+               this.currBytes = 0;
+
+               if ( entry.isDirectory() )
+                       this.currSize = 0;
+               else
+                       this.currSize = entry.getSize();
+               }
+
+       /**
+        * 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.
+        */
+       public void
+       closeEntry()
+               throws IOException
+               {
+               if ( this.assemLen > 0 )
+                       {
+                       for ( int i = this.assemLen ; i < this.assemBuf.length ; ++i )
+                               this.assemBuf[i] = 0;
+
+                       this.buffer.writeRecord( this.assemBuf );
+
+                       this.currBytes += this.assemLen;
+                       this.assemLen = 0;
+                       }
+
+               if ( this.currBytes < this.currSize )
+                       throw new IOException
+                               ( "entry closed at '" + this.currBytes
+                                       + "' before the '" + this.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 ).
+        *
+        * @param b The byte written.
+        */
+       public void
+       write( int b )
+               throws IOException
+               {
+               this.oneBuf[0] = (byte) b;
+               this.write( this.oneBuf, 0, 1 );
+               }
+
+       /**
+        * Writes bytes to the current tar archive entry.
+        *
+        * This method simply calls read( byte[], int, int ).
+        *
+        * @param wBuf The buffer to write to the archive.
+        * @return The number of bytes read, or -1 at EOF.
+        */
+       public void
+       write( byte[] wBuf )
+               throws IOException
+               {
+               this.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.
+        *
+        * This method simply calls read( byte[], int, int ).
+        *
+        * @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.
+        */
+       public void
+       write( byte[] wBuf, int wOffset, int numToWrite )
+               throws IOException
+               {
+               if ( (this.currBytes + numToWrite) > this.currSize )
+                       throw new IOException
+                               ( "request to write '" + numToWrite
+                                       + "' bytes exceeds size in header of '"
+                                       + this.currSize + "' bytes" );
+
+               //
+               // We have to deal with assembly!!!
+               // The programmer can be writing little 32 byte chunks for all
+               // we know, and we must assemble complete records for writing.
+               // REVIEW Maybe this should be in TarBuffer? Could that help to
+               //        eliminate some of the buffer copying.
+               //
+               if ( this.assemLen > 0 )
+                       {
+                       if ( (this.assemLen + numToWrite ) >= this.recordBuf.length )
+                               {
+                               int aLen = this.recordBuf.length - this.assemLen;
+
+                               System.arraycopy
+                                       ( this.assemBuf, 0, this.recordBuf, 0, this.assemLen );
+
+                               System.arraycopy
+                                       ( wBuf, wOffset, this.recordBuf, this.assemLen, aLen );
+
+                               this.buffer.writeRecord( this.recordBuf );
+
+                               this.currBytes += this.recordBuf.length;
+
+                               wOffset += aLen;
+                               numToWrite -= aLen;
+                               this.assemLen = 0;
+                               }
+                       else // ( (this.assemLen + numToWrite ) < this.recordBuf.length )
+                               {
+                               System.arraycopy
+                                       ( wBuf, wOffset, this.assemBuf,
+                                               this.assemLen, numToWrite );
+                               wOffset += numToWrite;
+                               this.assemLen += numToWrite; 
+                               numToWrite -= numToWrite;
+                               }
+                       }
+
+               //
+               // When we get here we have EITHER:
+               //   o An empty "assemble" buffer.
+               //   o No bytes to write (numToWrite == 0)
+               //
+
+               for ( ; numToWrite > 0 ; )
+                       {
+                       if ( numToWrite < this.recordBuf.length )
+                               {
+                               System.arraycopy
+                                       ( wBuf, wOffset, this.assemBuf, this.assemLen, numToWrite );
+                               this.assemLen += numToWrite;
+                               break;
+                               }
+
+                       this.buffer.writeRecord( wBuf, wOffset );
+
+                       long num = this.recordBuf.length;
+                       this.currBytes += num;
+                       numToWrite -= num;
+                       wOffset += num;
+                       }
+               }
+
+       /**
+        * 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 < this.recordBuf.length ; ++i )
+                       this.recordBuf[i] = 0;
+               this.buffer.writeRecord( this.recordBuf );
+               }
+
+       }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * This interface is provided to TarArchive to display progress
+ * information during operation. This is required to display the
+ * results of the 'list' operation.
+ */
+
+public interface
+TarProgressDisplay
+       {
+       /**
+        * Display a progress message.
+        *
+        * @param msg The message to display.
+        */
+
+       public void
+               showTarProgressMessage( String msg );
+       }
+
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * This interface indicates if a file qualifies for ASCII translation.
+ * To support customization of TAR translation, this interface allows
+ * the programmer to provide an object that will check files that do
+ * not match the MIME types file's check for 'text/*' types. To provide
+ * your own typer, subclass this class and set the TarArchive's TransFileTyper
+ * via the method setTransFileTyper().
+ */
+
+public static class
+TarTransFileTyper
+       {
+       /**
+        * Return true if the file should be translated as ASCII.
+        *
+        * @param f The file to be checked to see if it need ASCII translation.
+        */
+
+       public boolean
+       isAsciiFile( File f )
+               {
+               return false;
+               }
+
+       /**
+        * Return true if the file should be translated as ASCII based on its name.
+        * The file DOES NOT EXIST. This is called during extract, so all we know
+        * is the file name.
+        *
+        * @param name The name of the file to be checked to see if it need ASCII
+        *        translation.
+        */
+
+       public boolean
+       isAsciiFile( String name )
+               {
+               return false;
+               }
+
+       }
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time@gjt.org>  <http://www.trustice.com>
+** 
+** This work has been placed into the public domain.
+** You may use this work in any way and for any purpose you wish.
+**
+** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
+** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
+** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
+** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
+** REDISTRIBUTION OF THIS SOFTWARE. 
+** 
+*/
+
+/**
+ * The tar class implements a weak reproduction of the
+ * traditional UNIX tar command. It currently supports
+ * creating, listing, and extracting from archives. It
+ * also supports GZIP-ed archives with the '-z' flag.
+ * See the usage (-? or --usage) for option details.
+ *
+ * <pre>
+ * usage: com.ice.tar.tar has three basic modes:
+ * 
+ *   com.ice.tar -c [options] archive files...
+ *      Create new archive containing files.
+ * 
+ *   com.ice.tar -t [options] archive
+ *      List contents of tar archive
+ * 
+ *   com.ice.tar -x [options] archive
+ *      Extract contents of tar archive.
+ * 
+ * options:
+ *    -f file, use 'file' as the tar archive
+ *    -v, verbose mode
+ *    -z, use GZIP compression
+ *    -D, debug archive and buffer operation
+ *    -b blks, set blocking size to (blks * 512) bytes
+ *    -o, write a V7 format archive rather than ANSI
+ *    -u name, set user name to 'name'
+ *    -U id, set user id to 'id'
+ *    -g name, set group name to 'name'
+ *    -G id, set group id to 'id'
+ *    -?, print usage information
+ *    --trans, translate 'text/*' files
+ *    --mime file, use this mime types file and translate
+ *    --usage, print usage information
+ *    --version, print version information
+ * 
+ * The translation options will translate from local line
+ * endings to UNIX line endings of '\\n' when writing tar
+ * archives, and from UNIX line endings into local line endings
+ * when extracting archives.
+ * 
+ * Written by Tim Endres
+ * This software has been placed into the public domain.
+ * </pre>
+ *
+ * @version $Revision: 1.10 $
+ * @author Timothy Gerard Endres, <time@gjt.org>
+ * @see TarArchive
+ */
+
+public static class
+tar extends Object
+       implements TarProgressDisplay
+       {
+       /**
+        * Flag that determines if debugging information is displayed.
+        */
+       private boolean         debug;
+       /**
+        * Flag that determines if verbose feedback is provided.
+        */
+       private boolean         verbose;
+       /**
+        * Flag that determines if IO is GZIP-ed ('-z' option).
+        */
+       private boolean         compressed;
+       /**
+        * True if we are listing the archive. False if writing or extracting.
+        */
+       private boolean         listingArchive;
+       /**
+        * True if we are writing the archive. False if we are extracting it.
+        */
+       private boolean         writingArchive;
+       /**
+        * True if we are writing an old UNIX archive format (sets entry format).
+        */
+       private boolean         unixArchiveFormat;
+       /**
+        * True if we are not to overwrite existing files.
+        */
+       private boolean         keepOldFiles;
+       /**
+        * True if we are to convert ASCII text files from local line endings
+        * to the UNIX standard '\n'.
+        */
+       private boolean         asciiTranslate;
+       /**
+        * True if a MIME file has been loaded with the '--mime' option.
+        */
+       private boolean         mimeFileLoaded;
+
+       /**
+        * The archive name provided on the command line, null if stdio.
+        */
+       private String          archiveName;
+
+       /**
+        * The blocksize to use for the tar archive IO. Set by the '-b' option.
+        */
+       private int                     blockSize;
+
+       /**
+        * The userId to use for files written to archives. Set by '-U' option.
+        */
+       private int                     userId;
+       /**
+        * The userName to use for files written to archives. Set by '-u' option.
+        */
+       private String          userName;
+       /**
+        * The groupId to use for files written to archives. Set by '-G' option.
+        */
+       private int                     groupId;
+       /**
+        * The groupName to use for files written to archives. Set by '-g' option.
+        */
+       private String          groupName;
+
+
+       /**
+        * The main entry point of the tar class.
+        */
+       public static void
+       main( String argv[] )
+               {
+               tar app = new tar();
+
+               app.instanceMain( argv );
+               }
+
+       /**
+        * Establishes the default userName with the 'user.name' property.
+        */
+       public
+       tar()
+               {
+               this.debug = false;
+               this.verbose = false;
+               this.compressed = false;
+               this.archiveName = null;
+               this.listingArchive = false;
+               this.writingArchive = true;
+               this.unixArchiveFormat = false;
+               this.keepOldFiles = false;
+               this.asciiTranslate = false;
+
+               this.blockSize = TarBuffer.DEFAULT_BLKSIZE;
+
+               String sysUserName =
+                       System.getProperty( "user.name" );
+
+               this.userId = 0;
+               this.userName =
+                       ( (sysUserName == null) ? "" : sysUserName );
+
+               this.groupId = 0;
+               this.groupName = "";
+               }
+
+       /**
+        * This is the "real" main. The class main() instantiates a tar object
+        * for the application and then calls this method. Process the arguments
+        * and perform the requested operation.
+        */
+       public void
+       instanceMain( String argv[] )
+               {
+               TarArchive archive = null;
+
+               int argIdx = this.processArguments( argv );
+
+               if ( writingArchive )                           // WRITING
+                       {
+                       OutputStream outStream = System.out;
+
+                       if ( this.archiveName != null
+                                       && ! this.archiveName.equals( "-" ) )
+                               {
+                               try {
+                                       outStream = new FileOutputStream( this.archiveName );
+                                       }
+                               catch ( IOException ex )
+                                       {
+                                       outStream = null;
+                                       ex.printStackTrace( System.err );
+                                       }
+                               }
+
+                       if ( outStream != null )
+                               {
+                               if ( this.compressed )
+                                       {
+                                       try {
+                                               outStream = new GZIPOutputStream( outStream );
+                                               }
+                                       catch ( IOException ex )
+                                               {
+                                               outStream = null;
+                                               ex.printStackTrace( System.err );
+                                               }
+                                       }
+                               
+                               archive = new TarArchive( outStream, this.blockSize );
+                               }
+                       }
+               else                                                            // EXTRACING OR LISTING
+                       {
+                       InputStream inStream = System.in;
+
+                       if ( this.archiveName != null
+                                       && ! this.archiveName.equals( "-" ) )
+                               {
+                               try {
+                                       inStream = new FileInputStream( this.archiveName );
+                                       }
+                               catch ( IOException ex )
+                                       {
+                                       inStream = null;
+                                       ex.printStackTrace( System.err );
+                                       }
+                               }
+
+                       if ( inStream != null )
+                               {
+                               if ( this.compressed )
+                                       {
+                                       try {
+                                               inStream = new GZIPInputStream( inStream );
+                                               }
+                                       catch ( IOException ex )
+                                               {
+                                               inStream = null;
+                                               ex.printStackTrace( System.err );
+                                               }
+                                       }
+
+                               archive = new TarArchive( inStream, this.blockSize );
+                               }
+                       }
+
+               if ( archive != null )                                          // SET ARCHIVE OPTIONS
+                       {
+                       archive.setDebug( this.debug );
+                       archive.setVerbose( this.verbose );
+                       archive.setTarProgressDisplay( this );
+                       archive.setKeepOldFiles( this.keepOldFiles );
+                       archive.setAsciiTranslation( this.asciiTranslate );
+
+                       archive.setUserInfo(
+                                       this.userId, this.userName,
+                                       this.groupId, this.groupName );
+                       }
+
+               if ( archive == null )
+                       {
+                       System.err.println( "no processing due to errors" );
+                       }
+               else if ( this.writingArchive )                         // WRITING
+                       {
+                       for ( ; argIdx < argv.length ; ++argIdx )
+                               {
+                               try {
+                                       File f = new File( argv[ argIdx ] );
+                               
+                                       TarEntry entry = new TarEntry( f );
+
+                                       if ( this.unixArchiveFormat )
+                                               entry.setUnixTarFormat();
+                                       else
+                                               entry.setUSTarFormat();
+
+                                       archive.writeEntry( entry, true );
+                                       }
+                               catch ( IOException ex )
+                                       {
+                                       ex.printStackTrace( System.err );
+                                       }
+                               }
+                       }
+               else if ( this.listingArchive )                         // LISTING
+                       {
+                       try {
+                               archive.listContents();
+                               }
+                       catch ( InvalidHeaderException ex )
+                               {
+                               ex.printStackTrace( System.err );
+                               }
+                       catch ( IOException ex )
+                               {
+                               ex.printStackTrace( System.err );
+                               }
+                       }
+               else                                                                            // EXTRACTING
+                       {
+                       String userDir =
+                               System.getProperty( "user.dir", null );
+
+                       File destDir = new File( userDir );
+                       if ( ! destDir.exists() )
+                               {
+                               if ( ! destDir.mkdirs() )
+                                       {
+                                       destDir = null;
+                                       Throwable ex = new Throwable
+                                               ( "ERROR, mkdirs() on '" + destDir.getPath()
+                                                       + "' returned false." );
+                                       ex.printStackTrace( System.err );
+                                       }
+                               }
+
+                       if ( destDir != null )
+                               {
+                               try {
+                                       archive.extractContents( destDir );
+                                       }
+                               catch ( InvalidHeaderException ex )
+                                       {
+                                       ex.printStackTrace( System.err );
+                                       }
+                               catch ( IOException ex )
+                                       {
+                                       ex.printStackTrace( System.err );
+                                       }
+                               }
+                       }
+
+               if ( archive != null )                                          // CLOSE ARCHIVE
+                       {
+                       try {
+                               archive.closeArchive();
+                               }
+                       catch ( IOException ex )
+                               {
+                               ex.printStackTrace( System.err );
+                               }
+                       }
+               }
+
+       /**
+        * Process arguments, handling options, and return the index of the
+        * first non-option argument.
+        *
+        * @return The index of the first non-option argument.
+        */
+
+       private int
+       processArguments( String args[] )
+               {
+               int idx = 0;
+               boolean gotOP = false;
+
+               for ( ; idx < args.length ; ++idx )
+                       {
+                       String arg = args[ idx ];
+
+                       if ( ! arg.startsWith( "-" ) )
+                               break;
+
+                       if ( arg.startsWith( "--" ) )
+                               {
+                               if ( arg.equals( "--usage" ) )
+                                       {
+                                       this.usage();
+                                       System.exit(1);
+                                       }
+                               else if ( arg.equals( "--version" ) )
+                                       {
+                                       this.version();
+                                       System.exit(1);
+                                       }
+                               else
+                                       {
+                                       System.err.println
+                                               ( "unknown option: " + arg );
+                                       this.usage();
+                                       System.exit(1);
+                                       }
+                               }
+                       else for ( int cIdx = 1 ; cIdx < arg.length() ; ++cIdx )
+                               {
+                               char ch = arg.charAt( cIdx );
+
+                               if ( ch == '?' )
+                                       {
+                                       this.usage();
+                                       System.exit(1);
+                                       }
+                               else if ( ch == 'f' )
+                                       {
+                                       this.archiveName = args[ ++idx ];
+                                       }
+                               else if ( ch == 'z' )
+                                       {
+                                       this.compressed = true;
+                                       }
+                               else if ( ch == 'c' )
+                                       {
+                                       gotOP = true;
+                                       this.writingArchive = true;
+                                       this.listingArchive = false;
+                                       }
+                               else if ( ch == 'x' )
+                                       {
+                                       gotOP = true;
+                                       this.writingArchive = false;
+                                       this.listingArchive = false;
+                                       }
+                               else if ( ch == 't' )
+                                       {
+                                       gotOP = true;
+                                       this.writingArchive = false;
+                                       this.listingArchive = true;
+                                       }
+                               else if ( ch == 'k' )
+                                       {
+                                       this.keepOldFiles = true;
+                                       }
+                               else if ( ch == 'o' )
+                                       {
+                                       this.unixArchiveFormat = true;
+                                       }
+                               else if ( ch == 'b' )
+                                       {
+                                       try {
+                                               int blks = Integer.parseInt( args[ ++idx ] );
+                                               this.blockSize =
+                                                       ( blks * TarBuffer.DEFAULT_RCDSIZE );
+                                               }
+                                       catch ( NumberFormatException ex )
+                                               {
+                                               ex.printStackTrace( System.err );
+                                               }
+                                       }
+                               else if ( ch == 'u' )
+                                       {
+                                       this.userName = args[ ++idx ];
+                                       }
+                               else if ( ch == 'U' )
+                                       {
+                                       String idStr = args[ ++idx ];
+                                       try {
+                                               this.userId = Integer.parseInt( idStr );
+                                               }
+                                       catch ( NumberFormatException ex )
+                                               {
+                                               this.userId = 0;
+                                               ex.printStackTrace( System.err );
+                                               }
+                                       }
+                               else if ( ch == 'g' )
+                                       {
+                                       this.groupName = args[ ++idx ];
+                                       }
+                               else if ( ch == 'G' )
+                                       {
+                                       String idStr = args[ ++idx ];
+                                       try {
+                                               this.groupId = Integer.parseInt( idStr );
+                                               }
+                                       catch ( NumberFormatException ex )
+                                               {
+                                               this.groupId = 0;
+                                               ex.printStackTrace( System.err );
+                                               }
+                                       }
+                               else if ( ch == 'v' )
+                                       {
+                                       this.verbose = true;
+                                       }
+                               else if ( ch == 'D' )
+                                       {
+                                       this.debug = true;
+                                       }
+                               else
+                                       {
+                                       System.err.println
+                                               ( "unknown option: " + ch );
+                                       this.usage();
+                                       System.exit(1);
+                                       }
+                               }
+                       }
+
+               if ( ! gotOP )
+                       {
+                       System.err.println
+                               ( "you must specify an operation option (c, x, or t)" );
+                       this.usage();
+                       System.exit(1);
+                       }
+
+               return idx;
+               }
+
+        // I N T E R F A C E   TarProgressDisplay
+
+        /**
+        * Display progress information by printing it to System.out.
+        */
+
+       public void
+       showTarProgressMessage( String msg )
+               {
+               System.out.println( msg );
+               }
+
+       /**
+        * Print version information.
+        */
+
+       private void
+       version()
+               {
+               System.err.println
+                       ( "Release 2.4 - $Revision: 1.10 $ $Name:  $" );
+               }
+
+       /**
+        * Print usage information.
+        */
+
+       private void
+       usage()
+               {
+               System.err.println( "usage: com.ice.tar.tar has three basic modes:" );
+               System.err.println( "  com.ice.tar -c [options] archive files..." );
+               System.err.println( "    Create new archive containing files." );
+               System.err.println( "  com.ice.tar -t [options] archive" );
+               System.err.println( "    List contents of tar archive" );
+               System.err.println( "  com.ice.tar -x [options] archive" );
+               System.err.println( "    Extract contents of tar archive." );
+               System.err.println( "" );
+               System.err.println( "options:" );
+               System.err.println( "   -f file, use 'file' as the tar archive" );
+               System.err.println( "   -v, verbose mode" );
+               System.err.println( "   -z, use GZIP compression" );
+               System.err.println( "   -D, debug archive and buffer operation" );
+               System.err.println( "   -b blks, set blocking size to (blks * 512) bytes" );
+               System.err.println( "   -o, write a V7 format archive rather than ANSI" );
+               System.err.println( "   -u name, set user name to 'name'" );
+               System.err.println( "   -U id, set user id to 'id'" );
+               System.err.println( "   -g name, set group name to 'name'" );
+               System.err.println( "   -G id, set group id to 'id'" );
+               System.err.println( "   -?, print usage information" );
+               System.err.println( "   --trans, translate 'text/*' files" );
+               System.err.println( "   --mime file, use this mime types file and translate" );
+               System.err.println( "   --usage, print usage information" );
+               System.err.println( "   --version, print version information" );
+               System.err.println( "" );
+               System.err.println( "The translation options will translate from local line" );
+               System.err.println( "endings to UNIX line endings of '\\n' when writing tar" );
+               System.err.println( "archives, and from UNIX line endings into local line endings" );
+               System.err.println( "when extracting archives." );
+               System.err.println( "" );
+               System.err.println( "Written by Tim Endres" );
+               System.err.println( "" );
+               System.err.println( "This software has been placed into the public domain." );
+               System.err.println( "" );
+
+               this.version();
+
+               System.exit( 1 );
+               }
+
+       }
+
+
+}