• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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