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