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