KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > installer > TarInputStream


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
16 package installer;
17
18 import 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: 5354 $
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
36 public
37 class TarInputStream
38 extends 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 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 JavaDoc 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 JavaDoc 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
520
Popular Tags