| /* |
| * LZMAOutputStream |
| * |
| * Authors: Lasse Collin <lasse.collin@tukaani.org> |
| * Igor Pavlov <http://7-zip.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.IOException; |
| import org.tukaani.xz.lz.LZEncoder; |
| import org.tukaani.xz.rangecoder.RangeEncoderToStream; |
| import org.tukaani.xz.lzma.LZMAEncoder; |
| |
| /** |
| * Compresses into the legacy .lzma file format or into a raw LZMA stream. |
| * |
| * @since 1.6 |
| */ |
| public class LZMAOutputStream extends FinishableOutputStream { |
| private OutputStream out; |
| |
| private final ArrayCache arrayCache; |
| |
| private LZEncoder lz; |
| private final RangeEncoderToStream rc; |
| private LZMAEncoder lzma; |
| |
| private final int props; |
| private final boolean useEndMarker; |
| private final long expectedUncompressedSize; |
| private long currentUncompressedSize = 0; |
| |
| private boolean finished = false; |
| private IOException exception = null; |
| |
| private final byte[] tempBuf = new byte[1]; |
| |
| private LZMAOutputStream(OutputStream out, LZMA2Options options, |
| boolean useHeader, boolean useEndMarker, |
| long expectedUncompressedSize, |
| ArrayCache arrayCache) |
| throws IOException { |
| if (out == null) |
| throw new NullPointerException(); |
| |
| // -1 indicates unknown and >= 0 are for known sizes. |
| if (expectedUncompressedSize < -1) |
| throw new IllegalArgumentException( |
| "Invalid expected input size (less than -1)"); |
| |
| this.useEndMarker = useEndMarker; |
| this.expectedUncompressedSize = expectedUncompressedSize; |
| |
| this.arrayCache = arrayCache; |
| |
| this.out = out; |
| rc = new RangeEncoderToStream(out); |
| |
| int dictSize = options.getDictSize(); |
| lzma = LZMAEncoder.getInstance(rc, |
| options.getLc(), options.getLp(), options.getPb(), |
| options.getMode(), |
| dictSize, 0, options.getNiceLen(), |
| options.getMatchFinder(), options.getDepthLimit(), |
| arrayCache); |
| |
| lz = lzma.getLZEncoder(); |
| |
| byte[] presetDict = options.getPresetDict(); |
| if (presetDict != null && presetDict.length > 0) { |
| if (useHeader) |
| throw new UnsupportedOptionsException( |
| "Preset dictionary cannot be used in .lzma files " |
| + "(try a raw LZMA stream instead)"); |
| |
| lz.setPresetDict(dictSize, presetDict); |
| } |
| |
| props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc(); |
| |
| if (useHeader) { |
| // Props byte stores lc, lp, and pb. |
| out.write(props); |
| |
| // Dictionary size is stored as a 32-bit unsigned little endian |
| // integer. |
| for (int i = 0; i < 4; ++i) { |
| out.write(dictSize & 0xFF); |
| dictSize >>>= 8; |
| } |
| |
| // Uncompressed size is stored as a 64-bit unsigned little endian |
| // integer. The max value (-1 in two's complement) indicates |
| // unknown size. |
| for (int i = 0; i < 8; ++i) |
| out.write((int)(expectedUncompressedSize >>> (8 * i)) & 0xFF); |
| } |
| } |
| |
| /** |
| * Creates a new compressor for the legacy .lzma file format. |
| * <p> |
| * If the uncompressed size of the input data is known, it will be stored |
| * in the .lzma header and no end of stream marker will be used. Otherwise |
| * the header will indicate unknown uncompressed size and the end of stream |
| * marker will be used. |
| * <p> |
| * Note that a preset dictionary cannot be used in .lzma files but |
| * it can be used for raw LZMA streams. |
| * |
| * @param out output stream to which the compressed data |
| * will be written |
| * |
| * @param options LZMA compression options; the same class |
| * is used here as is for LZMA2 |
| * |
| * @param inputSize uncompressed size of the data to be compressed; |
| * use <code>-1</code> when unknown |
| * |
| * @throws IOException may be thrown from <code>out</code> |
| */ |
| public LZMAOutputStream(OutputStream out, LZMA2Options options, |
| long inputSize) |
| throws IOException { |
| this(out, options, inputSize, ArrayCache.getDefaultCache()); |
| } |
| |
| /** |
| * Creates a new compressor for the legacy .lzma file format. |
| * <p> |
| * This is identical to |
| * <code>LZMAOutputStream(OutputStream, LZMA2Options, long)</code> |
| * except that this also takes the <code>arrayCache</code> argument. |
| * |
| * @param out output stream to which the compressed data |
| * will be written |
| * |
| * @param options LZMA compression options; the same class |
| * is used here as is for LZMA2 |
| * |
| * @param inputSize uncompressed size of the data to be compressed; |
| * use <code>-1</code> when unknown |
| * |
| * @param arrayCache cache to be used for allocating large arrays |
| * |
| * @throws IOException may be thrown from <code>out</code> |
| * |
| * @since 1.7 |
| */ |
| public LZMAOutputStream(OutputStream out, LZMA2Options options, |
| long inputSize, ArrayCache arrayCache) |
| throws IOException { |
| this(out, options, true, inputSize == -1, inputSize, arrayCache); |
| } |
| |
| /** |
| * Creates a new compressor for raw LZMA (also known as LZMA1) stream. |
| * <p> |
| * Raw LZMA streams can be encoded with or without end of stream marker. |
| * When decompressing the stream, one must know if the end marker was used |
| * and tell it to the decompressor. If the end marker wasn't used, the |
| * decompressor will also need to know the uncompressed size. |
| * |
| * @param out output stream to which the compressed data |
| * will be written |
| * |
| * @param options LZMA compression options; the same class |
| * is used here as is for LZMA2 |
| * |
| * @param useEndMarker |
| * if end of stream marker should be written |
| * |
| * @throws IOException may be thrown from <code>out</code> |
| */ |
| public LZMAOutputStream(OutputStream out, LZMA2Options options, |
| boolean useEndMarker) throws IOException { |
| this(out, options, useEndMarker, ArrayCache.getDefaultCache()); |
| } |
| |
| /** |
| * Creates a new compressor for raw LZMA (also known as LZMA1) stream. |
| * <p> |
| * This is identical to |
| * <code>LZMAOutputStream(OutputStream, LZMA2Options, boolean)</code> |
| * except that this also takes the <code>arrayCache</code> argument. |
| * |
| * @param out output stream to which the compressed data |
| * will be written |
| * |
| * @param options LZMA compression options; the same class |
| * is used here as is for LZMA2 |
| * |
| * @param useEndMarker |
| * if end of stream marker should be written |
| * |
| * @param arrayCache cache to be used for allocating large arrays |
| * |
| * @throws IOException may be thrown from <code>out</code> |
| * |
| * @since 1.7 |
| */ |
| public LZMAOutputStream(OutputStream out, LZMA2Options options, |
| boolean useEndMarker, ArrayCache arrayCache) |
| throws IOException { |
| this(out, options, false, useEndMarker, -1, arrayCache); |
| } |
| |
| /** |
| * Returns the LZMA lc/lp/pb properties encoded into a single byte. |
| * This might be useful when handling file formats other than .lzma |
| * that use the same encoding for the LZMA properties as .lzma does. |
| */ |
| public int getProps() { |
| return props; |
| } |
| |
| /** |
| * Gets the amount of uncompressed data written to the stream. |
| * This is useful when creating raw LZMA streams without |
| * the end of stream marker. |
| */ |
| public long getUncompressedSize() { |
| return currentUncompressedSize; |
| } |
| |
| public void write(int b) throws IOException { |
| tempBuf[0] = (byte)b; |
| write(tempBuf, 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"); |
| |
| if (expectedUncompressedSize != -1 |
| && expectedUncompressedSize - currentUncompressedSize < len) |
| throw new XZIOException("Expected uncompressed input size (" |
| + expectedUncompressedSize + " bytes) was exceeded"); |
| |
| currentUncompressedSize += len; |
| |
| try { |
| while (len > 0) { |
| int used = lz.fillWindow(buf, off, len); |
| off += used; |
| len -= used; |
| lzma.encodeForLZMA1(); |
| } |
| } catch (IOException e) { |
| exception = e; |
| throw e; |
| } |
| } |
| |
| /** |
| * Flushing isn't supported and will throw XZIOException. |
| */ |
| public void flush() throws IOException { |
| throw new XZIOException("LZMAOutputStream does not support flushing"); |
| } |
| |
| /** |
| * Finishes the stream without closing the underlying OutputStream. |
| */ |
| public void finish() throws IOException { |
| if (!finished) { |
| if (exception != null) |
| throw exception; |
| |
| try { |
| if (expectedUncompressedSize != -1 |
| && expectedUncompressedSize != currentUncompressedSize) |
| throw new XZIOException("Expected uncompressed size (" |
| + expectedUncompressedSize + ") doesn't equal " |
| + "the number of bytes written to the stream (" |
| + currentUncompressedSize + ")"); |
| |
| lz.setFinishing(); |
| lzma.encodeForLZMA1(); |
| |
| if (useEndMarker) |
| lzma.encodeLZMA1EndMarker(); |
| |
| rc.finish(); |
| } catch (IOException e) { |
| exception = e; |
| throw e; |
| } |
| |
| finished = true; |
| |
| lzma.putArraysToCache(arrayCache); |
| lzma = null; |
| lz = null; |
| } |
| } |
| |
| /** |
| * Finishes the stream and closes the underlying OutputStream. |
| */ |
| public void close() throws IOException { |
| if (out != null) { |
| try { |
| finish(); |
| } catch (IOException e) {} |
| |
| try { |
| out.close(); |
| } catch (IOException e) { |
| if (exception == null) |
| exception = e; |
| } |
| |
| out = null; |
| } |
| |
| if (exception != null) |
| throw exception; |
| } |
| } |