1 | /*
|
---|
2 | * BlockOutputStream
|
---|
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 |
|
---|
10 | package org.tukaani.xz;
|
---|
11 |
|
---|
12 | import java.io.OutputStream;
|
---|
13 | import java.io.ByteArrayOutputStream;
|
---|
14 | import java.io.IOException;
|
---|
15 | import org.tukaani.xz.common.EncoderUtil;
|
---|
16 | import org.tukaani.xz.check.Check;
|
---|
17 |
|
---|
18 | class BlockOutputStream extends FinishableOutputStream {
|
---|
19 | private final OutputStream out;
|
---|
20 | private final CountingOutputStream outCounted;
|
---|
21 | private FinishableOutputStream filterChain;
|
---|
22 | private final Check check;
|
---|
23 |
|
---|
24 | private final int headerSize;
|
---|
25 | private final long compressedSizeLimit;
|
---|
26 | private long uncompressedSize = 0;
|
---|
27 |
|
---|
28 | private final byte[] tempBuf = new byte[1];
|
---|
29 |
|
---|
30 | public BlockOutputStream(OutputStream out, FilterEncoder[] filters,
|
---|
31 | Check check, ArrayCache arrayCache)
|
---|
32 | throws IOException {
|
---|
33 | this.out = out;
|
---|
34 | this.check = check;
|
---|
35 |
|
---|
36 | // Initialize the filter chain.
|
---|
37 | outCounted = new CountingOutputStream(out);
|
---|
38 | filterChain = outCounted;
|
---|
39 | for (int i = filters.length - 1; i >= 0; --i)
|
---|
40 | filterChain = filters[i].getOutputStream(filterChain, arrayCache);
|
---|
41 |
|
---|
42 | // Prepare to encode the Block Header field.
|
---|
43 | ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
|
---|
44 |
|
---|
45 | // Write a dummy Block Header Size field. The real value is written
|
---|
46 | // once everything else except CRC32 has been written.
|
---|
47 | bufStream.write(0x00);
|
---|
48 |
|
---|
49 | // Write Block Flags. Storing Compressed Size or Uncompressed Size
|
---|
50 | // isn't supported for now.
|
---|
51 | bufStream.write(filters.length - 1);
|
---|
52 |
|
---|
53 | // List of Filter Flags
|
---|
54 | for (int i = 0; i < filters.length; ++i) {
|
---|
55 | EncoderUtil.encodeVLI(bufStream, filters[i].getFilterID());
|
---|
56 | byte[] filterProps = filters[i].getFilterProps();
|
---|
57 | EncoderUtil.encodeVLI(bufStream, filterProps.length);
|
---|
58 | bufStream.write(filterProps);
|
---|
59 | }
|
---|
60 |
|
---|
61 | // Header Padding
|
---|
62 | while ((bufStream.size() & 3) != 0)
|
---|
63 | bufStream.write(0x00);
|
---|
64 |
|
---|
65 | byte[] buf = bufStream.toByteArray();
|
---|
66 |
|
---|
67 | // Total size of the Block Header: Take the size of the CRC32 field
|
---|
68 | // into account.
|
---|
69 | headerSize = buf.length + 4;
|
---|
70 |
|
---|
71 | // This is just a sanity check.
|
---|
72 | if (headerSize > EncoderUtil.BLOCK_HEADER_SIZE_MAX)
|
---|
73 | throw new UnsupportedOptionsException();
|
---|
74 |
|
---|
75 | // Block Header Size
|
---|
76 | buf[0] = (byte)(buf.length / 4);
|
---|
77 |
|
---|
78 | // Write the Block Header field to the output stream.
|
---|
79 | out.write(buf);
|
---|
80 | EncoderUtil.writeCRC32(out, buf);
|
---|
81 |
|
---|
82 | // Calculate the maximum allowed size of the Compressed Data field.
|
---|
83 | // It is hard to exceed it so this is mostly to be pedantic.
|
---|
84 | compressedSizeLimit = (EncoderUtil.VLI_MAX & ~3)
|
---|
85 | - headerSize - check.getSize();
|
---|
86 | }
|
---|
87 |
|
---|
88 | public void write(int b) throws IOException {
|
---|
89 | tempBuf[0] = (byte)b;
|
---|
90 | write(tempBuf, 0, 1);
|
---|
91 | }
|
---|
92 |
|
---|
93 | public void write(byte[] buf, int off, int len) throws IOException {
|
---|
94 | filterChain.write(buf, off, len);
|
---|
95 | check.update(buf, off, len);
|
---|
96 | uncompressedSize += len;
|
---|
97 | validate();
|
---|
98 | }
|
---|
99 |
|
---|
100 | public void flush() throws IOException {
|
---|
101 | filterChain.flush();
|
---|
102 | validate();
|
---|
103 | }
|
---|
104 |
|
---|
105 | public void finish() throws IOException {
|
---|
106 | // Finish the Compressed Data field.
|
---|
107 | filterChain.finish();
|
---|
108 | validate();
|
---|
109 |
|
---|
110 | // Block Padding
|
---|
111 | for (long i = outCounted.getSize(); (i & 3) != 0; ++i)
|
---|
112 | out.write(0x00);
|
---|
113 |
|
---|
114 | // Check
|
---|
115 | out.write(check.finish());
|
---|
116 | }
|
---|
117 |
|
---|
118 | private void validate() throws IOException {
|
---|
119 | long compressedSize = outCounted.getSize();
|
---|
120 |
|
---|
121 | // It is very hard to trigger this exception.
|
---|
122 | // This is just to be pedantic.
|
---|
123 | if (compressedSize < 0 || compressedSize > compressedSizeLimit
|
---|
124 | || uncompressedSize < 0)
|
---|
125 | throw new XZIOException("XZ Stream has grown too big");
|
---|
126 | }
|
---|
127 |
|
---|
128 | public long getUnpaddedSize() {
|
---|
129 | return headerSize + outCounted.getSize() + check.getSize();
|
---|
130 | }
|
---|
131 |
|
---|
132 | public long getUncompressedSize() {
|
---|
133 | return uncompressedSize;
|
---|
134 | }
|
---|
135 | }
|
---|