1 /* 2 * LZMAOutputStream 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.OutputStream; 14 import java.io.IOException; 15 import org.tukaani.xz.lz.LZEncoder; 16 import org.tukaani.xz.rangecoder.RangeEncoderToStream; 17 import org.tukaani.xz.lzma.LZMAEncoder; 18 19 /** 20 * Compresses into the legacy .lzma file format or into a raw LZMA stream. 21 * 22 * @since 1.6 23 */ 24 public class LZMAOutputStream extends FinishableOutputStream { 25 private OutputStream out; 26 27 private final ArrayCache arrayCache; 28 29 private LZEncoder lz; 30 private final RangeEncoderToStream rc; 31 private LZMAEncoder lzma; 32 33 private final int props; 34 private final boolean useEndMarker; 35 private final long expectedUncompressedSize; 36 private long currentUncompressedSize = 0; 37 38 private boolean finished = false; 39 private IOException exception = null; 40 41 private final byte[] tempBuf = new byte[1]; 42 LZMAOutputStream(OutputStream out, LZMA2Options options, boolean useHeader, boolean useEndMarker, long expectedUncompressedSize, ArrayCache arrayCache)43 private LZMAOutputStream(OutputStream out, LZMA2Options options, 44 boolean useHeader, boolean useEndMarker, 45 long expectedUncompressedSize, 46 ArrayCache arrayCache) 47 throws IOException { 48 if (out == null) 49 throw new NullPointerException(); 50 51 // -1 indicates unknown and >= 0 are for known sizes. 52 if (expectedUncompressedSize < -1) 53 throw new IllegalArgumentException( 54 "Invalid expected input size (less than -1)"); 55 56 this.useEndMarker = useEndMarker; 57 this.expectedUncompressedSize = expectedUncompressedSize; 58 59 this.arrayCache = arrayCache; 60 61 this.out = out; 62 rc = new RangeEncoderToStream(out); 63 64 int dictSize = options.getDictSize(); 65 lzma = LZMAEncoder.getInstance(rc, 66 options.getLc(), options.getLp(), options.getPb(), 67 options.getMode(), 68 dictSize, 0, options.getNiceLen(), 69 options.getMatchFinder(), options.getDepthLimit(), 70 arrayCache); 71 72 lz = lzma.getLZEncoder(); 73 74 byte[] presetDict = options.getPresetDict(); 75 if (presetDict != null && presetDict.length > 0) { 76 if (useHeader) 77 throw new UnsupportedOptionsException( 78 "Preset dictionary cannot be used in .lzma files " 79 + "(try a raw LZMA stream instead)"); 80 81 lz.setPresetDict(dictSize, presetDict); 82 } 83 84 props = (options.getPb() * 5 + options.getLp()) * 9 + options.getLc(); 85 86 if (useHeader) { 87 // Props byte stores lc, lp, and pb. 88 out.write(props); 89 90 // Dictionary size is stored as a 32-bit unsigned little endian 91 // integer. 92 for (int i = 0; i < 4; ++i) { 93 out.write(dictSize & 0xFF); 94 dictSize >>>= 8; 95 } 96 97 // Uncompressed size is stored as a 64-bit unsigned little endian 98 // integer. The max value (-1 in two's complement) indicates 99 // unknown size. 100 for (int i = 0; i < 8; ++i) 101 out.write((int)(expectedUncompressedSize >>> (8 * i)) & 0xFF); 102 } 103 } 104 105 /** 106 * Creates a new compressor for the legacy .lzma file format. 107 * <p> 108 * If the uncompressed size of the input data is known, it will be stored 109 * in the .lzma header and no end of stream marker will be used. Otherwise 110 * the header will indicate unknown uncompressed size and the end of stream 111 * marker will be used. 112 * <p> 113 * Note that a preset dictionary cannot be used in .lzma files but 114 * it can be used for raw LZMA streams. 115 * 116 * @param out output stream to which the compressed data 117 * will be written 118 * 119 * @param options LZMA compression options; the same class 120 * is used here as is for LZMA2 121 * 122 * @param inputSize uncompressed size of the data to be compressed; 123 * use <code>-1</code> when unknown 124 * 125 * @throws IOException may be thrown from <code>out</code> 126 */ LZMAOutputStream(OutputStream out, LZMA2Options options, long inputSize)127 public LZMAOutputStream(OutputStream out, LZMA2Options options, 128 long inputSize) 129 throws IOException { 130 this(out, options, inputSize, ArrayCache.getDefaultCache()); 131 } 132 133 /** 134 * Creates a new compressor for the legacy .lzma file format. 135 * <p> 136 * This is identical to 137 * <code>LZMAOutputStream(OutputStream, LZMA2Options, long)</code> 138 * except that this also takes the <code>arrayCache</code> argument. 139 * 140 * @param out output stream to which the compressed data 141 * will be written 142 * 143 * @param options LZMA compression options; the same class 144 * is used here as is for LZMA2 145 * 146 * @param inputSize uncompressed size of the data to be compressed; 147 * use <code>-1</code> when unknown 148 * 149 * @param arrayCache cache to be used for allocating large arrays 150 * 151 * @throws IOException may be thrown from <code>out</code> 152 * 153 * @since 1.7 154 */ LZMAOutputStream(OutputStream out, LZMA2Options options, long inputSize, ArrayCache arrayCache)155 public LZMAOutputStream(OutputStream out, LZMA2Options options, 156 long inputSize, ArrayCache arrayCache) 157 throws IOException { 158 this(out, options, true, inputSize == -1, inputSize, arrayCache); 159 } 160 161 /** 162 * Creates a new compressor for raw LZMA (also known as LZMA1) stream. 163 * <p> 164 * Raw LZMA streams can be encoded with or without end of stream marker. 165 * When decompressing the stream, one must know if the end marker was used 166 * and tell it to the decompressor. If the end marker wasn't used, the 167 * decompressor will also need to know the uncompressed size. 168 * 169 * @param out output stream to which the compressed data 170 * will be written 171 * 172 * @param options LZMA compression options; the same class 173 * is used here as is for LZMA2 174 * 175 * @param useEndMarker 176 * if end of stream marker should be written 177 * 178 * @throws IOException may be thrown from <code>out</code> 179 */ LZMAOutputStream(OutputStream out, LZMA2Options options, boolean useEndMarker)180 public LZMAOutputStream(OutputStream out, LZMA2Options options, 181 boolean useEndMarker) throws IOException { 182 this(out, options, useEndMarker, ArrayCache.getDefaultCache()); 183 } 184 185 /** 186 * Creates a new compressor for raw LZMA (also known as LZMA1) stream. 187 * <p> 188 * This is identical to 189 * <code>LZMAOutputStream(OutputStream, LZMA2Options, boolean)</code> 190 * except that this also takes the <code>arrayCache</code> argument. 191 * 192 * @param out output stream to which the compressed data 193 * will be written 194 * 195 * @param options LZMA compression options; the same class 196 * is used here as is for LZMA2 197 * 198 * @param useEndMarker 199 * if end of stream marker should be written 200 * 201 * @param arrayCache cache to be used for allocating large arrays 202 * 203 * @throws IOException may be thrown from <code>out</code> 204 * 205 * @since 1.7 206 */ LZMAOutputStream(OutputStream out, LZMA2Options options, boolean useEndMarker, ArrayCache arrayCache)207 public LZMAOutputStream(OutputStream out, LZMA2Options options, 208 boolean useEndMarker, ArrayCache arrayCache) 209 throws IOException { 210 this(out, options, false, useEndMarker, -1, arrayCache); 211 } 212 213 /** 214 * Returns the LZMA lc/lp/pb properties encoded into a single byte. 215 * This might be useful when handling file formats other than .lzma 216 * that use the same encoding for the LZMA properties as .lzma does. 217 */ getProps()218 public int getProps() { 219 return props; 220 } 221 222 /** 223 * Gets the amount of uncompressed data written to the stream. 224 * This is useful when creating raw LZMA streams without 225 * the end of stream marker. 226 */ getUncompressedSize()227 public long getUncompressedSize() { 228 return currentUncompressedSize; 229 } 230 write(int b)231 public void write(int b) throws IOException { 232 tempBuf[0] = (byte)b; 233 write(tempBuf, 0, 1); 234 } 235 write(byte[] buf, int off, int len)236 public void write(byte[] buf, int off, int len) throws IOException { 237 if (off < 0 || len < 0 || off + len < 0 || off + len > buf.length) 238 throw new IndexOutOfBoundsException(); 239 240 if (exception != null) 241 throw exception; 242 243 if (finished) 244 throw new XZIOException("Stream finished or closed"); 245 246 if (expectedUncompressedSize != -1 247 && expectedUncompressedSize - currentUncompressedSize < len) 248 throw new XZIOException("Expected uncompressed input size (" 249 + expectedUncompressedSize + " bytes) was exceeded"); 250 251 currentUncompressedSize += len; 252 253 try { 254 while (len > 0) { 255 int used = lz.fillWindow(buf, off, len); 256 off += used; 257 len -= used; 258 lzma.encodeForLZMA1(); 259 } 260 } catch (IOException e) { 261 exception = e; 262 throw e; 263 } 264 } 265 266 /** 267 * Flushing isn't supported and will throw XZIOException. 268 */ flush()269 public void flush() throws IOException { 270 throw new XZIOException("LZMAOutputStream does not support flushing"); 271 } 272 273 /** 274 * Finishes the stream without closing the underlying OutputStream. 275 */ finish()276 public void finish() throws IOException { 277 if (!finished) { 278 if (exception != null) 279 throw exception; 280 281 try { 282 if (expectedUncompressedSize != -1 283 && expectedUncompressedSize != currentUncompressedSize) 284 throw new XZIOException("Expected uncompressed size (" 285 + expectedUncompressedSize + ") doesn't equal " 286 + "the number of bytes written to the stream (" 287 + currentUncompressedSize + ")"); 288 289 lz.setFinishing(); 290 lzma.encodeForLZMA1(); 291 292 if (useEndMarker) 293 lzma.encodeLZMA1EndMarker(); 294 295 rc.finish(); 296 } catch (IOException e) { 297 exception = e; 298 throw e; 299 } 300 301 finished = true; 302 303 lzma.putArraysToCache(arrayCache); 304 lzma = null; 305 lz = null; 306 } 307 } 308 309 /** 310 * Finishes the stream and closes the underlying OutputStream. 311 */ close()312 public void close() throws IOException { 313 if (out != null) { 314 try { 315 finish(); 316 } catch (IOException e) {} 317 318 try { 319 out.close(); 320 } catch (IOException e) { 321 if (exception == null) 322 exception = e; 323 } 324 325 out = null; 326 } 327 328 if (exception != null) 329 throw exception; 330 } 331 } 332