001: /*
002: ** Authored by Timothy Gerard Endres
003: ** <mailto:time@gjt.org> <http://www.trustice.com>
004: **
005: ** This work has been placed into the public domain.
006: ** You may use this work in any way and for any purpose you wish.
007: **
008: ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
009: ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
010: ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
011: ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
012: ** REDISTRIBUTION OF THIS SOFTWARE.
013: **
014: */
015:
016: package installer;
017:
018: import java.io.*;
019:
020: /**
021: * The TarBuffer class implements the tar archive concept
022: * of a buffered input stream. This concept goes back to the
023: * days of blocked tape drives and special io devices. In the
024: * Java universe, the only real function that this class
025: * performs is to ensure that files have the correct "block"
026: * size, or other tars will complain.
027: * <p>
028: * You should never have a need to access this class directly.
029: * TarBuffers are created by Tar IO Streams.
030: *
031: * @version $Revision: 4480 $
032: * @author Timothy Gerard Endres,
033: * <a href="mailto:time@gjt.org">time@trustice.com</a>.
034: * @see TarArchive
035: */
036:
037: public class TarBuffer extends Object {
038: public static final int DEFAULT_RCDSIZE = (512);
039: public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
040:
041: private InputStream inStream;
042: private OutputStream outStream;
043:
044: private byte[] blockBuffer;
045: private int currBlkIdx;
046: private int currRecIdx;
047: private int blockSize;
048: private int recordSize;
049: private int recsPerBlock;
050:
051: private boolean debug;
052:
053: public TarBuffer(InputStream inStream) {
054: this (inStream, TarBuffer.DEFAULT_BLKSIZE);
055: }
056:
057: public TarBuffer(InputStream inStream, int blockSize) {
058: this (inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
059: }
060:
061: public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
062: this .inStream = inStream;
063: this .outStream = null;
064: this .initialize(blockSize, recordSize);
065: }
066:
067: public TarBuffer(OutputStream outStream) {
068: this (outStream, TarBuffer.DEFAULT_BLKSIZE);
069: }
070:
071: public TarBuffer(OutputStream outStream, int blockSize) {
072: this (outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
073: }
074:
075: public TarBuffer(OutputStream outStream, int blockSize,
076: int recordSize) {
077: this .inStream = null;
078: this .outStream = outStream;
079: this .initialize(blockSize, recordSize);
080: }
081:
082: /**
083: * Initialization common to all constructors.
084: */
085: private void initialize(int blockSize, int recordSize) {
086: this .debug = false;
087: this .blockSize = blockSize;
088: this .recordSize = recordSize;
089: this .recsPerBlock = (this .blockSize / this .recordSize);
090: this .blockBuffer = new byte[this .blockSize];
091:
092: if (this .inStream != null) {
093: this .currBlkIdx = -1;
094: this .currRecIdx = this .recsPerBlock;
095: } else {
096: this .currBlkIdx = 0;
097: this .currRecIdx = 0;
098: }
099: }
100:
101: /**
102: * Get the TAR Buffer's block size. Blocks consist of multiple records.
103: */
104: public int getBlockSize() {
105: return this .blockSize;
106: }
107:
108: /**
109: * Get the TAR Buffer's record size.
110: */
111: public int getRecordSize() {
112: return this .recordSize;
113: }
114:
115: /**
116: * Set the debugging flag for the buffer.
117: *
118: * @param debug If true, print debugging output.
119: */
120: public void setDebug(boolean debug) {
121: this .debug = debug;
122: }
123:
124: /**
125: * Determine if an archive record indicate End of Archive. End of
126: * archive is indicated by a record that consists entirely of null bytes.
127: *
128: * @param record The record data to check.
129: */
130: public boolean isEOFRecord(byte[] record) {
131: for (int i = 0, sz = this .getRecordSize(); i < sz; ++i)
132: if (record[i] != 0)
133: return false;
134:
135: return true;
136: }
137:
138: /**
139: * Skip over a record on the input stream.
140: */
141:
142: public void skipRecord() throws IOException {
143: if (this .debug) {
144: System.err.println("SkipRecord: recIdx = "
145: + this .currRecIdx + " blkIdx = " + this .currBlkIdx);
146: }
147:
148: if (this .inStream == null)
149: throw new IOException(
150: "reading (via skip) from an output buffer");
151:
152: if (this .currRecIdx >= this .recsPerBlock) {
153: if (!this .readBlock())
154: return; // UNDONE
155: }
156:
157: this .currRecIdx++;
158: }
159:
160: /**
161: * Read a record from the input stream and return the data.
162: *
163: * @return The record data.
164: */
165:
166: public byte[] readRecord() throws IOException {
167: if (this .debug) {
168: System.err.println("ReadRecord: recIdx = "
169: + this .currRecIdx + " blkIdx = " + this .currBlkIdx);
170: }
171:
172: if (this .inStream == null)
173: throw new IOException("reading from an output buffer");
174:
175: if (this .currRecIdx >= this .recsPerBlock) {
176: if (!this .readBlock())
177: return null;
178: }
179:
180: byte[] result = new byte[this .recordSize];
181:
182: System.arraycopy(this .blockBuffer,
183: (this .currRecIdx * this .recordSize), result, 0,
184: this .recordSize);
185:
186: this .currRecIdx++;
187:
188: return result;
189: }
190:
191: /**
192: * @return false if End-Of-File, else true
193: */
194:
195: private boolean readBlock() throws IOException {
196: if (this .debug) {
197: System.err
198: .println("ReadBlock: blkIdx = " + this .currBlkIdx);
199: }
200:
201: if (this .inStream == null)
202: throw new IOException("reading from an output buffer");
203:
204: this .currRecIdx = 0;
205:
206: int offset = 0;
207: int bytesNeeded = this .blockSize;
208: for (; bytesNeeded > 0;) {
209: long numBytes = this .inStream.read(this .blockBuffer,
210: offset, bytesNeeded);
211:
212: //
213: // NOTE
214: // We have fit EOF, and the block is not full!
215: //
216: // This is a broken archive. It does not follow the standard
217: // blocking algorithm. However, because we are generous, and
218: // it requires little effort, we will simply ignore the error
219: // and continue as if the entire block were read. This does
220: // not appear to break anything upstream. We used to return
221: // false in this case.
222: //
223: // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
224: //
225:
226: if (numBytes == -1)
227: break;
228:
229: offset += numBytes;
230: bytesNeeded -= numBytes;
231: if (numBytes != this .blockSize) {
232: if (this .debug) {
233: System.err.println("ReadBlock: INCOMPLETE READ "
234: + numBytes + " of " + this .blockSize
235: + " bytes read.");
236: }
237: }
238: }
239:
240: this .currBlkIdx++;
241:
242: return true;
243: }
244:
245: /**
246: * Get the current block number, zero based.
247: *
248: * @return The current zero based block number.
249: */
250: public int getCurrentBlockNum() {
251: return this .currBlkIdx;
252: }
253:
254: /**
255: * Get the current record number, within the current block, zero based.
256: * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
257: *
258: * @return The current zero based record number.
259: */
260: public int getCurrentRecordNum() {
261: return this .currRecIdx - 1;
262: }
263:
264: /**
265: * Write an archive record to the archive.
266: *
267: * @param record The record data to write to the archive.
268: */
269:
270: public void writeRecord(byte[] record) throws IOException {
271: if (this .debug) {
272: System.err.println("WriteRecord: recIdx = "
273: + this .currRecIdx + " blkIdx = " + this .currBlkIdx);
274: }
275:
276: if (this .outStream == null)
277: throw new IOException("writing to an input buffer");
278:
279: if (record.length != this .recordSize)
280: throw new IOException("record to write has length '"
281: + record.length
282: + "' which is not the record size of '"
283: + this .recordSize + "'");
284:
285: if (this .currRecIdx >= this .recsPerBlock) {
286: this .writeBlock();
287: }
288:
289: System.arraycopy(record, 0, this .blockBuffer,
290: (this .currRecIdx * this .recordSize), this .recordSize);
291:
292: this .currRecIdx++;
293: }
294:
295: /**
296: * Write an archive record to the archive, where the record may be
297: * inside of a larger array buffer. The buffer must be "offset plus
298: * record size" long.
299: *
300: * @param buf The buffer containing the record data to write.
301: * @param offset The offset of the record data within buf.
302: */
303:
304: public void writeRecord(byte[] buf, int offset) throws IOException {
305: if (this .debug) {
306: System.err.println("WriteRecord: recIdx = "
307: + this .currRecIdx + " blkIdx = " + this .currBlkIdx);
308: }
309:
310: if (this .outStream == null)
311: throw new IOException("writing to an input buffer");
312:
313: if ((offset + this .recordSize) > buf.length)
314: throw new IOException("record has length '" + buf.length
315: + "' with offset '" + offset
316: + "' which is less than the record size of '"
317: + this .recordSize + "'");
318:
319: if (this .currRecIdx >= this .recsPerBlock) {
320: this .writeBlock();
321: }
322:
323: System.arraycopy(buf, offset, this .blockBuffer,
324: (this .currRecIdx * this .recordSize), this .recordSize);
325:
326: this .currRecIdx++;
327: }
328:
329: /**
330: * Write a TarBuffer block to the archive.
331: */
332: private void writeBlock() throws IOException {
333: if (this .debug) {
334: System.err.println("WriteBlock: blkIdx = "
335: + this .currBlkIdx);
336: }
337:
338: if (this .outStream == null)
339: throw new IOException("writing to an input buffer");
340:
341: this .outStream.write(this .blockBuffer, 0, this .blockSize);
342: this .outStream.flush();
343:
344: this .currRecIdx = 0;
345: this .currBlkIdx++;
346: }
347:
348: /**
349: * Flush the current data block if it has any data in it.
350: */
351:
352: private void flushBlock() throws IOException {
353: if (this .debug) {
354: System.err.println("TarBuffer.flushBlock() called.");
355: }
356:
357: if (this .outStream == null)
358: throw new IOException("writing to an input buffer");
359:
360: if (this .currRecIdx > 0) {
361: this .writeBlock();
362: }
363: }
364:
365: /**
366: * Close the TarBuffer. If this is an output buffer, also flush the
367: * current block before closing.
368: */
369: public void close() throws IOException {
370: if (this .debug) {
371: System.err.println("TarBuffer.closeBuffer().");
372: }
373:
374: if (this.outStream != null) {
375: this.flushBlock();
376:
377: if (this.outStream != System.out
378: && this.outStream != System.err) {
379: this.outStream.close();
380: this.outStream = null;
381: }
382: } else if (this.inStream != null) {
383: if (this.inStream != System.in) {
384: this.inStream.close();
385: this.inStream = null;
386: }
387: }
388: }
389:
390: }
|