KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > tar > TarInputStream


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 /*
20  * This package is based on the work done by Timothy Gerard Endres
21  * (time@ice.com) to whom the Ant project is very grateful for his great code.
22  */

23
24 package org.apache.tools.tar;
25
26 import java.io.FilterInputStream JavaDoc;
27 import java.io.IOException JavaDoc;
28 import java.io.InputStream JavaDoc;
29 import java.io.OutputStream JavaDoc;
30
31 /**
32  * The TarInputStream reads a UNIX tar archive as an InputStream.
33  * methods are provided to position at each successive entry in
34  * the archive, and the read each entry as a normal input stream
35  * using read().
36  *
37  */

38 public class TarInputStream extends FilterInputStream JavaDoc {
39
40     // CheckStyle:VisibilityModifier OFF - bc
41
protected boolean debug;
42     protected boolean hasHitEOF;
43     protected long entrySize;
44     protected long entryOffset;
45     protected byte[] readBuf;
46     protected TarBuffer buffer;
47     protected TarEntry currEntry;
48
49     /**
50      * This contents of this array is not used at all in this class,
51      * it is only here to avoid repreated object creation during calls
52      * to the no-arg read method.
53      */

54     protected byte[] oneBuf;
55
56     // CheckStyle:VisibilityModifier ON
57

58     /**
59      * Constructor for TarInputStream.
60      * @param is the input stream to use
61      */

62     public TarInputStream(InputStream JavaDoc is) {
63         this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
64     }
65
66     /**
67      * Constructor for TarInputStream.
68      * @param is the input stream to use
69      * @param blockSize the block size to use
70      */

71     public TarInputStream(InputStream JavaDoc is, int blockSize) {
72         this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
73     }
74
75     /**
76      * Constructor for TarInputStream.
77      * @param is the input stream to use
78      * @param blockSize the block size to use
79      * @param recordSize the record size to use
80      */

81     public TarInputStream(InputStream JavaDoc is, int blockSize, int recordSize) {
82         super(is);
83
84         this.buffer = new TarBuffer(is, blockSize, recordSize);
85         this.readBuf = null;
86         this.oneBuf = new byte[1];
87         this.debug = false;
88         this.hasHitEOF = false;
89     }
90
91     /**
92      * Sets the debugging flag.
93      *
94      * @param debug True to turn on debugging.
95      */

96     public void setDebug(boolean debug) {
97         this.debug = debug;
98         this.buffer.setDebug(debug);
99     }
100
101     /**
102      * Closes this stream. Calls the TarBuffer's close() method.
103      * @throws IOException on error
104      */

105     public void close() throws IOException JavaDoc {
106         this.buffer.close();
107     }
108
109     /**
110      * Get the record size being used by this stream's TarBuffer.
111      *
112      * @return The TarBuffer record size.
113      */

114     public int getRecordSize() {
115         return this.buffer.getRecordSize();
116     }
117
118     /**
119      * Get the available data that can be read from the current
120      * entry in the archive. This does not indicate how much data
121      * is left in the entire archive, only in the current entry.
122      * This value is determined from the entry's size header field
123      * and the amount of data already read from the current entry.
124      * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE
125      * bytes are left in the current entry in the archive.
126      *
127      * @return The number of available bytes for the current entry.
128      * @throws IOException for signature
129      */

130     public int available() throws IOException JavaDoc {
131         if (this.entrySize - this.entryOffset > Integer.MAX_VALUE) {
132             return Integer.MAX_VALUE;
133         }
134         return (int) (this.entrySize - this.entryOffset);
135     }
136
137     /**
138      * Skip bytes in the input buffer. This skips bytes in the
139      * current entry's data, not the entire archive, and will
140      * stop at the end of the current entry's data if the number
141      * to skip extends beyond that point.
142      *
143      * @param numToSkip The number of bytes to skip.
144      * @return the number actually skipped
145      * @throws IOException on error
146      */

147     public long skip(long numToSkip) throws IOException JavaDoc {
148         // REVIEW
149
// This is horribly inefficient, but it ensures that we
150
// properly skip over bytes via the TarBuffer...
151
//
152
byte[] skipBuf = new byte[8 * 1024];
153         long skip = numToSkip;
154         while (skip > 0) {
155             int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
156             int numRead = this.read(skipBuf, 0, realSkip);
157             if (numRead == -1) {
158                 break;
159             }
160             skip -= numRead;
161         }
162         return (numToSkip - skip);
163     }
164
165     /**
166      * Since we do not support marking just yet, we return false.
167      *
168      * @return False.
169      */

170     public boolean markSupported() {
171         return false;
172     }
173
174     /**
175      * Since we do not support marking just yet, we do nothing.
176      *
177      * @param markLimit The limit to mark.
178      */

179     public void mark(int markLimit) {
180     }
181
182     /**
183      * Since we do not support marking just yet, we do nothing.
184      */

185     public void reset() {
186     }
187
188     /**
189      * Get the next entry in this tar archive. This will skip
190      * over any remaining data in the current entry, if there
191      * is one, and place the input stream at the header of the
192      * next entry, and read the header and instantiate a new
193      * TarEntry from the header bytes and return that entry.
194      * If there are no more entries in the archive, null will
195      * be returned to indicate that the end of the archive has
196      * been reached.
197      *
198      * @return The next TarEntry in the archive, or null.
199      * @throws IOException on error
200      */

201     public TarEntry getNextEntry() throws IOException JavaDoc {
202         if (this.hasHitEOF) {
203             return null;
204         }
205
206         if (this.currEntry != null) {
207             long numToSkip = this.entrySize - this.entryOffset;
208
209             if (this.debug) {
210                 System.err.println("TarInputStream: SKIP currENTRY '"
211                         + this.currEntry.getName() + "' SZ "
212                         + this.entrySize + " OFF "
213                         + this.entryOffset + " skipping "
214                         + numToSkip + " bytes");
215             }
216
217             if (numToSkip > 0) {
218                 this.skip(numToSkip);
219             }
220
221             this.readBuf = null;
222         }
223
224         byte[] headerBuf = this.buffer.readRecord();
225
226         if (headerBuf == null) {
227             if (this.debug) {
228                 System.err.println("READ NULL RECORD");
229             }
230             this.hasHitEOF = true;
231         } else if (this.buffer.isEOFRecord(headerBuf)) {
232             if (this.debug) {
233                 System.err.println("READ EOF RECORD");
234             }
235             this.hasHitEOF = true;
236         }
237
238         if (this.hasHitEOF) {
239             this.currEntry = null;
240         } else {
241             this.currEntry = new TarEntry(headerBuf);
242
243             if (this.debug) {
244                 System.err.println("TarInputStream: SET CURRENTRY '"
245                         + this.currEntry.getName()
246                         + "' size = "
247                         + this.currEntry.getSize());
248             }
249
250             this.entryOffset = 0;
251
252             this.entrySize = this.currEntry.getSize();
253         }
254
255         if (this.currEntry != null && this.currEntry.isGNULongNameEntry()) {
256             // read in the name
257
StringBuffer JavaDoc longName = new StringBuffer JavaDoc();
258             byte[] buf = new byte[256];
259             int length = 0;
260             while ((length = read(buf)) >= 0) {
261                 longName.append(new String JavaDoc(buf, 0, length));
262             }
263             getNextEntry();
264             if (this.currEntry == null) {
265                 // Bugzilla: 40334
266
// Malformed tar file - long entry name not followed by entry
267
return null;
268             }
269             // remove trailing null terminator
270
if (longName.length() > 0
271                 && longName.charAt(longName.length() - 1) == 0) {
272                 longName.deleteCharAt(longName.length() - 1);
273             }
274             this.currEntry.setName(longName.toString());
275         }
276
277         return this.currEntry;
278     }
279
280     /**
281      * Reads a byte from the current tar archive entry.
282      *
283      * This method simply calls read( byte[], int, int ).
284      *
285      * @return The byte read, or -1 at EOF.
286      * @throws IOException on error
287      */

288     public int read() throws IOException JavaDoc {
289         int num = this.read(this.oneBuf, 0, 1);
290         return num == -1 ? -1 : ((int) this.oneBuf[0]) & 0xFF;
291     }
292
293     /**
294      * Reads bytes from the current tar archive entry.
295      *
296      * This method is aware of the boundaries of the current
297      * entry in the archive and will deal with them as if they
298      * were this stream's start and EOF.
299      *
300      * @param buf The buffer into which to place bytes read.
301      * @param offset The offset at which to place bytes read.
302      * @param numToRead The number of bytes to read.
303      * @return The number of bytes read, or -1 at EOF.
304      * @throws IOException on error
305      */

306     public int read(byte[] buf, int offset, int numToRead) throws IOException JavaDoc {
307         int totalRead = 0;
308
309         if (this.entryOffset >= this.entrySize) {
310             return -1;
311         }
312
313         if ((numToRead + this.entryOffset) > this.entrySize) {
314             numToRead = (int) (this.entrySize - this.entryOffset);
315         }
316
317         if (this.readBuf != null) {
318             int sz = (numToRead > this.readBuf.length) ? this.readBuf.length
319                     : numToRead;
320
321             System.arraycopy(this.readBuf, 0, buf, offset, sz);
322
323             if (sz >= this.readBuf.length) {
324                 this.readBuf = null;
325             } else {
326                 int newLen = this.readBuf.length - sz;
327                 byte[] newBuf = new byte[newLen];
328
329                 System.arraycopy(this.readBuf, sz, newBuf, 0, newLen);
330
331                 this.readBuf = newBuf;
332             }
333
334             totalRead += sz;
335             numToRead -= sz;
336             offset += sz;
337         }
338
339         while (numToRead > 0) {
340             byte[] rec = this.buffer.readRecord();
341
342             if (rec == null) {
343                 // Unexpected EOF!
344
throw new IOException JavaDoc("unexpected EOF with " + numToRead
345                         + " bytes unread");
346             }
347
348             int sz = numToRead;
349             int recLen = rec.length;
350
351             if (recLen > sz) {
352                 System.arraycopy(rec, 0, buf, offset, sz);
353
354                 this.readBuf = new byte[recLen - sz];
355
356                 System.arraycopy(rec, sz, this.readBuf, 0, recLen - sz);
357             } else {
358                 sz = recLen;
359
360                 System.arraycopy(rec, 0, buf, offset, recLen);
361             }
362
363             totalRead += sz;
364             numToRead -= sz;
365             offset += sz;
366         }
367
368         this.entryOffset += totalRead;
369
370         return totalRead;
371     }
372
373     /**
374      * Copies the contents of the current tar archive entry directly into
375      * an output stream.
376      *
377      * @param out The OutputStream into which to write the entry's data.
378      * @throws IOException on error
379      */

380     public void copyEntryContents(OutputStream JavaDoc out) throws IOException JavaDoc {
381         byte[] buf = new byte[32 * 1024];
382
383         while (true) {
384             int numRead = this.read(buf, 0, buf.length);
385
386             if (numRead == -1) {
387                 break;
388             }
389
390             out.write(buf, 0, numRead);
391         }
392     }
393 }
394
Popular Tags