• 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.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