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