aboutsummaryrefslogtreecommitdiff
path: root/src/org/tukaani/xz/XZOutputStream.java
blob: 63cf5cf1dc8feb7cc7af7f5c47663d80c4605714 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
/*
 * XZOutputStream
 *
 * Author: Lasse Collin <lasse.collin@tukaani.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.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.
 *
 * <h2>Examples</h2>
 * <p>
 * Getting an output stream to compress with LZMA2 using the default
 * settings and the default integrity check type (CRC64):
 * <blockquote><pre>
 * FileOutputStream outfile = new FileOutputStream("foo.xz");
 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options());
 * </pre></blockquote>
 * <p>
 * Using the preset level <code>8</code> for LZMA2 (the default
 * is <code>6</code>) and SHA-256 instead of CRC64 for integrity checking:
 * <blockquote><pre>
 * XZOutputStream outxz = new XZOutputStream(outfile, new LZMA2Options(8),
 *                                           XZ.CHECK_SHA256);
 * </pre></blockquote>
 * <p>
 * Using the x86 BCJ filter together with LZMA2 to compress x86 executables
 * and printing the memory usage information before creating the
 * XZOutputStream:
 * <blockquote><pre>
 * 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);
 * </pre></blockquote>
 */
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, <code>flush()</code>
     * will use <code>endBlock()</code> 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
     * <code>XZOutputStream(OutputStream, FilterOptions[])</code>.
     *
     * @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 <code>out</code>
     */
    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
     * <code>XZOutputStream(OutputStream, FilterOptions[], ArrayCache)</code>.
     *
     * @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 <code>out</code>
     *
     * @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
     * <code>XZOutputStream(OutputStream, FilterOptions[], int)</code>.
     *
     * @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 <code>out</code>
     */
    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
     * <code>XZOutputStream(OutputStream, FilterOptions[], int,
     * ArrayCache)</code>.
     *
     * @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 <code>out</code>
     *
     * @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
     * <code>XZOutputStream(out, filterOptions, XZ.CHECK_CRC64)</code>.
     *
     * @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 <code>out</code>
     */
    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
     * <code>XZOutputStream(out, filterOptions, XZ.CHECK_CRC64,
     * arrayCache)</code>.
     *
     * @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 <code>out</code>
     *
     * @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 <code>out</code>
     */
    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 <code>out</code>
     *
     * @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 <code>updateFilters(FilterOptions[])</code>.
     *
     * @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.
     * <p>
     * Currently this cannot be used to update e.g. LZMA2 options in the
     * middle of a XZ Block. Use <code>endBlock()</code> 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
     *                          <code>finish()</code> or <code>close()</code>
     *                          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 <code>flush()</code> 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 <code>buf</code>
     * @param       len         number of bytes to write
     *
     * @throws      XZIOException
     *                          XZ Stream has grown too big: total file size
     *                          about 8&nbsp;EiB or the Index field exceeds
     *                          16&nbsp;GiB; you shouldn't reach these sizes
     *                          in practice
     *
     * @throws      XZIOException
     *                          <code>finish()</code> or <code>close()</code>
     *                          was already called and len &gt; 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 <code>flush()</code> if flushing is wanted in addition to
     * finishing the current XZ Block.
     * <p>
     * If there is no unfinished Block open, this function will do nothing.
     * (No empty XZ Block will be created.)
     * <p>
     * This function can be useful, for example, to create
     * random-accessible .xz files.
     * <p>
     * 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 <code>flush()</code> 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 <code>out.flush()</code>.
     * All buffered pending data will then be decompressible from
     * the output stream.
     * <p>
     * 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.
     * <p>
     * Some filters don't support flushing. If the filter chain has
     * such a filter, <code>flush()</code> will call <code>endBlock()</code>
     * 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 <code>write</code> with an empty buffer is OK).
     * <p>
     * Repeated calls to <code>finish()</code> do nothing unless
     * an exception was thrown by this stream earlier. In that case
     * the same exception is thrown again.
     * <p>
     * After finishing, the stream may be closed normally with
     * <code>close()</code>. If the stream will be closed anyway, there
     * usually is no need to call <code>finish()</code> 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 <code>out</code> is closed even if finishing
     * fails. If both finishing and closing fail, the exception thrown
     * by <code>finish()</code> is thrown and the exception from the failed
     * <code>out.close()</code> 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);
    }
}