PageRenderTime 106ms CodeModel.GetById 84ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/installer/TarInputStream.java

#
Java | 519 lines | 310 code | 72 blank | 137 comment | 42 complexity | 52f3ffb1664130c20c241a314f6c5b54 MD5 | raw file
  1/*
  2** Authored by Timothy Gerard Endres
  3** <mailto:time@gjt.org>  <http://www.trustice.com>
  4** 
  5** This work has been placed into the public domain.
  6** You may use this work in any way and for any purpose you wish.
  7**
  8** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  9** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
 10** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
 11** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
 12** REDISTRIBUTION OF THIS SOFTWARE. 
 13** 
 14*/
 15
 16package installer;
 17
 18import java.io.*;
 19
 20
 21/**
 22 * The TarInputStream reads a UNIX tar archive as an InputStream.
 23 * methods are provided to position at each successive entry in
 24 * the archive, and the read each entry as a normal input stream
 25 * using read().
 26 *
 27 * @version $Revision: 4480 $
 28 * @author Timothy Gerard Endres,
 29 *  <a href="mailto:time@gjt.org">time@trustice.com</a>.
 30 * @see TarBuffer
 31 * @see TarHeader
 32 * @see TarEntry
 33 */
 34
 35
 36public
 37class		TarInputStream
 38extends		FilterInputStream
 39	{
 40	protected boolean			debug;
 41	protected boolean			hasHitEOF;
 42
 43	protected int				entrySize;
 44	protected int				entryOffset;
 45
 46	protected byte[]			oneBuf;
 47	protected byte[]			readBuf;
 48
 49	protected TarBuffer			buffer;
 50
 51	protected TarEntry			currEntry;
 52
 53	protected EntryFactory		eFactory;
 54
 55
 56	public
 57	TarInputStream( InputStream is )
 58		{
 59		this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE );
 60		}
 61
 62	public
 63	TarInputStream( InputStream is, int blockSize )
 64		{
 65		this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE );
 66		}
 67
 68	public
 69	TarInputStream( InputStream is, int blockSize, int recordSize )
 70		{
 71		super( is );
 72
 73		this.buffer = new TarBuffer( is, blockSize, recordSize );
 74
 75		this.readBuf = null;
 76		this.oneBuf = new byte[1];
 77		this.debug = false;
 78		this.hasHitEOF = false;
 79		this.eFactory = null;
 80		}
 81
 82	/**
 83	 * Sets the debugging flag.
 84	 *
 85	 * @param debugF True to turn on debugging.
 86	 */
 87	public void
 88	setDebug( boolean debugF )
 89		{
 90		this.debug = debugF;
 91		}
 92
 93	/**
 94	 * Sets the debugging flag.
 95	 *
 96	 * @param debugF True to turn on debugging.
 97	 */
 98	public void
 99	setEntryFactory( EntryFactory factory )
