1/*
2 * XZOutputStream
3 *
4 * Author: Lasse Collin <lasse.collin@tukaani.org>
5 *
6 * This file has been put into the public domain.
7 * You can do whatever you want with this file.
8 */
9
10package org.tukaani.xz;
11
12import java.io.OutputStream;
13import java.io.IOException;
14import org.tukaani.xz.common.EncoderUtil;
15import org.tukaani.xz.common.StreamFlags;
16import org.tukaani.xz.check.Check;
17import org.tukaani.xz.index.IndexEncoder;
18
19/**
20 * Compresses into the .xz file format.
21 *
22 * <h4>Examples</h4>
23 * <p>
24 * Getting an output stream to compress with LZMA2 using the default
25 * settings and the default integrity check type (CRC64):
26 * <p><blockquote><pre>
27 * FileOutputStream outfile = new FileOutputStream("foo.xz");
28 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options());
29 * </pre></blockquote>
30 * <p>
31 * Using the preset level <code>8</code> for LZMA2 (the default
32 * is <code>6</code>) and SHA-256 instead of CRC64 for integrity checking:
33 * <p><blockquote><pre>
34 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options(8),
35 *                                           XZ.CHECK_SHA256);
36 * </pre></blockquote>
37 * <p>
38 * Using the x86 BCJ filter together with LZMA2 to compress x86 executables
39 * and printing the memory usage information before creating the
40 * XZOutputStream:
41 * <p><blockquote><pre>
42 * X86Options x86 = new X86Options();
43 * LZMA2Options lzma2 = new LZMA2Options();
44 * FilterOptions[] options = { x86, lzma2 };
45 * System.out.println("Encoder memory usage: "
46 *                    + FilterOptions.getEncoderMemoryUsage(options)
47 *                    + " KiB");
48 * System.out.println("Decoder memory usage: "
49 *                    + FilterOptions.getDecoderMemoryUsage(options)
50 *                    + " KiB");
51 * XZOutputStream outxz = new XZOutputStream(outfile, options);
52 * </pre></blockquote>
53 */
54public class XZOutputStream extends FinishableOutputStream {
55    private OutputStream out;
56    private final StreamFlags streamFlags = new StreamFlags();
57    private final Check check;
58    private final IndexEncoder index = new IndexEncoder();
59
60    private BlockOutputStream blockEncoder = null;
61    private FilterEncoder[] filters;
62
63    /**
64     * True if the current filter chain supports flushing.
65     * If it doesn't support flushing, <code>flush()</code>
66     * will use <code>endBlock()</code> as a fallback.
67     */
68    private boolean filtersSupportFlushing;
69
70    private IOException exception = null;
71    private boolean finished = false;
72
73    private final byte[] tempBuf = new byte[1];
74
75    /**
76     * Creates a new XZ compressor using one filter and CRC64 as
77     * the integrity check. This constructor is equivalent to passing
78     * a single-member FilterOptions array to
79     * <code>XZOutputStream(OutputStream, FilterOptions[])</code>.
80     *
81     * @param       out         output stream to which the compressed data
82     *                          will be written
83     *
84     * @param       filterOptions
85     *                          filter options to use
86     *
87     * @throws      UnsupportedOptionsException
88     *                          invalid filter chain
89     *
90     * @throws      IOException may be thrown from <code>out</code>
91     */
92    public XZOutputStream(OutputStream out, FilterOptions filterOptions)
93            throws IOException {
94        this(out, filterOptions, XZ.CHECK_CRC64);
95    }
96
97    /**
98     * Creates a new XZ compressor using one filter and the specified
99     * integrity check type. This constructor is equivalent to
100     * passing a single-member FilterOptions array to
101     * <code>XZOutputStream(OutputStream, FilterOptions[], int)</code>.
102     *
103     * @param       out         output stream to which the compressed data
104     *                          will be written
105     *
106     * @param       filterOptions
107     *                          filter options to use
108     *
109     * @param       checkType   type of the integrity check,
110     *                          for example XZ.CHECK_CRC32
111     *
112     * @throws      UnsupportedOptionsException
113     *                          invalid filter chain
114     *
115     * @throws      IOException may be thrown from <code>out</code>
116     */
117    public XZOutputStream(OutputStream out, FilterOptions filterOptions,
118                          int checkType) throws IOException {
119        this(out, new FilterOptions[] { filterOptions }, checkType);
120    }
121
122    /**
123     * Creates a new XZ compressor using 1-4 filters and CRC64 as
124     * the integrity check. This constructor is equivalent
125     * <code>XZOutputStream(out, filterOptions, XZ.CHECK_CRC64)</code>.
126     *
127     * @param       out         output stream to which the compressed data
128     *                          will be written
129     *
130     * @param       filterOptions
131     *                          array of filter options to use
132     *
133     * @throws      UnsupportedOptionsException
134     *                          invalid filter chain
135     *
136     * @throws      IOException may be thrown from <code>out</code>
137     */
138    public XZOutputStream(OutputStream out, FilterOptions[] filterOptions)
139            throws IOException {
140        this(out, filterOptions, XZ.CHECK_CRC64);
141    }
142
143    /**
144     * Creates a new XZ compressor using 1-4 filters and the specified
145     * integrity check type.
146     *
147     * @param       out         output stream to which the compressed data
148     *                          will be written
149     *
150     * @param       filterOptions
151     *                          array of filter options to use
152     *
153     * @param       checkType   type of the integrity check,
154     *                          for example XZ.CHECK_CRC32
155     *
156     * @throws      UnsupportedOptionsException
157     *                          invalid filter chain
158     *
159     * @throws      IOException may be thrown from <code>out</code>
160     */
161    public XZOutputStream(OutputStream out, FilterOptions[] filterOptions,
162                          int checkType) throws IOException {
163        this.out = out;
164        updateFilters(filterOptions);
165
166        streamFlags.checkType = checkType;
167        check = Check.getInstance(checkType);
168
169        encodeStreamHeader();
170    }
171
172    /**
173     * Updates the filter chain with a single filter.
174     * This is equivalent to passing a single-member FilterOptions array
175     * to <code>updateFilters(FilterOptions[])</code>.
176     *
177     * @param       filterOptions
178     *                          new filter to use
179     *
180     * @throws      UnsupportedOptionsException
181     *                          unsupported filter chain, or trying to change
182     *                          the filter chain in the middle of a Block
183     */
184    public void updateFilters(FilterOptions filterOptions)
185            throws XZIOException {
186        FilterOptions[] opts = new FilterOptions[1];
187        opts[0] = filterOptions;
188        updateFilters(opts);
189    }
190
191    /**
192     * Updates the filter chain with 1-4 filters.
193     * <p>
194     * Currently this cannot be used to update e.g. LZMA2 options in the
195     * middle of a XZ Block. Use <code>endBlock()</code> to finish the
196     * current XZ Block before calling this function. The new filter chain
197     * will then be used for the next XZ Block.
198     *
199     * @param       filterOptions
200     *                          new filter chain to use
201     *
202     * @throws      UnsupportedOptionsException
203     *                          unsupported filter chain, or trying to change
204     *                          the filter chain in the middle of a Block
205     */
206    public void updateFilters(FilterOptions[] filterOptions)
207            throws XZIOException {
208        if (blockEncoder != null)
209            throw new UnsupportedOptionsException("Changing filter options "
210                    + "in the middle of a XZ Block not implemented");
211
212        if (filterOptions.length < 1 || filterOptions.length > 4)
213            throw new UnsupportedOptionsException(
214                        "XZ filter chain must be 1-4 filters");
215
216        filtersSupportFlushing = true;
217        FilterEncoder[] newFilters = new FilterEncoder[filterOptions.length];
218        for (int i = 0; i < filterOptions.length; ++i) {
219            newFilters[i] = filterOptions[i].getFilterEncoder();
220            filtersSupportFlushing &= newFilters[i].supportsFlushing();
221        }
222
223        RawCoder.validate(newFilters);
224        filters = newFilters;
225    }
226
227    /**
228     * Writes one byte to be compressed.
229     *
230     * @throws      XZIOException
231     *                          XZ Stream has grown too big
232     *
233     * @throws      XZIOException
234     *                          <code>finish()</code> or <code>close()</code>
235     *                          was already called
236     *
237     * @throws      IOException may be thrown by the underlying output stream
238     */
239    public void write(int b) throws IOException {
240        tempBuf[0] = (byte)b;
241        write(tempBuf, 0, 1);
242    }
243
244    /**
245     * Writes an array of bytes to be compressed.
246     * The compressors tend to do internal buffering and thus the written
247     * data won't be readable from the compressed output immediately.
248     * Use <code>flush()</code> to force everything written so far to
249     * be written to the underlaying output stream, but be aware that
250     * flushing reduces compression ratio.
251     *
252     * @param       buf         buffer of bytes to be written
253     * @param       off         start offset in <code>buf</code>
254     * @param       len         number of bytes to write
255     *
256     * @throws      XZIOException
257     *                          XZ Stream has grown too big: total file size
258     *                          about 8&nbsp;EiB or the Index field exceeds
259     *                          16&nbsp;GiB; you shouldn't reach these sizes
260     *                          in practice
261     *
262     * @throws      XZIOException
263     *                          <code>finish()</code> or <code>close()</code>
264     *                          was already called and len &gt; 0
265     *
266     * @throws      IOException may be thrown by the underlying output stream
267     */
268    public void write(byte[] buf, int off, int len) throws IOException {
269        if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length)
270            throw new IndexOutOfBoundsException();
271
272        if (exception != null)
273            throw exception;
274
275        if (finished)
276            throw new XZIOException("Stream finished or closed");
277
278        try {
279            if (blockEncoder == null)
280                blockEncoder = new BlockOutputStream(out, filters, check);
281
282            blockEncoder.write(buf, off, len);
283        } catch (IOException e) {
284            exception = e;
285            throw e;
286        }
287    }
288
289    /**
290     * Finishes the current XZ Block (but not the whole XZ Stream).
291     * This doesn't flush the stream so it's possible that not all data will
292     * be decompressible from the output stream when this function returns.
293     * Call also <code>flush()</code> if flushing is wanted in addition to
294     * finishing the current XZ Block.
295     * <p>
296     * If there is no unfinished Block open, this function will do nothing.
297     * (No empty XZ Block will be created.)
298     * <p>
299     * This function can be useful, for example, to create
300     * random-accessible .xz files.
301     * <p>
302     * Starting a new XZ Block means that the encoder state is reset.
303     * Doing this very often will increase the size of the compressed
304     * file a lot (more than plain <code>flush()</code> would do).
305     *
306     * @throws      XZIOException
307     *                          XZ Stream has grown too big
308     *
309     * @throws      XZIOException
310     *                          stream finished or closed
311     *
312     * @throws      IOException may be thrown by the underlying output stream
313     */
314    public void endBlock() throws IOException {
315        if (exception != null)
316            throw exception;
317
318        if (finished)
319            throw new XZIOException("Stream finished or closed");
320
321        // NOTE: Once there is threading with multiple Blocks, it's possible
322        // that this function will be more like a barrier that returns
323        // before the last Block has been finished.
324        if (blockEncoder != null) {
325            try {
326                blockEncoder.finish();
327                index.add(blockEncoder.getUnpaddedSize(),
328                          blockEncoder.getUncompressedSize());
329                blockEncoder = null;
330            } catch (IOException e) {
331                exception = e;
332                throw e;
333            }
334        }
335    }
336
337    /**
338     * Flushes the encoder and calls <code>out.flush()</code>.
339     * All buffered pending data will then be decompressible from
340     * the output stream.
341     * <p>
342     * Calling this function very often may increase the compressed
343     * file size a lot. The filter chain options may affect the size
344     * increase too. For example, with LZMA2 the HC4 match finder has
345     * smaller penalty with flushing than BT4.
346     * <p>
347     * Some filters don't support flushing. If the filter chain has
348     * such a filter, <code>flush()</code> will call <code>endBlock()</code>
349     * before flushing.
350     *
351     * @throws      XZIOException
352     *                          XZ Stream has grown too big
353     *
354     * @throws      XZIOException
355     *                          stream finished or closed
356     *
357     * @throws      IOException may be thrown by the underlying output stream
358     */
359    public void flush() throws IOException {
360        if (exception != null)
361            throw exception;
362
363        if (finished)
364            throw new XZIOException("Stream finished or closed");
365
366        try {
367            if (blockEncoder != null) {
368                if (filtersSupportFlushing) {
369                    // This will eventually call out.flush() so
370                    // no need to do it here again.
371                    blockEncoder.flush();
372                } else {
373                    endBlock();
374                    out.flush();
375                }
376            } else {
377                out.flush();
378            }
379        } catch (IOException e) {
380            exception = e;
381            throw e;
382        }
383    }
384
385    /**
386     * Finishes compression without closing the underlying stream.
387     * No more data can be written to this stream after finishing
388     * (calling <code>write</code> with an empty buffer is OK).
389     * <p>
390     * Repeated calls to <code>finish()</code> do nothing unless
391     * an exception was thrown by this stream earlier. In that case
392     * the same exception is thrown again.
393     * <p>
394     * After finishing, the stream may be closed normally with
395     * <code>close()</code>. If the stream will be closed anyway, there
396     * usually is no need to call <code>finish()</code> separately.
397     *
398     * @throws      XZIOException
399     *                          XZ Stream has grown too big
400     *
401     * @throws      IOException may be thrown by the underlying output stream
402     */
403    public void finish() throws IOException {
404        if (!finished) {
405            // This checks for pending exceptions so we don't need to
406            // worry about it here.
407            endBlock();
408
409            try {
410                index.encode(out);
411                encodeStreamFooter();
412            } catch (IOException e) {
413                exception = e;
414                throw e;
415            }
416
417            // Set it to true only if everything goes fine. Setting it earlier
418            // would cause repeated calls to finish() do nothing instead of
419            // throwing an exception to indicate an earlier error.
420            finished = true;
421        }
422    }
423
424    /**
425     * Finishes compression and closes the underlying stream.
426     * The underlying stream <code>out</code> is closed even if finishing
427     * fails. If both finishing and closing fail, the exception thrown
428     * by <code>finish()</code> is thrown and the exception from the failed
429     * <code>out.close()</code> is lost.
430     *
431     * @throws      XZIOException
432     *                          XZ Stream has grown too big
433     *
434     * @throws      IOException may be thrown by the underlying output stream
435     */
436    public void close() throws IOException {
437        if (out != null) {
438            // If finish() throws an exception, it stores the exception to
439            // the variable "exception". So we can ignore the possible
440            // exception here.
441            try {
442                finish();
443            } catch (IOException e) {}
444
445            try {
446                out.close();
447            } catch (IOException e) {
448                // Remember the exception but only if there is no previous
449                // pending exception.
450                if (exception == null)
451                    exception = e;
452            }
453
454            out = null;
455        }
456
457        if (exception != null)
458            throw exception;
459    }
460
461    private void encodeStreamFlags(byte[] buf, int off) {
462        buf[off] = 0x00;
463        buf[off + 1] = (byte)streamFlags.checkType;
464    }
465
466    private void encodeStreamHeader() throws IOException {
467        out.write(XZ.HEADER_MAGIC);
468
469        byte[] buf = new byte[2];
470        encodeStreamFlags(buf, 0);
471        out.write(buf);
472
473        EncoderUtil.writeCRC32(out, buf);
474    }
475
476    private void encodeStreamFooter() throws IOException {
477        byte[] buf = new byte[6];
478        long backwardSize = index.getIndexSize() / 4 - 1;
479        for (int i = 0; i < 4; ++i)
480            buf[i] = (byte)(backwardSize >>> (i * 8));
481
482        encodeStreamFlags(buf, 4);
483
484        EncoderUtil.writeCRC32(out, buf);
485        out.write(buf);
486        out.write(XZ.FOOTER_MAGIC);
487    }
488}
489