• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BlockOutputStream(OutputStream out, FilterEncoder[] filters, Check check, ArrayCache arrayCache)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 
write(int b)88     public void write(int b) throws IOException {
89         tempBuf[0] = (byte)b;
90         write(tempBuf, 0, 1);
91     }
92 
write(byte[] buf, int off, int len)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 
flush()100     public void flush() throws IOException {
101         filterChain.flush();
102         validate();
103     }
104 
finish()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 
validate()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 
getUnpaddedSize()128     public long getUnpaddedSize() {
129         return headerSize + outCounted.getSize() + check.getSize();
130     }
131 
getUncompressedSize()132     public long getUncompressedSize() {
133         return uncompressedSize;
134     }
135 }
136