1 /* 2 * LZMA2OutputStream 3 * 4 * Authors: Lasse Collin <lasse.collin@tukaani.org> 5 * Igor Pavlov <http://7-zip.org/> 6 * 7 * This file has been put into the public domain. 8 * You can do whatever you want with this file. 9 */ 10 11 package org.tukaani.xz; 12 13 import java.io.DataOutputStream; 14 import java.io.IOException; 15 import org.tukaani.xz.lz.LZEncoder; 16 import org.tukaani.xz.rangecoder.RangeEncoderToBuffer; 17 import org.tukaani.xz.lzma.LZMAEncoder; 18 19 class LZMA2OutputStream extends FinishableOutputStream { 20 static final int COMPRESSED_SIZE_MAX = 64 << 10; 21 22 private final ArrayCache arrayCache; 23 24 private FinishableOutputStream out; 25 private final DataOutputStream outData; 26 27 private LZEncoder lz; 28 private RangeEncoderToBuffer rc; 29 private LZMAEncoder lzma; 30 31 private final int props; // Cannot change props on the fly for now. 32 private boolean dictResetNeeded = true; 33 private boolean stateResetNeeded = true; 34 private boolean propsNeeded = true; 35 36 private int pendingSize = 0; 37 private boolean finished = false; 38 private IOException exception = null; 39 40 private final byte[] tempBuf = new byte[1]; 41 getExtraSizeBefore(int dictSize)42 private static int getExtraSizeBefore(int dictSize) { 43 return COMPRESSED_SIZE_MAX > dictSize 44 ? COMPRESSED_SIZE_MAX - dictSize : 0; 45 } 46 getMemoryUsage(LZMA2Options options)47 static int getMemoryUsage(LZMA2Options options) { 48 // 64 KiB buffer for the range encoder + a little extra + LZMAEncoder 49 int dictSize = options.getDictSize(); 50 int extraSizeBefore = getExtraSizeBefore(dictSize); 51 return 70 + LZMAEncoder.getMemoryUsage(options.getMode(), 52 dictSize, extraSizeBefore, 53 options.getMatchFinder()); 54 } 55 LZMA2OutputStream(FinishableOutputStream out, LZMA2Options options, ArrayCache arrayCache)56 LZMA2OutputStream(FinishableOutputStream out, LZMA2Options options, 57 ArrayCache arrayCache) { 58 if (out == null) 59 throw new NullPointerException(); 60 61 this.arrayCache = arrayCache; 62 this.out = out; 63 outData = new DataOutputStream(out); 64 rc = new RangeEncoderToBuffer(COMPRESSED_SIZE_MAX, arrayCache); 65 66 int dictSize = options.getDictSize(); 67 int extraSizeBefore = getExtraSizeBefore(dictSize); 68 lzma = LZMAEncoder.getInstance(rc, 69 options.getLc(), options.getLp(), options.getPb(), 70 options.getMode(), 71 dictSize, extraSizeBefore, options.getNiceLen(), 72 options.getMatchFinder(), options.getDepthLimit(), 73 this.arrayCache); 74 75 lz = lzma.getLZEncoder(); 76 77 byte[] presetDict = options.getPresetDict(); 78 if (presetDict != null && presetDict.length > 0) { 79 lz.setPresetDict(dictSize, presetDict); 80 dictResetNeeded = false; 81 } 82 83 props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc(); 84 } 85 write(int b)86 public void write(int b) throws IOException { 87 tempBuf[0] = (byte)b; 88 write(tempBuf, 0, 1); 89 } 90 write(byte[] buf, int off, int len)91 public void write(byte[] buf, int off, int len) throws IOException { 92 if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length) 93 throw new IndexOutOfBoundsException(); 94 95 if (exception != null) 96 throw exception; 97 98 if (finished) 99 throw new XZIOException("Stream finished or closed"); 100 101 try { 102 while (len > 0) { 103 int used = lz.fillWindow(buf, off, len); 104 off += used; 105 len -= used; 106 pendingSize += used; 107 108 if (lzma.encodeForLZMA2()) 109 writeChunk(); 110 } 111 } catch (IOException e) { 112 exception = e; 113 throw e; 114 } 115 } 116 writeChunk()117 private void writeChunk() throws IOException { 118 int compressedSize = rc.finish(); 119 int uncompressedSize = lzma.getUncompressedSize(); 120 121 assert compressedSize > 0 : compressedSize; 122 assert uncompressedSize > 0 : uncompressedSize; 123 124 // +2 because the header of a compressed chunk is 2 bytes 125 // bigger than the header of an uncompressed chunk. 126 if (compressedSize + 2 < uncompressedSize) { 127 writeLZMA(uncompressedSize, compressedSize); 128 } else { 129 lzma.reset(); 130 uncompressedSize = lzma.getUncompressedSize(); 131 assert uncompressedSize > 0 : uncompressedSize; 132 writeUncompressed(uncompressedSize); 133 } 134 135 pendingSize -= uncompressedSize; 136 lzma.resetUncompressedSize(); 137 rc.reset(); 138 } 139 writeLZMA(int uncompressedSize, int compressedSize)140 private void writeLZMA(int uncompressedSize, int compressedSize) 141 throws IOException { 142 int control; 143 144 if (propsNeeded) { 145 if (dictResetNeeded) 146 control = 0x80 + (3 << 5); 147 else 148 control = 0x80 + (2 << 5); 149 } else { 150 if (stateResetNeeded) 151 control = 0x80 + (1 << 5); 152 else 153 control = 0x80; 154 } 155 156 control |= (uncompressedSize - 1) >>> 16; 157 outData.writeByte(control); 158 159 outData.writeShort(uncompressedSize - 1); 160 outData.writeShort(compressedSize - 1); 161 162 if (propsNeeded) 163 outData.writeByte(props); 164 165 rc.write(out); 166 167 propsNeeded = false; 168 stateResetNeeded = false; 169 dictResetNeeded = false; 170 } 171 writeUncompressed(int uncompressedSize)172 private void writeUncompressed(int uncompressedSize) throws IOException { 173 while (uncompressedSize > 0) { 174 int chunkSize = Math.min(uncompressedSize, COMPRESSED_SIZE_MAX); 175 outData.writeByte(dictResetNeeded ? 0x01 : 0x02); 176 outData.writeShort(chunkSize - 1); 177 lz.copyUncompressed(out, uncompressedSize, chunkSize); 178 uncompressedSize -= chunkSize; 179 dictResetNeeded = false; 180 } 181 182 stateResetNeeded = true; 183 } 184 writeEndMarker()185 private void writeEndMarker() throws IOException { 186 assert !finished; 187 188 if (exception != null) 189 throw exception; 190 191 lz.setFinishing(); 192 193 try { 194 while (pendingSize > 0) { 195 lzma.encodeForLZMA2(); 196 writeChunk(); 197 } 198 199 out.write(0x00); 200 } catch (IOException e) { 201 exception = e; 202 throw e; 203 } 204 205 finished = true; 206 207 lzma.putArraysToCache(arrayCache); 208 lzma = null; 209 lz = null; 210 rc.putArraysToCache(arrayCache); 211 rc = null; 212 } 213 flush()214 public void flush() throws IOException { 215 if (exception != null) 216 throw exception; 217 218 if (finished) 219 throw new XZIOException("Stream finished or closed"); 220 221 try { 222 lz.setFlushing(); 223 224 while (pendingSize > 0) { 225 lzma.encodeForLZMA2(); 226 writeChunk(); 227 } 228 229 out.flush(); 230 } catch (IOException e) { 231 exception = e; 232 throw e; 233 } 234 } 235 finish()236 public void finish() throws IOException { 237 if (!finished) { 238 writeEndMarker(); 239 240 try { 241 out.finish(); 242 } catch (IOException e) { 243 exception = e; 244 throw e; 245 } 246 } 247 } 248 close()249 public void close() throws IOException { 250 if (out != null) { 251 if (!finished) { 252 try { 253 writeEndMarker(); 254 } catch (IOException e) {} 255 } 256 257 try { 258 out.close(); 259 } catch (IOException e) { 260 if (exception == null) 261 exception = e; 262 } 263 264 out = null; 265 } 266 267 if (exception != null) 268 throw exception; 269 } 270 } 271