/* * XZOutputStream * * Author: Lasse Collin * * 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.common.EncoderUtil; import org.tukaani.xz.common.StreamFlags; import org.tukaani.xz.check.Check; import org.tukaani.xz.index.IndexEncoder; /** * Compresses into the .xz file format. * *

Examples

*

* Getting an output stream to compress with LZMA2 using the default * settings and the default integrity check type (CRC64): *

 * FileOutputStream outfile = new FileOutputStream("foo.xz");
 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options());
 * 
*

* Using the preset level 8 for LZMA2 (the default * is 6) and SHA-256 instead of CRC64 for integrity checking: *

 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options(8),
 *                                           XZ.CHECK_SHA256);
 * 
*

* Using the x86 BCJ filter together with LZMA2 to compress x86 executables * and printing the memory usage information before creating the * XZOutputStream: *

 * X86Options x86 = new X86Options();
 * LZMA2Options lzma2 = new LZMA2Options();
 * FilterOptions[] options = { x86, lzma2 };
 * System.out.println("Encoder memory usage: "
 *                    + FilterOptions.getEncoderMemoryUsage(options)
 *                    + " KiB");
 * System.out.println("Decoder memory usage: "
 *                    + FilterOptions.getDecoderMemoryUsage(options)
 *                    + " KiB");
 * XZOutputStream outxz = new XZOutputStream(outfile, options);
 * 
*/ public class XZOutputStream extends FinishableOutputStream { private final ArrayCache arrayCache; private OutputStream out; private final StreamFlags streamFlags = new StreamFlags(); private final Check check; private final IndexEncoder index = new IndexEncoder(); private BlockOutputStream blockEncoder = null; private FilterEncoder[] filters; /** * True if the current filter chain supports flushing. * If it doesn't support flushing, flush() * will use endBlock() as a fallback. */ private boolean filtersSupportFlushing; private IOException exception = null; private boolean finished = false; private final byte[] tempBuf = new byte[1]; /** * Creates a new XZ compressor using one filter and CRC64 as * the integrity check. This constructor is equivalent to passing * a single-member FilterOptions array to * XZOutputStream(OutputStream, FilterOptions[]). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * filter options to use * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out */ public XZOutputStream(OutputStream out, FilterOptions filterOptions) throws IOException { this(out, filterOptions, XZ.CHECK_CRC64); } /** * Creates a new XZ compressor using one filter and CRC64 as * the integrity check. This constructor is equivalent to passing * a single-member FilterOptions array to * XZOutputStream(OutputStream, FilterOptions[], ArrayCache). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * filter options to use * * @param arrayCache cache to be used for allocating large arrays * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out * * @since 1.7 */ public XZOutputStream(OutputStream out, FilterOptions filterOptions, ArrayCache arrayCache) throws IOException { this(out, filterOptions, XZ.CHECK_CRC64, arrayCache); } /** * Creates a new XZ compressor using one filter and the specified * integrity check type. This constructor is equivalent to * passing a single-member FilterOptions array to * XZOutputStream(OutputStream, FilterOptions[], int). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * filter options to use * * @param checkType type of the integrity check, * for example XZ.CHECK_CRC32 * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out */ public XZOutputStream(OutputStream out, FilterOptions filterOptions, int checkType) throws IOException { this(out, new FilterOptions[] { filterOptions }, checkType); } /** * Creates a new XZ compressor using one filter and the specified * integrity check type. This constructor is equivalent to * passing a single-member FilterOptions array to * XZOutputStream(OutputStream, FilterOptions[], int, * ArrayCache). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * filter options to use * * @param checkType type of the integrity check, * for example XZ.CHECK_CRC32 * * @param arrayCache cache to be used for allocating large arrays * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out * * @since 1.7 */ public XZOutputStream(OutputStream out, FilterOptions filterOptions, int checkType, ArrayCache arrayCache) throws IOException { this(out, new FilterOptions[] { filterOptions }, checkType, arrayCache); } /** * Creates a new XZ compressor using 1-4 filters and CRC64 as * the integrity check. This constructor is equivalent * XZOutputStream(out, filterOptions, XZ.CHECK_CRC64). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * array of filter options to use * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out */ public XZOutputStream(OutputStream out, FilterOptions[] filterOptions) throws IOException { this(out, filterOptions, XZ.CHECK_CRC64); } /** * Creates a new XZ compressor using 1-4 filters and CRC64 as * the integrity check. This constructor is equivalent * XZOutputStream(out, filterOptions, XZ.CHECK_CRC64, * arrayCache). * * @param out output stream to which the compressed data * will be written * * @param filterOptions * array of filter options to use * * @param arrayCache cache to be used for allocating large arrays * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out * * @since 1.7 */ public XZOutputStream(OutputStream out, FilterOptions[] filterOptions, ArrayCache arrayCache) throws IOException { this(out, filterOptions, XZ.CHECK_CRC64, arrayCache); } /** * Creates a new XZ compressor using 1-4 filters and the specified * integrity check type. * * @param out output stream to which the compressed data * will be written * * @param filterOptions * array of filter options to use * * @param checkType type of the integrity check, * for example XZ.CHECK_CRC32 * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out */ public XZOutputStream(OutputStream out, FilterOptions[] filterOptions, int checkType) throws IOException { this(out, filterOptions, checkType, ArrayCache.getDefaultCache()); } /** * Creates a new XZ compressor using 1-4 filters and the specified * integrity check type. * * @param out output stream to which the compressed data * will be written * * @param filterOptions * array of filter options to use * * @param checkType type of the integrity check, * for example XZ.CHECK_CRC32 * * @param arrayCache cache to be used for allocating large arrays * * @throws UnsupportedOptionsException * invalid filter chain * * @throws IOException may be thrown from out * * @since 1.7 */ public XZOutputStream(OutputStream out, FilterOptions[] filterOptions, int checkType, ArrayCache arrayCache) throws IOException { this.arrayCache = arrayCache; this.out = out; updateFilters(filterOptions); streamFlags.checkType = checkType; check = Check.getInstance(checkType); encodeStreamHeader(); } /** * Updates the filter chain with a single filter. * This is equivalent to passing a single-member FilterOptions array * to updateFilters(FilterOptions[]). * * @param filterOptions * new filter to use * * @throws UnsupportedOptionsException * unsupported filter chain, or trying to change * the filter chain in the middle of a Block */ public void updateFilters(FilterOptions filterOptions) throws XZIOException { FilterOptions[] opts = new FilterOptions[1]; opts[0] = filterOptions; updateFilters(opts); } /** * Updates the filter chain with 1-4 filters. *

* Currently this cannot be used to update e.g. LZMA2 options in the * middle of a XZ Block. Use endBlock() to finish the * current XZ Block before calling this function. The new filter chain * will then be used for the next XZ Block. * * @param filterOptions * new filter chain to use * * @throws UnsupportedOptionsException * unsupported filter chain, or trying to change * the filter chain in the middle of a Block */ public void updateFilters(FilterOptions[] filterOptions) throws XZIOException { if (blockEncoder != null) throw new UnsupportedOptionsException("Changing filter options " + "in the middle of a XZ Block not implemented"); if (filterOptions.length < 1 || filterOptions.length > 4) throw new UnsupportedOptionsException( "XZ filter chain must be 1-4 filters"); filtersSupportFlushing = true; FilterEncoder[] newFilters = new FilterEncoder[filterOptions.length]; for (int i = 0; i < filterOptions.length; ++i) { newFilters[i] = filterOptions[i].getFilterEncoder(); filtersSupportFlushing &= newFilters[i].supportsFlushing(); } RawCoder.validate(newFilters); filters = newFilters; } /** * Writes one byte to be compressed. * * @throws XZIOException * XZ Stream has grown too big * * @throws XZIOException * finish() or close() * was already called * * @throws IOException may be thrown by the underlying output stream */ public void write(int b) throws IOException { tempBuf[0] = (byte)b; write(tempBuf, 0, 1); } /** * Writes an array of bytes to be compressed. * The compressors tend to do internal buffering and thus the written * data won't be readable from the compressed output immediately. * Use flush() to force everything written so far to * be written to the underlaying output stream, but be aware that * flushing reduces compression ratio. * * @param buf buffer of bytes to be written * @param off start offset in buf * @param len number of bytes to write * * @throws XZIOException * XZ Stream has grown too big: total file size * about 8 EiB or the Index field exceeds * 16 GiB; you shouldn't reach these sizes * in practice * * @throws XZIOException * finish() or close() * was already called and len > 0 * * @throws IOException may be thrown by the underlying output stream */ 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 { if (blockEncoder == null) blockEncoder = new BlockOutputStream(out, filters, check, arrayCache); blockEncoder.write(buf, off, len); } catch (IOException e) { exception = e; throw e; } } /** * Finishes the current XZ Block (but not the whole XZ Stream). * This doesn't flush the stream so it's possible that not all data will * be decompressible from the output stream when this function returns. * Call also flush() if flushing is wanted in addition to * finishing the current XZ Block. *

* If there is no unfinished Block open, this function will do nothing. * (No empty XZ Block will be created.) *

* This function can be useful, for example, to create * random-accessible .xz files. *

* Starting a new XZ Block means that the encoder state is reset. * Doing this very often will increase the size of the compressed * file a lot (more than plain flush() would do). * * @throws XZIOException * XZ Stream has grown too big * * @throws XZIOException * stream finished or closed * * @throws IOException may be thrown by the underlying output stream */ public void endBlock() throws IOException { if (exception != null) throw exception; if (finished) throw new XZIOException("Stream finished or closed"); // NOTE: Once there is threading with multiple Blocks, it's possible // that this function will be more like a barrier that returns // before the last Block has been finished. if (blockEncoder != null) { try { blockEncoder.finish(); index.add(blockEncoder.getUnpaddedSize(), blockEncoder.getUncompressedSize()); blockEncoder = null; } catch (IOException e) { exception = e; throw e; } } } /** * Flushes the encoder and calls out.flush(). * All buffered pending data will then be decompressible from * the output stream. *

* Calling this function very often may increase the compressed * file size a lot. The filter chain options may affect the size * increase too. For example, with LZMA2 the HC4 match finder has * smaller penalty with flushing than BT4. *

* Some filters don't support flushing. If the filter chain has * such a filter, flush() will call endBlock() * before flushing. * * @throws XZIOException * XZ Stream has grown too big * * @throws XZIOException * stream finished or closed * * @throws IOException may be thrown by the underlying output stream */ public void flush() throws IOException { if (exception != null) throw exception; if (finished) throw new XZIOException("Stream finished or closed"); try { if (blockEncoder != null) { if (filtersSupportFlushing) { // This will eventually call out.flush() so // no need to do it here again. blockEncoder.flush(); } else { endBlock(); out.flush(); } } else { out.flush(); } } catch (IOException e) { exception = e; throw e; } } /** * Finishes compression without closing the underlying stream. * No more data can be written to this stream after finishing * (calling write with an empty buffer is OK). *

* Repeated calls to finish() do nothing unless * an exception was thrown by this stream earlier. In that case * the same exception is thrown again. *

* After finishing, the stream may be closed normally with * close(). If the stream will be closed anyway, there * usually is no need to call finish() separately. * * @throws XZIOException * XZ Stream has grown too big * * @throws IOException may be thrown by the underlying output stream */ public void finish() throws IOException { if (!finished) { // This checks for pending exceptions so we don't need to // worry about it here. endBlock(); try { index.encode(out); encodeStreamFooter(); } catch (IOException e) { exception = e; throw e; } // Set it to true only if everything goes fine. Setting it earlier // would cause repeated calls to finish() do nothing instead of // throwing an exception to indicate an earlier error. finished = true; } } /** * Finishes compression and closes the underlying stream. * The underlying stream out is closed even if finishing * fails. If both finishing and closing fail, the exception thrown * by finish() is thrown and the exception from the failed * out.close() is lost. * * @throws XZIOException * XZ Stream has grown too big * * @throws IOException may be thrown by the underlying output stream */ public void close() throws IOException { if (out != null) { // If finish() throws an exception, it stores the exception to // the variable "exception". So we can ignore the possible // exception here. try { finish(); } catch (IOException e) {} try { out.close(); } catch (IOException e) { // Remember the exception but only if there is no previous // pending exception. if (exception == null) exception = e; } out = null; } if (exception != null) throw exception; } private void encodeStreamFlags(byte[] buf, int off) { buf[off] = 0x00; buf[off + 1] = (byte)streamFlags.checkType; } private void encodeStreamHeader() throws IOException { out.write(XZ.HEADER_MAGIC); byte[] buf = new byte[2]; encodeStreamFlags(buf, 0); out.write(buf); EncoderUtil.writeCRC32(out, buf); } private void encodeStreamFooter() throws IOException { byte[] buf = new byte[6]; long backwardSize = index.getIndexSize() / 4 - 1; for (int i = 0; i < 4; ++i) buf[i] = (byte)(backwardSize >>> (i * 8)); encodeStreamFlags(buf, 4); EncoderUtil.writeCRC32(out, buf); out.write(buf); out.write(XZ.FOOTER_MAGIC); } }