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