100		{
101		this.eFactory = factory;
102		}
103
104	/**
105	 * Sets the debugging flag in this stream's TarBuffer.
106	 *
107	 * @param debugF True to turn on debugging.
108	 */
109	public void
110	setBufferDebug( boolean debug )
111		{
112		this.buffer.setDebug( debug );
113		}
114
115	/**
116	 * Closes this stream. Calls the TarBuffer's close() method.
117	 */
118	public void
119	close()
120		throws IOException
121		{
122		this.buffer.close();
123		}
124
125	/**
126	 * Get the record size being used by this stream's TarBuffer.
127	 *
128	 * @return The TarBuffer record size.
129	 */
130	public int
131	getRecordSize()
132		{
133		return this.buffer.getRecordSize();
134		}
135
136	/**
137	 * Get the available data that can be read from the current
138	 * entry in the archive. This does not indicate how much data
139	 * is left in the entire archive, only in the current entry.
140	 * This value is determined from the entry's size header field
141	 * and the amount of data already read from the current entry.
142	 * 
143	 *
144	 * @return The number of available bytes for the current entry.
145	 */
146	public int
147	available()
148		throws IOException
149		{
150		return this.entrySize - this.entryOffset;
151		}
152
153	/**
154	 * Skip bytes in the input buffer. This skips bytes in the
155	 * current entry's data, not the entire archive, and will
156	 * stop at the end of the current entry's data if the number
157	 * to skip extends beyond that point.
158	 *
159	 * @param numToSkip The number of bytes to skip.
160	 */
161	public void
162	skip( int numToSkip )
163		throws IOException
164		{
165		// REVIEW
166		// This is horribly inefficient, but it ensures that we
167		// properly skip over bytes via the TarBuffer...
168		//
169
170		byte[] skipBuf = new byte[ 8 * 1024 ];
171
172		for ( int num = numToSkip ; num > 0 ; )
173			{
174			int numRead =
175				this.read( skipBuf, 0,
176					( num > skipBuf.length ? skipBuf.length : num ) );
177
178			if ( numRead == -1 )
179				break;
180
181			num -= numRead;
182			}
183		}
184
185	/**
186	 * Since we do not support marking just yet, we return false.
187	 *
188	 * @return False.
189	 */
190	public boolean
191	markSupported()
192		{
193		return false;
194		}
195
196	/**
197	 * Since we do not support marking just yet, we do nothing.
198	 *
199	 * @param markLimit The limit to mark.
200	 */
201	public void
202	mark( int markLimit )
203		{
204		}
205
206	/**
207	 * Since we do not support marking just yet, we do nothing.
208	 */
209	public void
210	reset()
211		{
212		}
213
214	/**
215	 * Get the next entry in this tar archive. This will skip
216	 * over any remaining data in the current entry, if there
217	 * is one, and place the input stream at the header of the
218	 * next entry, and read the header and instantiate a new
219	 * TarEntry from the header bytes and return that entry.
220	 * If there are no more entries in the archive, null will
221	 * be returned to indicate that the end of the archive has
222	 * been reached.
223	 *
224	 * @return The next TarEntry in the archive, or null.
225	 */
226	public TarEntry
227	getNextEntry()
228		throws IOException
229		{
230		if ( this.hasHitEOF )
231			return null;
232
233		if ( this.currEntry != null )
234			{
235			int numToSkip = this.entrySize - this.entryOffset;
236
237			if ( this.debug )
238			System.err.println
239				( "TarInputStream: SKIP currENTRY '"
240				+ this.currEntry.getName() + "' SZ "
241				+ this.entrySize + " OFF " + this.entryOffset
242				+ "  skipping " + numToSkip + " bytes" );
243
244			if ( numToSkip > 0 )
245				{
246				this.skip( numToSkip );
247				}
248
249			this.readBuf = null;
250			}
251
252		byte[] headerBuf = this.buffer.readRecord();
253
254		if ( headerBuf == null )
255			{
256			if ( this.debug )
257				{
258				System.err.println( "READ NULL RECORD" );
259				}
260
261			this.hasHitEOF = true;
262			}
263		else if ( this.buffer.isEOFRecord( headerBuf ) )
264			{
265			if ( this.debug )
266				{
267				System.err.println( "READ EOF RECORD" );
268				}
269
270			this.hasHitEOF = true;
271			}
272
273		if ( this.hasHitEOF )
274			{
275			this.currEntry = null;
276			}
277		else
278			{
279			try {
280				if ( this.eFactory == null )
281					{
282					this.currEntry = new TarEntry( headerBuf );
283					}
284				else
285					{
286					this.currEntry =
287						this.eFactory.createEntry( headerBuf );
288					}
289
290				if ( ! ( headerBuf[257] == 'u' && headerBuf[258] == 's'
291						&& headerBuf[259] == 't' && headerBuf[260] == 'a'
292						&& headerBuf[261] == 'r' ) )
293					{
294					throw new InvalidHeaderException
295						( "header magic is not 'ustar', but '"
296							+ headerBuf[257] + headerBuf[258] + headerBuf[259]
297							+ headerBuf[260] + headerBuf[261] + "', or (dec) "
298							+ ((int)headerBuf[257]) + ", "
299							+ ((int)headerBuf[258]) + ", "
300							+ ((int)headerBuf[259]) + ", "
301							+ ((int)headerBuf[260]) + ", "
302							+ ((int)headerBuf[261]) );
303					}
304
305				if ( this.debug )
306				System.err.println
307					( "TarInputStream: SET CURRENTRY '"
308						+ this.currEntry.getName()
309						+ "' size = " + this.currEntry.getSize() );
310
311				this.entryOffset = 0;
312				// REVIEW How do we resolve this discrepancy?!
313				this.entrySize = (int) this.currEntry.getSize();
314				}
315			catch ( InvalidHeaderException ex )
316				{
317				this.entrySize = 0;
318				this.entryOffset = 0;
319				this.currEntry = null;
320				throw new InvalidHeaderException
321					( "bad header in block "
322						+ this.buffer.getCurrentBlockNum()
323						+ " record "
324						+ this.buffer.getCurrentRecordNum()
325						+ ", " + ex.getMessage() );
326				}
327			}
328
329		return this.currEntry;
330		}
331
332	/**
333	 * Reads a byte from the current tar archive entry.
334	 *
335	 * This method simply calls read( byte[], int, int ).
336	 *
337	 * @return The byte read, or -1 at EOF.
338	 */
339	public int
340	read()
341		throws IOException
342		{
343		int num = this.read( this.oneBuf, 0, 1 );
344		if ( num == -1 )
345			return num;
346		else
347			return (int) this.oneBuf[0];
348		}
349
350	/**
351	 * Reads bytes from the current tar archive entry.
352	 *
353	 * This method simply calls read( byte[], int, int ).
354	 *
355	 * @param buf The buffer into which to place bytes read.
356	 * @return The number of bytes read, or -1 at EOF.
357	 */
358	public int
359	read( byte[] buf )
360		throws IOException
361		{
362		return this.read( buf, 0, buf.length );
363		}
364
365	/**
366	 * Reads bytes from the current tar archive entry.
367	 *
368	 * This method is aware of the boundaries of the current
369	 * entry in the archive and will deal with them as if they
370	 * were this stream's start and EOF.
371	 *
372	 * @param buf The buffer into which to place bytes read.
373	 * @param offset The offset at which to place bytes read.
374	 * @param numToRead The number of bytes to read.
375	 * @return The number of bytes read, or -1 at EOF.
376	 */
377	public int
378	read( byte[] buf, int offset, int numToRead )
379		throws IOException
380		{
381		int totalRead = 0;
382
383		if ( this.entryOffset >= this.entrySize )
384			return -1;
385
386		if ( (numToRead + this.entryOffset) > this.entrySize )
387			{
388			numToRead = (this.entrySize - this.entryOffset);
389			}
390
391		if ( this.readBuf != null )
392			{
393			int sz = ( numToRead > this.readBuf.length )
394						? this.readBuf.length : numToRead;
395
396			System.arraycopy( this.readBuf, 0, buf, offset, sz );
397
398			if ( sz >= this.readBuf.length )
399				{
400				this.readBuf = null;
401				}
402			else
403				{
404				int newLen = this.readBuf.length - sz;
405				byte[] newBuf = new byte[ newLen ];
406				System.arraycopy( this.readBuf, sz, newBuf, 0, newLen );
407				this.readBuf = newBuf;
408				}
409
410			totalRead += sz;
411			numToRead -= sz;
412			offset += sz;
413			}
414
415		for ( ; numToRead > 0 ; )
416			{
417			byte[] rec = this.buffer.readRecord();
418			if ( rec == null )
419				{
420				// Unexpected EOF!
421				throw new IOException
422					( "unexpected EOF with " + numToRead + " bytes unread" );
423				}
424
425			int sz = numToRead;
426			int recLen = rec.length;
427
428			if ( recLen > sz )
429				{
430				System.arraycopy( rec, 0, buf, offset, sz );
431				this.readBuf = new byte[ recLen - sz ];
432				System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz );
433				}
434			else
435				{
436				sz = recLen;
437				System.arraycopy( rec, 0, buf, offset, recLen );
438				}
439
440			totalRead += sz;
441			numToRead -= sz;
442			offset += sz;
443			}
444
445		this.entryOffset += totalRead;
446
447		return totalRead;
448		}
449
450	/**
451	 * Copies the contents of the current tar archive entry directly into
452	 * an output stream.
453	 *
454	 * @param out The OutputStream into which to write the entry's data.
455	 */
456	public void
457	copyEntryContents( OutputStream out )
458		throws IOException
459		{
460		byte[] buf = new byte[ 32 * 1024 ];
461
462		for ( ; ; )
463			{
464			int numRead = this.read( buf, 0, buf.length );
465			if ( numRead == -1 )
466				break;
467			out.write( buf, 0, numRead );
468			}
469		}
470
471	/**
472	 * This interface is provided, with the method setEntryFactory(), to allow
473	 * the programmer to have their own TarEntry subclass instantiated for the
474	 * entries return from getNextEntry().
475	 */
476
477	public
478	interface	EntryFactory
479		{
480		public TarEntry
481			createEntry( String name );
482
483		public TarEntry
484			createEntry( File path )
485				throws InvalidHeaderException;
486
487		public TarEntry
488			createEntry( byte[] headerBuf )
489				throws InvalidHeaderException;
490		}
491
492	public
493	class		EntryAdapter
494	implements	EntryFactory
495		{
496		public TarEntry
497		createEntry( String name )
498			{
499			return new TarEntry( name );
500			}
501
502		public TarEntry
503		createEntry( File path )
504			throws InvalidHeaderException
505			{
506			return new TarEntry( path );
507			}
508
509		public TarEntry
510		createEntry( byte[] headerBuf )
511			throws InvalidHeaderException
512			{
513			return new TarEntry( headerBuf );
514			}
515		}
516
517	}
518
519