source: josm/trunk/src/org/tukaani/xz/LZMA2OutputStream.java@ 13472

Last change on this file since 13472 was 13350, checked in by stoecker, 6 years ago

see #15816 - add XZ support

File size: 7.6 KB
Line 
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
11package org.tukaani.xz;
12
13import java.io.DataOutputStream;
14import java.io.IOException;
15import org.tukaani.xz.lz.LZEncoder;
16import org.tukaani.xz.rangecoder.RangeEncoderToBuffer;
17import org.tukaani.xz.lzma.LZMAEncoder;
18
19class 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
42 private static int getExtraSizeBefore(int dictSize) {
43 return COMPRESSED_SIZE_MAX > dictSize
44 ? COMPRESSED_SIZE_MAX - dictSize : 0;
45 }
46
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
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
86 public void write(int b) throws IOException {
87 tempBuf[0] = (byte)b;
88 write(tempBuf, 0, 1);
89 }
90
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
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
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
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
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
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
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
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}
Note: See TracBrowser for help on using the repository browser.