org.xbib.io.compress.xz.LZMA2OutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of io-compress-xz Show documentation
Show all versions of io-compress-xz Show documentation
Archive algorithms for Java
package org.xbib.io.compress.xz;
import org.xbib.io.compress.xz.lz.LZEncoder;
import org.xbib.io.compress.xz.lzma.LZMAEncoder;
import org.xbib.io.compress.xz.rangecoder.RangeEncoder;
import java.io.DataOutputStream;
import java.io.IOException;
class LZMA2OutputStream extends FinishableOutputStream {
static final int COMPRESSED_SIZE_MAX = 64 << 10;
private FinishableOutputStream out;
private final DataOutputStream outData;
private final LZEncoder lz;
private final RangeEncoder rc;
private final LZMAEncoder lzma;
private final int props; // Cannot change props on the fly for now.
private boolean dictResetNeeded = true;
private boolean stateResetNeeded = true;
private boolean propsNeeded = true;
private int pendingSize = 0;
private boolean finished = false;
private IOException exception = null;
private static int getExtraSizeBefore(int dictSize) {
return COMPRESSED_SIZE_MAX > dictSize
? COMPRESSED_SIZE_MAX - dictSize : 0;
}
static int getMemoryUsage(LZMA2Options options) {
// 64 KiB buffer for the range encoder + a little extra + LZMAEncoder
int dictSize = options.getDictSize();
int extraSizeBefore = getExtraSizeBefore(dictSize);
return 70 + LZMAEncoder.getMemoryUsage(options.getMode(),
dictSize, extraSizeBefore,
options.getMatchFinder());
}
LZMA2OutputStream(FinishableOutputStream out, LZMA2Options options) {
if (out == null) {
throw new NullPointerException();
}
this.out = out;
outData = new DataOutputStream(out);
rc = new RangeEncoder(COMPRESSED_SIZE_MAX);
int dictSize = options.getDictSize();
int extraSizeBefore = getExtraSizeBefore(dictSize);
lzma = LZMAEncoder.getInstance(rc,
options.getLc(), options.getLp(), options.getPb(),
options.getMode(),
dictSize, extraSizeBefore, options.getNiceLen(),
options.getMatchFinder(), options.getDepthLimit());
lz = lzma.getLZEncoder();
byte[] presetDict = options.getPresetDict();
if (presetDict != null && presetDict.length > 0) {
lz.setPresetDict(dictSize, presetDict);
dictResetNeeded = false;
}
props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc();
}
public void write(int b) throws IOException {
byte[] buf = new byte[1];
buf[0] = (byte) b;
write(buf, 0, 1);
}
public void write(byte[] buf, int off, int len) throws IOException {
if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length) {
throw new IndexOutOfBoundsException();
}
if (exception != null) {
throw exception;
}
if (finished) {
throw new XZIOException("Stream finished or closed");
}
try {
while (len > 0) {
int used = lz.fillWindow(buf, off, len);
off += used;
len -= used;
pendingSize += used;
if (lzma.encodeForLZMA2()) {
writeChunk();
}
}
} catch (IOException e) {
exception = e;
throw e;
}
}
private void writeChunk() throws IOException {
int compressedSize = rc.finish();
int uncompressedSize = lzma.getUncompressedSize();
assert compressedSize > 0 : compressedSize;
assert uncompressedSize > 0 : uncompressedSize;
// +2 because the header of a compressed chunk is 2 bytes
// bigger than the header of an uncompressed chunk.
if (compressedSize + 2 < uncompressedSize) {
writeLZMA(uncompressedSize, compressedSize);
} else {
lzma.reset();
uncompressedSize = lzma.getUncompressedSize();
assert uncompressedSize > 0 : uncompressedSize;
writeUncompressed(uncompressedSize);
}
pendingSize -= uncompressedSize;
lzma.resetUncompressedSize();
rc.reset();
}
private void writeLZMA(int uncompressedSize, int compressedSize)
throws IOException {
int control;
if (propsNeeded) {
if (dictResetNeeded) {
control = 0x80 + (3 << 5);
} else {
control = 0x80 + (2 << 5);
}
} else {
if (stateResetNeeded) {
control = 0x80 + (1 << 5);
} else {
control = 0x80;
}
}
control |= (uncompressedSize - 1) >>> 16;
outData.writeByte(control);
outData.writeShort(uncompressedSize - 1);
outData.writeShort(compressedSize - 1);
if (propsNeeded) {
outData.writeByte(props);
}
rc.write(out);
propsNeeded = false;
stateResetNeeded = false;
dictResetNeeded = false;
}
private void writeUncompressed(int uncompressedSize) throws IOException {
while (uncompressedSize > 0) {
int chunkSize = Math.min(uncompressedSize, COMPRESSED_SIZE_MAX);
outData.writeByte(dictResetNeeded ? 0x01 : 0x02);
outData.writeShort(chunkSize - 1);
lz.copyUncompressed(out, uncompressedSize, chunkSize);
uncompressedSize -= chunkSize;
dictResetNeeded = false;
}
stateResetNeeded = true;
}
private void writeEndMarker() throws IOException {
assert !finished;
if (exception != null) {
throw exception;
}
lz.setFinishing();
try {
while (pendingSize > 0) {
lzma.encodeForLZMA2();
writeChunk();
}
out.write(0x00);
} catch (IOException e) {
exception = e;
throw e;
}
finished = true;
}
public void flush() throws IOException {
if (exception != null) {
throw exception;
}
if (finished) {
throw new XZIOException("Stream finished or closed");
}
try {
lz.setFlushing();
while (pendingSize > 0) {
lzma.encodeForLZMA2();
writeChunk();
}
out.flush();
} catch (IOException e) {
exception = e;
throw e;
}
}
public void finish() throws IOException {
if (!finished) {
writeEndMarker();
try {
out.finish();
} catch (IOException e) {
exception = e;
throw e;
}
finished = true;
}
}
public void close() throws IOException {
if (out != null) {
if (!finished) {
try {
writeEndMarker();
} catch (IOException e) {
}
}
try {
out.close();
} catch (IOException e) {
if (exception == null) {
exception = e;
}
}
out = null;
}
if (exception != null) {
throw exception;
}
}
}