1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.vfs.provider.tar;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.util.Arrays;
23
24 /***
25 * The TarBuffer class implements the tar archive concept of a buffered input
26 * stream. This concept goes back to the days of blocked tape drives and special
27 * io devices. In the Java universe, the only real function that this class
28 * performs is to ensure that files have the correct "block" size, or other tars
29 * will complain. <p>
30 *
31 * You should never have a need to access this class directly. TarBuffers are
32 * created by Tar IO Streams.
33 *
34 * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
35 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
36 * @version $Revision: 480428 $ $Date: 2006-11-29 07:15:24 +0100 (Mi, 29 Nov 2006) $
37 */
38 class TarBuffer
39 {
40 public static final int DEFAULT_RECORDSIZE = ( 512 );
41 public static final int DEFAULT_BLOCKSIZE = ( DEFAULT_RECORDSIZE * 20 );
42
43 private byte[] m_blockBuffer;
44 private int m_blockSize;
45 private int m_currBlkIdx;
46 private int m_currRecIdx;
47 private boolean m_debug;
48
49 private InputStream m_input;
50 private OutputStream m_output;
51 private int m_recordSize;
52 private int m_recsPerBlock;
53
54 TarBuffer( final InputStream input )
55 {
56 this( input, TarBuffer.DEFAULT_BLOCKSIZE );
57 }
58
59 TarBuffer( final InputStream input, final int blockSize )
60 {
61 this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
62 }
63
64 TarBuffer( final InputStream input,
65 final int blockSize,
66 final int recordSize )
67 {
68 m_input = input;
69 initialize( blockSize, recordSize );
70 }
71
72 TarBuffer( final OutputStream output )
73 {
74 this( output, TarBuffer.DEFAULT_BLOCKSIZE );
75 }
76
77 TarBuffer( final OutputStream output, final int blockSize )
78 {
79 this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
80 }
81
82 TarBuffer( final OutputStream output,
83 final int blockSize,
84 final int recordSize )
85 {
86 m_output = output;
87 initialize( blockSize, recordSize );
88 }
89
90 /***
91 * Set the debugging flag for the buffer.
92 *
93 * @param debug If true, print debugging output.
94 */
95 public void setDebug( final boolean debug )
96 {
97 m_debug = debug;
98 }
99
100 /***
101 * Get the TAR Buffer's block size. Blocks consist of multiple records.
102 *
103 * @return The BlockSize value
104 */
105 public int getBlockSize()
106 {
107 return m_blockSize;
108 }
109
110 /***
111 * Get the current block number, zero based.
112 *
113 * @return The current zero based block number.
114 */
115 public int getCurrentBlockNum()
116 {
117 return m_currBlkIdx;
118 }
119
120 /***
121 * Get the current record number, within the current block, zero based.
122 * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
123 *
124 * @return The current zero based record number.
125 */
126 public int getCurrentRecordNum()
127 {
128 return m_currRecIdx - 1;
129 }
130
131 /***
132 * Get the TAR Buffer's record size.
133 *
134 * @return The RecordSize value
135 */
136 public int getRecordSize()
137 {
138 return m_recordSize;
139 }
140
141 /***
142 * Determine if an archive record indicate End of Archive. End of archive is
143 * indicated by a record that consists entirely of null bytes.
144 *
145 * @param record The record data to check.
146 * @return The EOFRecord value
147 */
148 public boolean isEOFRecord( final byte[] record )
149 {
150 final int size = getRecordSize();
151 for( int i = 0; i < size; ++i )
152 {
153 if( record[ i ] != 0 )
154 {
155 return false;
156 }
157 }
158
159 return true;
160 }
161
162 /***
163 * Close the TarBuffer. If this is an output buffer, also flush the current
164 * block before closing.
165 */
166 public void close()
167 throws IOException
168 {
169 if( m_debug )
170 {
171 debug( "TarBuffer.closeBuffer()." );
172 }
173
174 if( null != m_output )
175 {
176 flushBlock();
177
178 if( m_output != System.out && m_output != System.err )
179 {
180 m_output.close();
181 m_output = null;
182 }
183 }
184 else if( m_input != null )
185 {
186 if( m_input != System.in )
187 {
188 m_input.close();
189 m_input = null;
190 }
191 }
192 }
193
194 /***
195 * Read a record from the input stream and return the data.
196 *
197 * @return The record data.
198 * @exception IOException Description of Exception
199 */
200 public byte[] readRecord()
201 throws IOException
202 {
203 if( m_debug )
204 {
205 final String message = "ReadRecord: recIdx = " + m_currRecIdx +
206 " blkIdx = " + m_currBlkIdx;
207 debug( message );
208 }
209
210 if( null == m_input )
211 {
212 final String message = "reading from an output buffer";
213 throw new IOException( message );
214 }
215
216 if( m_currRecIdx >= m_recsPerBlock )
217 {
218 if( !readBlock() )
219 {
220 return null;
221 }
222 }
223
224 final byte[] result = new byte[ m_recordSize ];
225 System.arraycopy( m_blockBuffer,
226 ( m_currRecIdx * m_recordSize ),
227 result,
228 0,
229 m_recordSize );
230
231 m_currRecIdx++;
232
233 return result;
234 }
235
236 /***
237 * Skip over a record on the input stream.
238 */
239 public void skipRecord()
240 throws IOException
241 {
242 if( m_debug )
243 {
244 final String message = "SkipRecord: recIdx = " + m_currRecIdx +
245 " blkIdx = " + m_currBlkIdx;
246 debug( message );
247 }
248
249 if( null == m_input )
250 {
251 final String message = "reading (via skip) from an output buffer";
252 throw new IOException( message );
253 }
254
255 if( m_currRecIdx >= m_recsPerBlock )
256 {
257 if( !readBlock() )
258 {
259 return;
260 }
261 }
262
263 m_currRecIdx++;
264 }
265
266 /***
267 * Write an archive record to the archive.
268 *
269 * @param record The record data to write to the archive.
270 */
271 public void writeRecord( final byte[] record )
272 throws IOException
273 {
274 if( m_debug )
275 {
276 final String message = "WriteRecord: recIdx = " + m_currRecIdx +
277 " blkIdx = " + m_currBlkIdx;
278 debug( message );
279 }
280
281 if( null == m_output )
282 {
283 final String message = "writing to an input buffer";
284 throw new IOException( message );
285 }
286
287 if( record.length != m_recordSize )
288 {
289 final String message = "record to write has length '" +
290 record.length + "' which is not the record size of '" +
291 m_recordSize + "'";
292 throw new IOException( message );
293 }
294
295 if( m_currRecIdx >= m_recsPerBlock )
296 {
297 writeBlock();
298 }
299
300 System.arraycopy( record,
301 0,
302 m_blockBuffer,
303 ( m_currRecIdx * m_recordSize ),
304 m_recordSize );
305
306 m_currRecIdx++;
307 }
308
309 /***
310 * Write an archive record to the archive, where the record may be inside of
311 * a larger array buffer. The buffer must be "offset plus record size" long.
312 *
313 * @param buffer The buffer containing the record data to write.
314 * @param offset The offset of the record data within buf.
315 */
316 public void writeRecord( final byte[] buffer, final int offset )
317 throws IOException
318 {
319 if( m_debug )
320 {
321 final String message = "WriteRecord: recIdx = " + m_currRecIdx +
322 " blkIdx = " + m_currBlkIdx;
323 debug( message );
324 }
325
326 if( null == m_output )
327 {
328 final String message = "writing to an input buffer";
329 throw new IOException( message );
330 }
331
332 if( ( offset + m_recordSize ) > buffer.length )
333 {
334 final String message = "record has length '" + buffer.length +
335 "' with offset '" + offset + "' which is less than the record size of '" +
336 m_recordSize + "'";
337 throw new IOException( message );
338 }
339
340 if( m_currRecIdx >= m_recsPerBlock )
341 {
342 writeBlock();
343 }
344
345 System.arraycopy( buffer,
346 offset,
347 m_blockBuffer,
348 ( m_currRecIdx * m_recordSize ),
349 m_recordSize );
350
351 m_currRecIdx++;
352 }
353
354 /***
355 * Flush the current data block if it has any data in it.
356 */
357 private void flushBlock()
358 throws IOException
359 {
360 if( m_debug )
361 {
362 final String message = "TarBuffer.flushBlock() called.";
363 debug( message );
364 }
365
366 if( m_output == null )
367 {
368 final String message = "writing to an input buffer";
369 throw new IOException( message );
370 }
371
372 if( m_currRecIdx > 0 )
373 {
374 writeBlock();
375 }
376 }
377
378 /***
379 * Initialization common to all constructors.
380 */
381 private void initialize( final int blockSize, final int recordSize )
382 {
383 m_debug = false;
384 m_blockSize = blockSize;
385 m_recordSize = recordSize;
386 m_recsPerBlock = ( m_blockSize / m_recordSize );
387 m_blockBuffer = new byte[ m_blockSize ];
388
389 if( null != m_input )
390 {
391 m_currBlkIdx = -1;
392 m_currRecIdx = m_recsPerBlock;
393 }
394 else
395 {
396 m_currBlkIdx = 0;
397 m_currRecIdx = 0;
398 }
399 }
400
401 /***
402 * @return false if End-Of-File, else true
403 */
404 private boolean readBlock()
405 throws IOException
406 {
407 if( m_debug )
408 {
409 final String message = "ReadBlock: blkIdx = " + m_currBlkIdx;
410 debug( message );
411 }
412
413 if( null == m_input )
414 {
415 final String message = "reading from an output buffer";
416 throw new IOException( message );
417 }
418
419 m_currRecIdx = 0;
420
421 int offset = 0;
422 int bytesNeeded = m_blockSize;
423
424 while( bytesNeeded > 0 )
425 {
426 final long numBytes = m_input.read( m_blockBuffer, offset, bytesNeeded );
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441 if( numBytes == -1 )
442 {
443
444
445
446
447
448
449 Arrays.fill(m_blockBuffer, offset, offset + bytesNeeded, (byte) 0);
450
451 break;
452 }
453
454 offset += numBytes;
455 bytesNeeded -= numBytes;
456
457 if( numBytes != m_blockSize )
458 {
459 if( m_debug )
460 {
461 System.err.println( "ReadBlock: INCOMPLETE READ "
462 + numBytes + " of " + m_blockSize
463 + " bytes read." );
464 }
465 }
466 }
467
468 m_currBlkIdx++;
469
470 return true;
471 }
472
473 /***
474 * Write a TarBuffer block to the archive.
475 *
476 * @exception IOException Description of Exception
477 */
478 private void writeBlock()
479 throws IOException
480 {
481 if( m_debug )
482 {
483 final String message = "WriteBlock: blkIdx = " + m_currBlkIdx;
484 debug( message );
485 }
486
487 if( null == m_output )
488 {
489 final String message = "writing to an input buffer";
490 throw new IOException( message );
491 }
492
493 m_output.write( m_blockBuffer, 0, m_blockSize );
494 m_output.flush();
495
496 m_currRecIdx = 0;
497 m_currBlkIdx++;
498 }
499
500 protected void debug( final String message )
501 {
502 if( m_debug )
503 {
504 System.err.println( message );
505 }
506 }
507 }