All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xbib.io.compress.xz.XZOutputStream Maven / Gradle / Ivy

package org.xbib.io.compress.xz;

import org.xbib.io.compress.xz.check.Check;
import org.xbib.io.compress.xz.common.EncoderUtil;
import org.xbib.io.compress.xz.common.StreamFlags;
import org.xbib.io.compress.xz.index.IndexEncoder;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 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 };
 * String msg = "Encoder memory usage: "
 *                    + FilterOptions.getEncoderMemoryUsage(options)
 *                    + " KiB";
 * trign msg2 = "Decoder memory usage: "
 *                    + FilterOptions.getDecoderMemoryUsage(options)
 *                    + " KiB";
 * XZOutputStream outxz = new XZOutputStream(outfile, options);
 * 
*/ public class XZOutputStream extends FinishableOutputStream { 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; /** * 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 java.io.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 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 java.io.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 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 java.io.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 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 java.io.IOException may be thrown from out */ public XZOutputStream(OutputStream out, FilterOptions[] filterOptions, int checkType) throws IOException { 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 java.io.IOException may be thrown by the underlying output stream */ public void write(int b) throws IOException { byte[] buf = new byte[]{(byte) b}; write(buf, 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 java.io.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); } 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 java.io.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 java.io.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 java.io.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 java.io.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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy