1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.commons.compress.compressors.lz4;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24
25 import org.apache.commons.compress.compressors.CompressorOutputStream;
26 import org.apache.commons.compress.utils.ByteUtils;
27
28
29
30
31
32
33
34
35
36
37
38
39 public class FramedLZ4CompressorOutputStream extends CompressorOutputStream<OutputStream> {
40
41
42
43
44 public enum BlockSize {
45
46 K64(64 * 1024, 4),
47
48 K256(256 * 1024, 5),
49
50 M1(1024 * 1024, 6),
51
52 M4(4096 * 1024, 7);
53
54 private final int size, index;
55
56 BlockSize(final int size, final int index) {
57 this.size = size;
58 this.index = index;
59 }
60
61 int getIndex() {
62 return index;
63 }
64
65 int getSize() {
66 return size;
67 }
68 }
69
70
71
72
73 public static class Parameters {
74
75
76
77
78
79
80
81 public static final Parameters DEFAULT = new Parameters(BlockSize.M4, true, false, false);
82 private final BlockSize blockSize;
83 private final boolean withContentChecksum, withBlockChecksum, withBlockDependency;
84
85 private final org.apache.commons.compress.compressors.lz77support.Parameters lz77params;
86
87
88
89
90
91
92
93 public Parameters(final BlockSize blockSize) {
94 this(blockSize, true, false, false);
95 }
96
97
98
99
100
101
102
103
104
105
106 public Parameters(final BlockSize blockSize, final boolean withContentChecksum, final boolean withBlockChecksum, final boolean withBlockDependency) {
107 this(blockSize, withContentChecksum, withBlockChecksum, withBlockDependency, BlockLZ4CompressorOutputStream.createParameterBuilder().build());
108 }
109
110
111
112
113
114
115
116
117
118
119
120 public Parameters(final BlockSize blockSize, final boolean withContentChecksum, final boolean withBlockChecksum, final boolean withBlockDependency,
121 final org.apache.commons.compress.compressors.lz77support.Parameters lz77params) {
122 this.blockSize = blockSize;
123 this.withContentChecksum = withContentChecksum;
124 this.withBlockChecksum = withBlockChecksum;
125 this.withBlockDependency = withBlockDependency;
126 this.lz77params = lz77params;
127 }
128
129
130
131
132
133
134
135
136 public Parameters(final BlockSize blockSize, final org.apache.commons.compress.compressors.lz77support.Parameters lz77params) {
137 this(blockSize, true, false, false, lz77params);
138 }
139
140 @Override
141 public String toString() {
142 return "LZ4 Parameters with BlockSize " + blockSize + ", withContentChecksum " + withContentChecksum + ", withBlockChecksum " + withBlockChecksum
143 + ", withBlockDependency " + withBlockDependency;
144 }
145 }
146
147 private static final byte[] END_MARK = new byte[4];
148
149 private final byte[] oneByte = new byte[1];
150 private final byte[] blockData;
151 private final Parameters params;
152
153 private boolean finished;
154
155
156 private final org.apache.commons.codec.digest.XXHash32 contentHash = new org.apache.commons.codec.digest.XXHash32();
157
158 private final org.apache.commons.codec.digest.XXHash32 blockHash;
159
160
161 private final byte[] blockDependencyBuffer;
162
163 private int collectedBlockDependencyBytes;
164 private int currentIndex;
165
166
167
168
169
170
171
172 public FramedLZ4CompressorOutputStream(final OutputStream out) throws IOException {
173 this(out, Parameters.DEFAULT);
174 }
175
176
177
178
179
180
181
182
183 public FramedLZ4CompressorOutputStream(final OutputStream out, final Parameters params) throws IOException {
184 super(out);
185 this.params = params;
186 blockData = new byte[params.blockSize.getSize()];
187 blockHash = params.withBlockChecksum ? new org.apache.commons.codec.digest.XXHash32() : null;
188 out.write(FramedLZ4CompressorInputStream.LZ4_SIGNATURE);
189 writeFrameDescriptor();
190 blockDependencyBuffer = params.withBlockDependency ? new byte[BlockLZ4CompressorInputStream.WINDOW_SIZE] : null;
191 }
192
193 private void appendToBlockDependencyBuffer(final byte[] b, final int off, int len) {
194 len = Math.min(len, blockDependencyBuffer.length);
195 if (len > 0) {
196 final int keep = blockDependencyBuffer.length - len;
197 if (keep > 0) {
198
199 System.arraycopy(blockDependencyBuffer, len, blockDependencyBuffer, 0, keep);
200 }
201
202 System.arraycopy(b, off, blockDependencyBuffer, keep, len);
203 collectedBlockDependencyBytes = Math.min(collectedBlockDependencyBytes + len, blockDependencyBuffer.length);
204 }
205 }
206
207 @Override
208 public void close() throws IOException {
209 try {
210 finish();
211 } finally {
212 out.close();
213 }
214 }
215
216
217
218
219
220
221 public void finish() throws IOException {
222 if (!finished) {
223 flushBlock();
224 writeTrailer();
225 finished = true;
226 }
227 }
228
229 private void flushBlock() throws IOException {
230 if (currentIndex == 0) {
231 return;
232 }
233 final boolean withBlockDependency = params.withBlockDependency;
234 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
235 try (BlockLZ4CompressorOutputStream o = new BlockLZ4CompressorOutputStream(baos, params.lz77params)) {
236 if (withBlockDependency) {
237 o.prefill(blockDependencyBuffer, blockDependencyBuffer.length - collectedBlockDependencyBytes, collectedBlockDependencyBytes);
238 }
239 o.write(blockData, 0, currentIndex);
240 }
241 if (withBlockDependency) {
242 appendToBlockDependencyBuffer(blockData, 0, currentIndex);
243 }
244 final byte[] b = baos.toByteArray();
245 if (b.length > currentIndex) {
246 ByteUtils.toLittleEndian(out, currentIndex | FramedLZ4CompressorInputStream.UNCOMPRESSED_FLAG_MASK, 4);
247 out.write(blockData, 0, currentIndex);
248 if (params.withBlockChecksum) {
249 blockHash.update(blockData, 0, currentIndex);
250 }
251 } else {
252 ByteUtils.toLittleEndian(out, b.length, 4);
253 out.write(b);
254 if (params.withBlockChecksum) {
255 blockHash.update(b, 0, b.length);
256 }
257 }
258 if (params.withBlockChecksum) {
259 ByteUtils.toLittleEndian(out, blockHash.getValue(), 4);
260 blockHash.reset();
261 }
262 currentIndex = 0;
263 }
264
265 @Override
266 public void write(final byte[] data, int off, int len) throws IOException {
267 if (params.withContentChecksum) {
268 contentHash.update(data, off, len);
269 }
270 int blockDataRemaining = blockData.length - currentIndex;
271 while (len > 0) {
272 final int copyLen = Math.min(len, blockDataRemaining);
273 System.arraycopy(data, off, blockData, currentIndex, copyLen);
274 off += copyLen;
275 blockDataRemaining -= copyLen;
276 len -= copyLen;
277 currentIndex += copyLen;
278 if (blockDataRemaining == 0) {
279 flushBlock();
280 blockDataRemaining = blockData.length;
281 }
282 }
283 }
284
285 @Override
286 public void write(final int b) throws IOException {
287 oneByte[0] = (byte) (b & 0xff);
288 write(oneByte);
289 }
290
291 private void writeFrameDescriptor() throws IOException {
292 int flags = FramedLZ4CompressorInputStream.SUPPORTED_VERSION;
293 if (!params.withBlockDependency) {
294 flags |= FramedLZ4CompressorInputStream.BLOCK_INDEPENDENCE_MASK;
295 }
296 if (params.withContentChecksum) {
297 flags |= FramedLZ4CompressorInputStream.CONTENT_CHECKSUM_MASK;
298 }
299 if (params.withBlockChecksum) {
300 flags |= FramedLZ4CompressorInputStream.BLOCK_CHECKSUM_MASK;
301 }
302 out.write(flags);
303 contentHash.update(flags);
304 final int bd = params.blockSize.getIndex() << 4 & FramedLZ4CompressorInputStream.BLOCK_MAX_SIZE_MASK;
305 out.write(bd);
306 contentHash.update(bd);
307 out.write((int) (contentHash.getValue() >> 8 & 0xff));
308 contentHash.reset();
309 }
310
311 private void writeTrailer() throws IOException {
312 out.write(END_MARK);
313 if (params.withContentChecksum) {
314 ByteUtils.toLittleEndian(out, contentHash.getValue(), 4);
315 }
316 }
317
318 }