KickJava   Java API By Example, From Geeks To Geeks.

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


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.InputStream JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.util.Arrays JavaDoc;
30
31 /**
32  * The TarBuffer class implements the tar archive concept
33  * of a buffered input stream. This concept goes back to the
34  * days of blocked tape drives and special io devices. In the
35  * Java universe, the only real function that this class
36  * performs is to ensure that files have the correct "block"
37  * size, or other tars will complain.
38  * <p>
39  * You should never have a need to access this class directly.
40  * TarBuffers are created by Tar IO Streams.
41  *
42  */

43
44 public class TarBuffer {
45
46     /** Default record size */
47     public static final int DEFAULT_RCDSIZE = (512);
48
49     /** Default block size */
50     public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
51
52     private InputStream JavaDoc inStream;
53     private OutputStream JavaDoc outStream;
54     private byte[] blockBuffer;
55     private int currBlkIdx;
56     private int currRecIdx;
57     private int blockSize;
58     private int recordSize;
59     private int recsPerBlock;
60     private boolean debug;
61
62     /**
63      * Constructor for a TarBuffer on an input stream.
64      * @param inStream the input stream to use
65      */

66     public TarBuffer(InputStream JavaDoc inStream) {
67         this(inStream, TarBuffer.DEFAULT_BLKSIZE);
68     }
69
70     /**
71      * Constructor for a TarBuffer on an input stream.
72      * @param inStream the input stream to use
73      * @param blockSize the block size to use
74      */

75     public TarBuffer(InputStream JavaDoc inStream, int blockSize) {
76         this(inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
77     }
78
79     /**
80      * Constructor for a TarBuffer on an input stream.
81      * @param inStream the input stream to use
82      * @param blockSize the block size to use
83      * @param recordSize the record size to use
84      */

85     public TarBuffer(InputStream JavaDoc inStream, int blockSize, int recordSize) {
86         this.inStream = inStream;
87         this.outStream = null;
88
89         this.initialize(blockSize, recordSize);
90     }
91
92     /**
93      * Constructor for a TarBuffer on an output stream.
94      * @param outStream the output stream to use
95      */

96     public TarBuffer(OutputStream JavaDoc outStream) {
97         this(outStream, TarBuffer.DEFAULT_BLKSIZE);
98     }
99
100     /**
101      * Constructor for a TarBuffer on an output stream.
102      * @param outStream the output stream to use
103      * @param blockSize the block size to use
104      */

105     public TarBuffer(OutputStream JavaDoc outStream, int blockSize) {
106         this(outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
107     }
108
109     /**
110      * Constructor for a TarBuffer on an output stream.
111      * @param outStream the output stream to use
112      * @param blockSize the block size to use
113      * @param recordSize the record size to use
114      */

115     public TarBuffer(OutputStream JavaDoc outStream, int blockSize, int recordSize) {
116         this.inStream = null;
117         this.outStream = outStream;
118
119         this.initialize(blockSize, recordSize);
120     }
121
122     /**
123      * Initialization common to all constructors.
124      */

125     private void initialize(int blockSize, int recordSize) {
126         this.debug = false;
127         this.blockSize = blockSize;
128         this.recordSize = recordSize;
129         this.recsPerBlock = (this.blockSize / this.recordSize);
130         this.blockBuffer = new byte[this.blockSize];
131
132         if (this.inStream != null) {
133             this.currBlkIdx = -1;
134             this.currRecIdx = this.recsPerBlock;
135         } else {
136             this.currBlkIdx = 0;
137             this.currRecIdx = 0;
138         }
139     }
140
141     /**
142      * Get the TAR Buffer's block size. Blocks consist of multiple records.
143      * @return the block size
144      */

145     public int getBlockSize() {
146         return this.blockSize;
147     }
148
149     /**
150      * Get the TAR Buffer's record size.
151      * @return the record size
152      */

153     public int getRecordSize() {
154         return this.recordSize;
155     }
156
157     /**
158      * Set the debugging flag for the buffer.
159      *
160      * @param debug If true, print debugging output.
161      */

162     public void setDebug(boolean debug) {
163         this.debug = debug;
164     }
165
166     /**
167      * Determine if an archive record indicate End of Archive. End of
168      * archive is indicated by a record that consists entirely of null bytes.
169      *
170      * @param record The record data to check.
171      * @return true if the record data is an End of Archive
172      */

173     public boolean isEOFRecord(byte[] record) {
174         for (int i = 0, sz = this.getRecordSize(); i < sz; ++i) {
175             if (record[i] != 0) {
176                 return false;
177             }
178         }
179
180         return true;
181     }
182
183     /**
184      * Skip over a record on the input stream.
185      * @throws IOException on error
186      */

187     public void skipRecord() throws IOException JavaDoc {
188         if (this.debug) {
189             System.err.println("SkipRecord: recIdx = " + this.currRecIdx
190                                + " blkIdx = " + this.currBlkIdx);
191         }
192
193         if (this.inStream == null) {
194             throw new IOException JavaDoc("reading (via skip) from an output buffer");
195         }
196
197         if (this.currRecIdx >= this.recsPerBlock) {
198             if (!this.readBlock()) {
199                 return; // UNDONE
200
}
201         }
202
203         this.currRecIdx++;
204     }
205
206     /**
207      * Read a record from the input stream and return the data.
208      *
209      * @return The record data.
210      * @throws IOException on error
211      */

212     public byte[] readRecord() throws IOException JavaDoc {
213         if (this.debug) {
214             System.err.println("ReadRecord: recIdx = " + this.currRecIdx
215                                + " blkIdx = " + this.currBlkIdx);
216         }
217
218         if (this.inStream == null) {
219             throw new IOException JavaDoc("reading from an output buffer");
220         }
221
222         if (this.currRecIdx >= this.recsPerBlock) {
223             if (!this.readBlock()) {
224                 return null;
225             }
226         }
227
228         byte[] result = new byte[this.recordSize];
229
230         System.arraycopy(this.blockBuffer,
231                          (this.currRecIdx * this.recordSize), result, 0,
232                          this.recordSize);
233
234         this.currRecIdx++;
235
236         return result;
237     }
238
239     /**
240      * @return false if End-Of-File, else true
241      */

242     private boolean readBlock() throws IOException JavaDoc {
243         if (this.debug) {
244             System.err.println("ReadBlock: blkIdx = " + this.currBlkIdx);
245         }
246
247         if (this.inStream == null) {
248             throw new IOException JavaDoc("reading from an output buffer");
249         }
250
251         this.currRecIdx = 0;
252
253         int offset = 0;
254         int bytesNeeded = this.blockSize;
255
256         while (bytesNeeded > 0) {
257             long numBytes = this.inStream.read(this.blockBuffer, offset,
258                                                bytesNeeded);
259
260             //
261
// NOTE
262
// We have fit EOF, and the block is not full!
263
//
264
// This is a broken archive. It does not follow the standard
265
// blocking algorithm. However, because we are generous, and
266
// it requires little effort, we will simply ignore the error
267
// and continue as if the entire block were read. This does
268
// not appear to break anything upstream. We used to return
269
// false in this case.
270
//
271
// Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
272
//
273
if (numBytes == -1) {
274                 if (offset == 0) {
275                     // Ensure that we do not read gigabytes of zeros
276
// for a corrupt tar file.
277
// See http://issues.apache.org/bugzilla/show_bug.cgi?id=39924
278
return false;
279                 }
280                 // However, just leaving the unread portion of the buffer dirty does
281
// cause problems in some cases. This problem is described in
282
// http://issues.apache.org/bugzilla/show_bug.cgi?id=29877
283
//
284
// The solution is to fill the unused portion of the buffer with zeros.
285

286                 Arrays.fill(blockBuffer, offset, offset + bytesNeeded, (byte) 0);
287
288                 break;
289             }
290
291             offset += numBytes;
292             bytesNeeded -= numBytes;
293
294             if (numBytes != this.blockSize) {
295                 if (this.debug) {
296                     System.err.println("ReadBlock: INCOMPLETE READ "
297                                        + numBytes + " of " + this.blockSize
298                                        + " bytes read.");
299                 }
300             }
301         }
302
303         this.currBlkIdx++;
304
305         return true;
306     }
307
308     /**
309      * Get the current block number, zero based.
310      *
311      * @return The current zero based block number.
312      */

313     public int getCurrentBlockNum() {
314         return this.currBlkIdx;
315     }
316
317     /**
318      * Get the current record number, within the current block, zero based.
319      * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
320      *
321      * @return The current zero based record number.
322      */

323     public int getCurrentRecordNum() {
324         return this.currRecIdx - 1;
325     }
326
327     /**
328      * Write an archive record to the archive.
329      *
330      * @param record The record data to write to the archive.
331      * @throws IOException on error
332      */

333     public void writeRecord(byte[] record) throws IOException JavaDoc {
334         if (this.debug) {
335             System.err.println("WriteRecord: recIdx = " + this.currRecIdx
336                                + " blkIdx = " + this.currBlkIdx);
337         }
338
339         if (this.outStream == null) {
340             throw new IOException JavaDoc("writing to an input buffer");
341         }
342
343         if (record.length != this.recordSize) {
344             throw new IOException JavaDoc("record to write has length '"
345                                   + record.length
346                                   + "' which is not the record size of '"
347                                   + this.recordSize + "'");
348         }
349
350         if (this.currRecIdx >= this.recsPerBlock) {
351             this.writeBlock();
352         }
353
354         System.arraycopy(record, 0, this.blockBuffer,
355                          (this.currRecIdx * this.recordSize),
356                          this.recordSize);
357
358         this.currRecIdx++;
359     }
360
361     /**
362      * Write an archive record to the archive, where the record may be
363      * inside of a larger array buffer. The buffer must be "offset plus
364      * record size" long.
365      *
366      * @param buf The buffer containing the record data to write.
367      * @param offset The offset of the record data within buf.
368      * @throws IOException on error
369      */

370     public void writeRecord(byte[] buf, int offset) throws IOException JavaDoc {
371         if (this.debug) {
372             System.err.println("WriteRecord: recIdx = " + this.currRecIdx
373                                + " blkIdx = " + this.currBlkIdx);
374         }
375
376         if (this.outStream == null) {
377             throw new IOException JavaDoc("writing to an input buffer");
378         }
379
380         if ((offset + this.recordSize) > buf.length) {
381             throw new IOException JavaDoc("record has length '" + buf.length
382                                   + "' with offset '" + offset
383                                   + "' which is less than the record size of '"
384                                   + this.recordSize + "'");
385         }
386
387         if (this.currRecIdx >= this.recsPerBlock) {
388             this.writeBlock();
389         }
390
391         System.arraycopy(buf, offset, this.blockBuffer,
392                          (this.currRecIdx * this.recordSize),
393                          this.recordSize);
394
395         this.currRecIdx++;
396     }
397
398     /**
399      * Write a TarBuffer block to the archive.
400      */

401     private void writeBlock() throws IOException JavaDoc {
402         if (this.debug) {
403             System.err.println("WriteBlock: blkIdx = " + this.currBlkIdx);
404         }
405
406         if (this.outStream == null) {
407             throw new IOException JavaDoc("writing to an input buffer");
408         }
409
410         this.outStream.write(this.blockBuffer, 0, this.blockSize);
411         this.outStream.flush();
412
413         this.currRecIdx = 0;
414         this.currBlkIdx++;
415     }
416
417     /**
418      * Flush the current data block if it has any data in it.
419      */

420     private void flushBlock() throws IOException JavaDoc {
421         if (this.debug) {
422             System.err.println("TarBuffer.flushBlock() called.");
423         }
424
425         if (this.outStream == null) {
426             throw new IOException JavaDoc("writing to an input buffer");
427         }
428
429         if (this.currRecIdx > 0) {
430             this.writeBlock();
431         }
432     }
433
434     /**
435      * Close the TarBuffer. If this is an output buffer, also flush the
436      * current block before closing.
437      * @throws IOException on error
438      */

439     public void close() throws IOException JavaDoc {
440         if (this.debug) {
441             System.err.println("TarBuffer.closeBuffer().");
442         }
443
444         if (this.outStream != null) {
445             this.flushBlock();
446
447             if (this.outStream != System.out
448                     && this.outStream != System.err) {
449                 this.outStream.close();
450
451                 this.outStream = null;
452             }
453         } else if (this.inStream != null) {
454             if (this.inStream != System.in) {
455                 this.inStream.close();
456
457                 this.inStream = null;
458             }
459         }
460     }
461 }
462
Popular Tags