[jboss-svn-commits] JBoss Common SVN: r4539 - in javatar/trunk/src/main/java: org and 2 other directories.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Mon Jun 21 20:26:28 EDT 2010


Author: ALRubinger
Date: 2010-06-21 20:26:27 -0400 (Mon, 21 Jun 2010)
New Revision: 4539

Added:
   javatar/trunk/src/main/java/org/
   javatar/trunk/src/main/java/org/jboss/
   javatar/trunk/src/main/java/org/jboss/javatar/
   javatar/trunk/src/main/java/org/jboss/javatar/FastTarStream.java
   javatar/trunk/src/main/java/org/jboss/javatar/InvalidHeaderException.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarArchive.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarBuffer.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarEntry.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarEntryEnumerator.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarGzOutputStream.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarHeader.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarInputStream.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarOutputStream.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarProgressDisplay.java
   javatar/trunk/src/main/java/org/jboss/javatar/TarTransFileTyper.java
   javatar/trunk/src/main/java/org/jboss/javatar/package.html
Log:
[SHRINKWRAP-194] Fork the ICE Java TAR Library as standalone Mazenized project in JBoss.org

Added: javatar/trunk/src/main/java/org/jboss/javatar/FastTarStream.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/FastTarStream.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/FastTarStream.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,305 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * 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 at gjt.org>
+ *
+ */
+
+public
+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 );
+			}
+		}
+
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/InvalidHeaderException.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/InvalidHeaderException.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/InvalidHeaderException.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,42 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.IOException;
+
+/**
+ * This exception is used to indicate that there is a problem
+ * with a TAR archive header.
+ */
+
+public class
+InvalidHeaderException extends IOException
+	{
+
+	public
+	InvalidHeaderException()
+		{
+		super();
+		}
+
+	public
+	InvalidHeaderException( String msg )
+		{
+		super( msg );
+		}
+
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarArchive.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarArchive.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarArchive.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,832 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import javax.activation.FileTypeMap;
+import javax.activation.MimeType;
+import javax.activation.MimeTypeParseException;
+
+
+/**
+ * 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 at gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public 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 );
+
+				if ( this.asciiTranslate )
+					{
+					MimeType mime = null;
+					String contentType = null;
+
+					try {
+						contentType =
+							FileTypeMap.getDefaultFileTypeMap().
+								getContentType( destFile );
+
+						mime = new MimeType( contentType );
+
+						if ( mime.getPrimaryType().
+								equalsIgnoreCase( "text" ) )
+							{
+							asciiTrans = true;
+							}
+						else if ( this.transTyper != null )
+							{
+							if ( this.transTyper.isAsciiFile( entry.getName() ) )
+								{
+								asciiTrans = true;
+								}
+							}
+						}
+					catch ( MimeTypeParseException ex )
+						{ }
+
+					if ( this.debug )
+						{
+						System.err.println
+							( "EXTRACT TRANS? '" + asciiTrans
+								+ "'  ContentType='" + contentType
+								+ "'  PrimaryType='" + mime.getPrimaryType()
+								+ "'" );
+						}
+					}
+
+				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() );
+			}
+
+		if ( this.asciiTranslate
+				 && ! entry.isDirectory() )
+			{
+			MimeType mime = null;
+			String contentType = null;
+
+			try {
+				contentType =
+					FileTypeMap.getDefaultFileTypeMap().
+						getContentType( eFile );
+
+				mime = new MimeType( contentType );
+
+				if ( mime.getPrimaryType().
+						equalsIgnoreCase( "text" ) )
+					{
+					asciiTrans = true;
+					}
+				else if ( this.transTyper != null )
+					{
+					if ( this.transTyper.isAsciiFile( eFile ) )
+						{
+						asciiTrans = true;
+						}
+					}
+				}
+			catch ( MimeTypeParseException ex )
+				{
+				// IGNORE THIS ERROR...
+				}
+
+			if ( this.debug )
+				{
+				System.err.println
+					( "CREATE TRANS? '" + asciiTrans
+						+ "'  ContentType='" + contentType
+						+ "'  PrimaryType='" + mime.getPrimaryType()
+						+ "'" );
+				}
+
+			if ( asciiTrans )
+				{
+				String tempFileName =
+					this.getTempFilePath( eFile );
+
+				tFile = new File( tempFileName );
+
+				BufferedReader in =
+					new BufferedReader
+						( new InputStreamReader
+							( new FileInputStream( eFile ) ) );
+
+				BufferedOutputStream out =
+					new BufferedOutputStream
+						( new FileOutputStream( tFile ) );
+
+				for ( ; ; )
+					{
+					String line = in.readLine();
+					if ( line == null )
+						break;
+
+					out.write( line.getBytes() );
+					out.write( (byte)'\n' );
+					}
+
+				in.close();
+				out.flush();
+				out.close();
+
+				entry.setSize( tFile.length() );
+
+				eFile = tFile;
+				}
+			}
+
+		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();
+			}
+		}
+
+	}
+
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarBuffer.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarBuffer.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarBuffer.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,499 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * 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 at gjt.org">time at trustice.com</a>.
+ * @see TarArchive
+ */
+
+public
+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 at 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 at 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;
+				}
+			}
+		}
+
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarEntry.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarEntry.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarEntry.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,1014 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.File;
+import java.util.Date;
+
+
+/**
+ *
+ * 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 at informatik.hu-berlin.de>
+ *
+ * @see TarHeader
+ * @author Timothy Gerard Endres, <time at gjt.org>
+ */
+
+public
+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 at 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 at 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();
+		}
+
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarEntryEnumerator.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarEntryEnumerator.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarEntryEnumerator.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,135 @@
+/*
+** 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.
+**
+*/
+
+package org.jboss.javatar;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * 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
+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;
+		}
+	}

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarGzOutputStream.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarGzOutputStream.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarGzOutputStream.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,162 @@
+/*
+** Contributed by "Bay" <bayard at generationjava.com>
+**
+** This code has been placed into the public domain.
+*/
+
+package org.jboss.javatar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+
+// 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 at generationjava.com>
+ */
+
+public
+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 );
+			}
+		}
+
+	}

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarHeader.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarHeader.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarHeader.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,533 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+/**
+ * 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 at gjt.org>
+ */
+
+public
+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 at 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;
+		}
+
+	}
+ 

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarInputStream.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarInputStream.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarInputStream.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,543 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.File;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * The TarInputStream reads a UNIX tar archive as an InputStream.
+ * methods are provided to position at each successive entry in
+ * the archive, and the read each entry as a normal input stream
+ * using read().
+ *
+ * Kerry Menzel <kmenzel at 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 at gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+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 );
+			}
+		}
+
+	}
+
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarOutputStream.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarOutputStream.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarOutputStream.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,357 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * The TarOutputStream writes a UNIX tar archive as an OutputStream.
+ * Methods are provided to put entries, and then write their contents
+ * by writing to this stream using write().
+ *
+ * Kerry Menzel <kmenzel at 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 at gjt.org>
+ * @see TarBuffer
+ * @see TarHeader
+ * @see TarEntry
+ */
+
+
+public
+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 );
+		}
+
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarProgressDisplay.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarProgressDisplay.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarProgressDisplay.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,36 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+/**
+ * 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 );
+	}
+

Added: javatar/trunk/src/main/java/org/jboss/javatar/TarTransFileTyper.java
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/TarTransFileTyper.java	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/TarTransFileTyper.java	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,59 @@
+/*
+** Authored by Timothy Gerard Endres
+** <mailto:time at 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.jboss.javatar;
+
+import java.io.File;
+
+/**
+ * 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 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;
+		}
+
+	}

Added: javatar/trunk/src/main/java/org/jboss/javatar/package.html
===================================================================
--- javatar/trunk/src/main/java/org/jboss/javatar/package.html	                        (rev 0)
+++ javatar/trunk/src/main/java/org/jboss/javatar/package.html	2010-06-22 00:26:27 UTC (rev 4539)
@@ -0,0 +1,34 @@
+<html>
+
+<head>
+</head>
+
+<body>
+
+Implements support for UNIX tar archives.
+
+<p>
+This package implements the UNIX tar archive, as well
+as an equivalent of the tar command.
+
+<p>
+The package makes use of the Java Activation package
+to support the translation of ASCII files via MIME
+types. This allows you to convert text files, based
+on their extension, as you archive and extract files.
+
+<p>
+The package is designed to allow you to integrate tar
+archive support into any Java application.
+
+<p>
+This implementation is a fork of the original work done by 
+Tim Endres in <a href="http://www.trustice.com/java/tar/">the ICE
+Java TAR library</a>. 
+
+ at author Tim Endres <mailto:time at gjt.com>
+ at author Andrew Lee Rubinger <mailto:alr at jboss.org>
+
+</body>
+
+</html>



More information about the jboss-svn-commits mailing list