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.snappy;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23
24 import org.apache.commons.compress.compressors.CompressorOutputStream;
25 import org.apache.commons.compress.compressors.lz77support.LZ77Compressor;
26 import org.apache.commons.compress.compressors.lz77support.Parameters;
27 import org.apache.commons.compress.utils.ByteUtils;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class SnappyCompressorOutputStream extends CompressorOutputStream<OutputStream> {
50
51
52 private static final int MAX_LITERAL_SIZE_WITHOUT_SIZE_BYTES = 60;
53 private static final int MAX_LITERAL_SIZE_WITH_ONE_SIZE_BYTE = 1 << 8;
54 private static final int MAX_LITERAL_SIZE_WITH_TWO_SIZE_BYTES = 1 << 16;
55
56 private static final int MAX_LITERAL_SIZE_WITH_THREE_SIZE_BYTES = 1 << 24;
57
58 private static final int ONE_SIZE_BYTE_MARKER = 60 << 2;
59
60 private static final int TWO_SIZE_BYTE_MARKER = 61 << 2;
61
62 private static final int THREE_SIZE_BYTE_MARKER = 62 << 2;
63
64 private static final int FOUR_SIZE_BYTE_MARKER = 63 << 2;
65
66
67
68 private static final int MIN_MATCH_LENGTH_WITH_ONE_OFFSET_BYTE = 4;
69
70 private static final int MAX_MATCH_LENGTH_WITH_ONE_OFFSET_BYTE = 11;
71
72 private static final int MAX_OFFSET_WITH_ONE_OFFSET_BYTE = 1 << 11 - 1;
73
74 private static final int MAX_OFFSET_WITH_TWO_OFFSET_BYTES = 1 << 16 - 1;
75
76 private static final int ONE_BYTE_COPY_TAG = 1;
77
78 private static final int TWO_BYTE_COPY_TAG = 2;
79 private static final int FOUR_BYTE_COPY_TAG = 3;
80
81
82
83 private static final int MIN_MATCH_LENGTH = 4;
84
85 private static final int MAX_MATCH_LENGTH = 64;
86
87
88
89
90
91
92
93 public static Parameters.Builder createParameterBuilder(final int blockSize) {
94
95
96
97 return Parameters.builder(blockSize).withMinBackReferenceLength(MIN_MATCH_LENGTH).withMaxBackReferenceLength(MAX_MATCH_LENGTH).withMaxOffset(blockSize)
98 .withMaxLiteralLength(blockSize);
99 }
100
101 private final LZ77Compressor compressor;
102 private final ByteUtils.ByteConsumer consumer;
103
104
105 private final byte[] oneByte = new byte[1];
106
107
108
109
110
111
112
113
114 public SnappyCompressorOutputStream(final OutputStream os, final long uncompressedSize) throws IOException {
115 this(os, uncompressedSize, SnappyCompressorInputStream.DEFAULT_BLOCK_SIZE);
116 }
117
118
119
120
121
122
123
124
125
126 public SnappyCompressorOutputStream(final OutputStream os, final long uncompressedSize, final int blockSize) throws IOException {
127 this(os, uncompressedSize, createParameterBuilder(blockSize).build());
128 }
129
130
131
132
133
134
135
136
137
138 public SnappyCompressorOutputStream(final OutputStream out, final long uncompressedSize, final Parameters params) throws IOException {
139 super(out);
140 consumer = new ByteUtils.OutputStreamByteConsumer(out);
141 compressor = new LZ77Compressor(params, block -> {
142 switch (block.getType()) {
143 case LITERAL:
144 writeLiteralBlock((LZ77Compressor.LiteralBlock) block);
145 break;
146 case BACK_REFERENCE:
147 writeBackReference((LZ77Compressor.BackReference) block);
148 break;
149 case EOD:
150 break;
151 }
152 });
153 writeUncompressedSize(uncompressedSize);
154 }
155
156 @Override
157 public void close() throws IOException {
158 try {
159 finish();
160 } finally {
161 super.close();
162 }
163 }
164
165
166
167
168
169
170 @Override
171 public void finish() throws IOException {
172 if (!isFinished()) {
173 compressor.finish();
174 super.finish();
175 }
176 }
177
178 @Override
179 public void write(final byte[] data, final int off, final int len) throws IOException {
180 compressor.compress(data, off, len);
181 }
182
183 @Override
184 public void write(final int b) throws IOException {
185 oneByte[0] = (byte) (b & 0xff);
186 write(oneByte);
187 }
188
189 private void writeBackReference(final LZ77Compressor.BackReference block) throws IOException {
190 final int len = block.getLength();
191 final int offset = block.getOffset();
192 if (len >= MIN_MATCH_LENGTH_WITH_ONE_OFFSET_BYTE && len <= MAX_MATCH_LENGTH_WITH_ONE_OFFSET_BYTE && offset <= MAX_OFFSET_WITH_ONE_OFFSET_BYTE) {
193 writeBackReferenceWithOneOffsetByte(len, offset);
194 } else if (offset < MAX_OFFSET_WITH_TWO_OFFSET_BYTES) {
195 writeBackReferenceWithTwoOffsetBytes(len, offset);
196 } else {
197 writeBackReferenceWithFourOffsetBytes(len, offset);
198 }
199 }
200
201 private void writeBackReferenceWithFourOffsetBytes(final int len, final int offset) throws IOException {
202 writeBackReferenceWithLittleEndianOffset(FOUR_BYTE_COPY_TAG, 4, len, offset);
203 }
204
205 private void writeBackReferenceWithLittleEndianOffset(final int tag, final int offsetBytes, final int len, final int offset) throws IOException {
206 out.write(tag | len - 1 << 2);
207 writeLittleEndian(offsetBytes, offset);
208 }
209
210 private void writeBackReferenceWithOneOffsetByte(final int len, final int offset) throws IOException {
211 out.write(ONE_BYTE_COPY_TAG | len - 4 << 2 | (offset & 0x700) >> 3);
212 out.write(offset & 0xff);
213 }
214
215 private void writeBackReferenceWithTwoOffsetBytes(final int len, final int offset) throws IOException {
216 writeBackReferenceWithLittleEndianOffset(TWO_BYTE_COPY_TAG, 2, len, offset);
217 }
218
219 private void writeLiteralBlock(final LZ77Compressor.LiteralBlock block) throws IOException {
220 final int len = block.getLength();
221 if (len <= MAX_LITERAL_SIZE_WITHOUT_SIZE_BYTES) {
222 writeLiteralBlockNoSizeBytes(block, len);
223 } else if (len <= MAX_LITERAL_SIZE_WITH_ONE_SIZE_BYTE) {
224 writeLiteralBlockOneSizeByte(block, len);
225 } else if (len <= MAX_LITERAL_SIZE_WITH_TWO_SIZE_BYTES) {
226 writeLiteralBlockTwoSizeBytes(block, len);
227 } else if (len <= MAX_LITERAL_SIZE_WITH_THREE_SIZE_BYTES) {
228 writeLiteralBlockThreeSizeBytes(block, len);
229 } else {
230 writeLiteralBlockFourSizeBytes(block, len);
231 }
232 }
233
234 private void writeLiteralBlockFourSizeBytes(final LZ77Compressor.LiteralBlock block, final int len) throws IOException {
235 writeLiteralBlockWithSize(FOUR_SIZE_BYTE_MARKER, 4, len, block);
236 }
237
238 private void writeLiteralBlockNoSizeBytes(final LZ77Compressor.LiteralBlock block, final int len) throws IOException {
239 writeLiteralBlockWithSize(len - 1 << 2, 0, len, block);
240 }
241
242 private void writeLiteralBlockOneSizeByte(final LZ77Compressor.LiteralBlock block, final int len) throws IOException {
243 writeLiteralBlockWithSize(ONE_SIZE_BYTE_MARKER, 1, len, block);
244 }
245
246 private void writeLiteralBlockThreeSizeBytes(final LZ77Compressor.LiteralBlock block, final int len) throws IOException {
247 writeLiteralBlockWithSize(THREE_SIZE_BYTE_MARKER, 3, len, block);
248 }
249
250 private void writeLiteralBlockTwoSizeBytes(final LZ77Compressor.LiteralBlock block, final int len) throws IOException {
251 writeLiteralBlockWithSize(TWO_SIZE_BYTE_MARKER, 2, len, block);
252 }
253
254 private void writeLiteralBlockWithSize(final int tagByte, final int sizeBytes, final int len, final LZ77Compressor.LiteralBlock block) throws IOException {
255 out.write(tagByte);
256 writeLittleEndian(sizeBytes, len - 1);
257 out.write(block.getData(), block.getOffset(), len);
258 }
259
260 private void writeLittleEndian(final int numBytes, final int num) throws IOException {
261 ByteUtils.toLittleEndian(consumer, num, numBytes);
262 }
263
264 private void writeUncompressedSize(long uncompressedSize) throws IOException {
265 boolean more;
266 do {
267 int currentByte = (int) (uncompressedSize & 0x7F);
268 more = uncompressedSize > currentByte;
269 if (more) {
270 currentByte |= 0x80;
271 }
272 out.write(currentByte);
273 uncompressedSize >>= 7;
274 } while (more);
275 }
276 }