001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.vfs.provider.tar;
018:
019: import java.io.IOException;
020: import java.io.InputStream;
021: import java.io.OutputStream;
022: import java.util.Arrays;
023:
024: /**
025: * The TarBuffer class implements the tar archive concept of a buffered input
026: * stream. This concept goes back to the days of blocked tape drives and special
027: * io devices. In the Java universe, the only real function that this class
028: * performs is to ensure that files have the correct "block" size, or other tars
029: * will complain. <p>
030: *
031: * You should never have a need to access this class directly. TarBuffers are
032: * created by Tar IO Streams.
033: *
034: * @author <a href="mailto:time@ice.com">Timothy Gerard Endres</a>
035: * @author <a href="mailto:peter@apache.org">Peter Donald</a>
036: * @version $Revision: 480428 $ $Date: 2006-11-28 22:15:24 -0800 (Tue, 28 Nov 2006) $
037: */
038: class TarBuffer {
039: public static final int DEFAULT_RECORDSIZE = (512);
040: public static final int DEFAULT_BLOCKSIZE = (DEFAULT_RECORDSIZE * 20);
041:
042: private byte[] m_blockBuffer;
043: private int m_blockSize;
044: private int m_currBlkIdx;
045: private int m_currRecIdx;
046: private boolean m_debug;
047:
048: private InputStream m_input;
049: private OutputStream m_output;
050: private int m_recordSize;
051: private int m_recsPerBlock;
052:
053: TarBuffer(final InputStream input) {
054: this (input, TarBuffer.DEFAULT_BLOCKSIZE);
055: }
056:
057: TarBuffer(final InputStream input, final int blockSize) {
058: this (input, blockSize, TarBuffer.DEFAULT_RECORDSIZE);
059: }
060:
061: TarBuffer(final InputStream input, final int blockSize,
062: final int recordSize) {
063: m_input = input;
064: initialize(blockSize, recordSize);
065: }
066:
067: TarBuffer(final OutputStream output) {
068: this (output, TarBuffer.DEFAULT_BLOCKSIZE);
069: }
070:
071: TarBuffer(final OutputStream output, final int blockSize) {
072: this (output, blockSize, TarBuffer.DEFAULT_RECORDSIZE);
073: }
074:
075: TarBuffer(final OutputStream output, final int blockSize,
076: final int recordSize) {
077: m_output = output;
078: initialize(blockSize, recordSize);
079: }
080:
081: /**
082: * Set the debugging flag for the buffer.
083: *
084: * @param debug If true, print debugging output.
085: */
086: public void setDebug(final boolean debug) {
087: m_debug = debug;
088: }
089:
090: /**
091: * Get the TAR Buffer's block size. Blocks consist of multiple records.
092: *
093: * @return The BlockSize value
094: */
095: public int getBlockSize() {
096: return m_blockSize;
097: }
098:
099: /**
100: * Get the current block number, zero based.
101: *
102: * @return The current zero based block number.
103: */
104: public int getCurrentBlockNum() {
105: return m_currBlkIdx;
106: }
107:
108: /**
109: * Get the current record number, within the current block, zero based.
110: * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
111: *
112: * @return The current zero based record number.
113: */
114: public int getCurrentRecordNum() {
115: return m_currRecIdx - 1;
116: }
117:
118: /**
119: * Get the TAR Buffer's record size.
120: *
121: * @return The RecordSize value
122: */
123: public int getRecordSize() {
124: return m_recordSize;
125: }
126:
127: /**
128: * Determine if an archive record indicate End of Archive. End of archive is
129: * indicated by a record that consists entirely of null bytes.
130: *
131: * @param record The record data to check.
132: * @return The EOFRecord value
133: */
134: public boolean isEOFRecord(final byte[] record) {
135: final int size = getRecordSize();
136: for (int i = 0; i < size; ++i) {
137: if (record[i] != 0) {
138: return false;
139: }
140: }
141:
142: return true;
143: }
144:
145: /**
146: * Close the TarBuffer. If this is an output buffer, also flush the current
147: * block before closing.
148: */
149: public void close() throws IOException {
150: if (m_debug) {
151: debug("TarBuffer.closeBuffer().");
152: }
153:
154: if (null != m_output) {
155: flushBlock();
156:
157: if (m_output != System.out && m_output != System.err) {
158: m_output.close();
159: m_output = null;
160: }
161: } else if (m_input != null) {
162: if (m_input != System.in) {
163: m_input.close();
164: m_input = null;
165: }
166: }
167: }
168:
169: /**
170: * Read a record from the input stream and return the data.
171: *
172: * @return The record data.
173: * @exception IOException Description of Exception
174: */
175: public byte[] readRecord() throws IOException {
176: if (m_debug) {
177: final String message = "ReadRecord: recIdx = "
178: + m_currRecIdx + " blkIdx = " + m_currBlkIdx;
179: debug(message);
180: }
181:
182: if (null == m_input) {
183: final String message = "reading from an output buffer";
184: throw new IOException(message);
185: }
186:
187: if (m_currRecIdx >= m_recsPerBlock) {
188: if (!readBlock()) {
189: return null;
190: }
191: }
192:
193: final byte[] result = new byte[m_recordSize];
194: System.arraycopy(m_blockBuffer, (m_currRecIdx * m_recordSize),
195: result, 0, m_recordSize);
196:
197: m_currRecIdx++;
198:
199: return result;
200: }
201:
202: /**
203: * Skip over a record on the input stream.
204: */
205: public void skipRecord() throws IOException {
206: if (m_debug) {
207: final String message = "SkipRecord: recIdx = "
208: + m_currRecIdx + " blkIdx = " + m_currBlkIdx;
209: debug(message);
210: }
211:
212: if (null == m_input) {
213: final String message = "reading (via skip) from an output buffer";
214: throw new IOException(message);
215: }
216:
217: if (m_currRecIdx >= m_recsPerBlock) {
218: if (!readBlock()) {
219: return;// UNDONE
220: }
221: }
222:
223: m_currRecIdx++;
224: }
225:
226: /**
227: * Write an archive record to the archive.
228: *
229: * @param record The record data to write to the archive.
230: */
231: public void writeRecord(final byte[] record) throws IOException {
232: if (m_debug) {
233: final String message = "WriteRecord: recIdx = "
234: + m_currRecIdx + " blkIdx = " + m_currBlkIdx;
235: debug(message);
236: }
237:
238: if (null == m_output) {
239: final String message = "writing to an input buffer";
240: throw new IOException(message);
241: }
242:
243: if (record.length != m_recordSize) {
244: final String message = "record to write has length '"
245: + record.length
246: + "' which is not the record size of '"
247: + m_recordSize + "'";
248: throw new IOException(message);
249: }
250:
251: if (m_currRecIdx >= m_recsPerBlock) {
252: writeBlock();
253: }
254:
255: System.arraycopy(record, 0, m_blockBuffer,
256: (m_currRecIdx * m_recordSize), m_recordSize);
257:
258: m_currRecIdx++;
259: }
260:
261: /**
262: * Write an archive record to the archive, where the record may be inside of
263: * a larger array buffer. The buffer must be "offset plus record size" long.
264: *
265: * @param buffer The buffer containing the record data to write.
266: * @param offset The offset of the record data within buf.
267: */
268: public void writeRecord(final byte[] buffer, final int offset)
269: throws IOException {
270: if (m_debug) {
271: final String message = "WriteRecord: recIdx = "
272: + m_currRecIdx + " blkIdx = " + m_currBlkIdx;
273: debug(message);
274: }
275:
276: if (null == m_output) {
277: final String message = "writing to an input buffer";
278: throw new IOException(message);
279: }
280:
281: if ((offset + m_recordSize) > buffer.length) {
282: final String message = "record has length '"
283: + buffer.length + "' with offset '" + offset
284: + "' which is less than the record size of '"
285: + m_recordSize + "'";
286: throw new IOException(message);
287: }
288:
289: if (m_currRecIdx >= m_recsPerBlock) {
290: writeBlock();
291: }
292:
293: System.arraycopy(buffer, offset, m_blockBuffer,
294: (m_currRecIdx * m_recordSize), m_recordSize);
295:
296: m_currRecIdx++;
297: }
298:
299: /**
300: * Flush the current data block if it has any data in it.
301: */
302: private void flushBlock() throws IOException {
303: if (m_debug) {
304: final String message = "TarBuffer.flushBlock() called.";
305: debug(message);
306: }
307:
308: if (m_output == null) {
309: final String message = "writing to an input buffer";
310: throw new IOException(message);
311: }
312:
313: if (m_currRecIdx > 0) {
314: writeBlock();
315: }
316: }
317:
318: /**
319: * Initialization common to all constructors.
320: */
321: private void initialize(final int blockSize, final int recordSize) {
322: m_debug = false;
323: m_blockSize = blockSize;
324: m_recordSize = recordSize;
325: m_recsPerBlock = (m_blockSize / m_recordSize);
326: m_blockBuffer = new byte[m_blockSize];
327:
328: if (null != m_input) {
329: m_currBlkIdx = -1;
330: m_currRecIdx = m_recsPerBlock;
331: } else {
332: m_currBlkIdx = 0;
333: m_currRecIdx = 0;
334: }
335: }
336:
337: /**
338: * @return false if End-Of-File, else true
339: */
340: private boolean readBlock() throws IOException {
341: if (m_debug) {
342: final String message = "ReadBlock: blkIdx = "
343: + m_currBlkIdx;
344: debug(message);
345: }
346:
347: if (null == m_input) {
348: final String message = "reading from an output buffer";
349: throw new IOException(message);
350: }
351:
352: m_currRecIdx = 0;
353:
354: int offset = 0;
355: int bytesNeeded = m_blockSize;
356:
357: while (bytesNeeded > 0) {
358: final long numBytes = m_input.read(m_blockBuffer, offset,
359: bytesNeeded);
360:
361: //
362: // NOTE
363: // We have fit EOF, and the block is not full!
364: //
365: // This is a broken archive. It does not follow the standard
366: // blocking algorithm. However, because we are generous, and
367: // it requires little effort, we will simply ignore the error
368: // and continue as if the entire block were read. This does
369: // not appear to break anything upstream. We used to return
370: // false in this case.
371: //
372: // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
373: //
374: if (numBytes == -1) {
375: // However, just leaving the unread portion of the buffer dirty does
376: // cause problems in some cases. This problem is described in
377: // http://issues.apache.org/bugzilla/show_bug.cgi?id=29877
378: //
379: // The solution is to fill the unused portion of the buffer with zeros.
380:
381: Arrays.fill(m_blockBuffer, offset,
382: offset + bytesNeeded, (byte) 0);
383:
384: break;
385: }
386:
387: offset += numBytes;
388: bytesNeeded -= numBytes;
389:
390: if (numBytes != m_blockSize) {
391: if (m_debug) {
392: System.err.println("ReadBlock: INCOMPLETE READ "
393: + numBytes + " of " + m_blockSize
394: + " bytes read.");
395: }
396: }
397: }
398:
399: m_currBlkIdx++;
400:
401: return true;
402: }
403:
404: /**
405: * Write a TarBuffer block to the archive.
406: *
407: * @exception IOException Description of Exception
408: */
409: private void writeBlock() throws IOException {
410: if (m_debug) {
411: final String message = "WriteBlock: blkIdx = "
412: + m_currBlkIdx;
413: debug(message);
414: }
415:
416: if (null == m_output) {
417: final String message = "writing to an input buffer";
418: throw new IOException(message);
419: }
420:
421: m_output.write(m_blockBuffer, 0, m_blockSize);
422: m_output.flush();
423:
424: m_currRecIdx = 0;
425: m_currBlkIdx++;
426: }
427:
428: protected void debug(final String message) {
429: if (m_debug) {
430: System.err.println(message);
431: }
432: }
433: }
|