2015-07-28 20:36:41 +08:00
|
|
|
/*
|
|
|
|
|
* BlockOutputStream
|
|
|
|
|
*
|
|
|
|
|
* Author: Lasse Collin <lasse.collin@tukaani.org>
|
|
|
|
|
*
|
|
|
|
|
* This file has been put into the public domain.
|
|
|
|
|
* You can do whatever you want with this file.
|
|
|
|
|
*/
|
|
|
|
|
package org.tukaani.xz;
|
|
|
|
|
|
|
|
|
|
import java.io.OutputStream;
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import org.tukaani.xz.common.EncoderUtil;
|
|
|
|
|
import org.tukaani.xz.check.Check;
|
|
|
|
|
|
|
|
|
|
class BlockOutputStream extends FinishableOutputStream {
|
2015-11-17 12:51:04 +08:00
|
|
|
|
2015-07-28 20:36:41 +08:00
|
|
|
private final OutputStream out;
|
|
|
|
|
private final CountingOutputStream outCounted;
|
|
|
|
|
private FinishableOutputStream filterChain;
|
|
|
|
|
private final Check check;
|
|
|
|
|
|
|
|
|
|
private final int headerSize;
|
|
|
|
|
private final long compressedSizeLimit;
|
|
|
|
|
private long uncompressedSize = 0;
|
|
|
|
|
|
|
|
|
|
private final byte[] tempBuf = new byte[1];
|
|
|
|
|
|
|
|
|
|
public BlockOutputStream(OutputStream out, FilterEncoder[] filters,
|
|
|
|
|
Check check) throws IOException {
|
|
|
|
|
this.out = out;
|
|
|
|
|
this.check = check;
|
|
|
|
|
|
|
|
|
|
// Initialize the filter chain.
|
|
|
|
|
outCounted = new CountingOutputStream(out);
|
|
|
|
|
filterChain = outCounted;
|
|
|
|
|
for (int i = filters.length - 1; i >= 0; --i)
|
|
|
|
|
filterChain = filters[i].getOutputStream(filterChain);
|
|
|
|
|
|
|
|
|
|
// Prepare to encode the Block Header field.
|
|
|
|
|
ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
|
|
|
|
|
|
|
|
|
|
// Write a dummy Block Header Size field. The real value is written
|
|
|
|
|
// once everything else except CRC32 has been written.
|
|
|
|
|
bufStream.write(0x00);
|
|
|
|
|
|
|
|
|
|
// Write Block Flags. Storing Compressed Size or Uncompressed Size
|
|
|
|
|
// isn't supported for now.
|
|
|
|
|
bufStream.write(filters.length - 1);
|
|
|
|
|
|
|
|
|
|
// List of Filter Flags
|
|
|
|
|
for (int i = 0; i < filters.length; ++i) {
|
|
|
|
|
EncoderUtil.encodeVLI(bufStream, filters[i].getFilterID());
|
|
|
|
|
byte[] filterProps = filters[i].getFilterProps();
|
|
|
|
|
EncoderUtil.encodeVLI(bufStream, filterProps.length);
|
|
|
|
|
bufStream.write(filterProps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Header Padding
|
|
|
|
|
while ((bufStream.size() & 3) != 0)
|
|
|
|
|
bufStream.write(0x00);
|
|
|
|
|
|
|
|
|
|
byte[] buf = bufStream.toByteArray();
|
|
|
|
|
|
|
|
|
|
// Total size of the Block Header: Take the size of the CRC32 field
|
|
|
|
|
// into account.
|
|
|
|
|
headerSize = buf.length + 4;
|
|
|
|
|
|
|
|
|
|
// This is just a sanity check.
|
|
|
|
|
if (headerSize > EncoderUtil.BLOCK_HEADER_SIZE_MAX)
|
|
|
|
|
throw new UnsupportedOptionsException();
|
|
|
|
|
|
|
|
|
|
// Block Header Size
|
2015-11-17 12:51:04 +08:00
|
|
|
buf[0] = (byte) (buf.length / 4);
|
2015-07-28 20:36:41 +08:00
|
|
|
|
|
|
|
|
// Write the Block Header field to the output stream.
|
|
|
|
|
out.write(buf);
|
|
|
|
|
EncoderUtil.writeCRC32(out, buf);
|
|
|
|
|
|
|
|
|
|
// Calculate the maximum allowed size of the Compressed Data field.
|
|
|
|
|
// It is hard to exceed it so this is mostly to be pedantic.
|
|
|
|
|
compressedSizeLimit = (EncoderUtil.VLI_MAX & ~3)
|
|
|
|
|
- headerSize - check.getSize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void write(int b) throws IOException {
|
2015-11-17 12:51:04 +08:00
|
|
|
tempBuf[0] = (byte) b;
|
2015-07-28 20:36:41 +08:00
|
|
|
write(tempBuf, 0, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void write(byte[] buf, int off, int len) throws IOException {
|
|
|
|
|
filterChain.write(buf, off, len);
|
|
|
|
|
check.update(buf, off, len);
|
|
|
|
|
uncompressedSize += len;
|
|
|
|
|
validate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void flush() throws IOException {
|
|
|
|
|
filterChain.flush();
|
|
|
|
|
validate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void finish() throws IOException {
|
|
|
|
|
// Finish the Compressed Data field.
|
|
|
|
|
filterChain.finish();
|
|
|
|
|
validate();
|
|
|
|
|
|
|
|
|
|
// Block Padding
|
|
|
|
|
for (long i = outCounted.getSize(); (i & 3) != 0; ++i)
|
|
|
|
|
out.write(0x00);
|
|
|
|
|
|
|
|
|
|
// Check
|
|
|
|
|
out.write(check.finish());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void validate() throws IOException {
|
|
|
|
|
long compressedSize = outCounted.getSize();
|
|
|
|
|
|
|
|
|
|
// It is very hard to trigger this exception.
|
|
|
|
|
// This is just to be pedantic.
|
|
|
|
|
if (compressedSize < 0 || compressedSize > compressedSizeLimit
|
2015-11-17 12:51:04 +08:00
|
|
|
|| uncompressedSize < 0)
|
2015-07-28 20:36:41 +08:00
|
|
|
throw new XZIOException("XZ Stream has grown too big");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long getUnpaddedSize() {
|
|
|
|
|
return headerSize + outCounted.getSize() + check.getSize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long getUncompressedSize() {
|
|
|
|
|
return uncompressedSize;
|
|
|
|
|
}
|
|
|
|
|
}
|