1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.tools; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.InputStream; |
10 | import java.io.OutputStream; |
11 | import java.util.zip.DeflaterOutputStream; |
12 | import java.util.zip.GZIPInputStream; |
13 | import java.util.zip.GZIPOutputStream; |
14 | import java.util.zip.InflaterInputStream; |
15 | import java.util.zip.ZipEntry; |
16 | import java.util.zip.ZipInputStream; |
17 | import java.util.zip.ZipOutputStream; |
18 | |
19 | import org.h2.api.ErrorCode; |
20 | import org.h2.compress.CompressDeflate; |
21 | import org.h2.compress.CompressLZF; |
22 | import org.h2.compress.CompressNo; |
23 | import org.h2.compress.Compressor; |
24 | import org.h2.compress.LZFInputStream; |
25 | import org.h2.compress.LZFOutputStream; |
26 | import org.h2.engine.Constants; |
27 | import org.h2.message.DbException; |
28 | import org.h2.mvstore.DataUtils; |
29 | import org.h2.util.StringUtils; |
30 | |
31 | /** |
32 | * A tool to losslessly compress data, and expand the compressed data again. |
33 | */ |
34 | public class CompressTool { |
35 | |
36 | private static final int MAX_BUFFER_SIZE = |
37 | 3 * Constants.IO_BUFFER_SIZE_COMPRESS; |
38 | private byte[] cachedBuffer; |
39 | |
40 | private CompressTool() { |
41 | // don't allow construction |
42 | } |
43 | |
44 | private byte[] getBuffer(int min) { |
45 | if (min > MAX_BUFFER_SIZE) { |
46 | return DataUtils.newBytes(min); |
47 | } |
48 | if (cachedBuffer == null || cachedBuffer.length < min) { |
49 | cachedBuffer = DataUtils.newBytes(min); |
50 | } |
51 | return cachedBuffer; |
52 | } |
53 | |
54 | /** |
55 | * Get a new instance. Each instance uses a separate buffer, so multiple |
56 | * instances can be used concurrently. However each instance alone is not |
57 | * multithreading safe. |
58 | * |
59 | * @return a new instance |
60 | */ |
61 | public static CompressTool getInstance() { |
62 | return new CompressTool(); |
63 | } |
64 | |
65 | /** |
66 | * Compressed the data using the specified algorithm. If no algorithm is |
67 | * supplied, LZF is used |
68 | * |
69 | * @param in the byte array with the original data |
70 | * @param algorithm the algorithm (LZF, DEFLATE) |
71 | * @return the compressed data |
72 | */ |
73 | public byte[] compress(byte[] in, String algorithm) { |
74 | int len = in.length; |
75 | if (in.length < 5) { |
76 | algorithm = "NO"; |
77 | } |
78 | Compressor compress = getCompressor(algorithm); |
79 | byte[] buff = getBuffer((len < 100 ? len + 100 : len) * 2); |
80 | int newLen = compress(in, in.length, compress, buff); |
81 | byte[] out = DataUtils.newBytes(newLen); |
82 | System.arraycopy(buff, 0, out, 0, newLen); |
83 | return out; |
84 | } |
85 | |
86 | private static int compress(byte[] in, int len, Compressor compress, |
87 | byte[] out) { |
88 | int newLen = 0; |
89 | out[0] = (byte) compress.getAlgorithm(); |
90 | int start = 1 + writeVariableInt(out, 1, len); |
91 | newLen = compress.compress(in, len, out, start); |
92 | if (newLen > len + start || newLen <= 0) { |
93 | out[0] = Compressor.NO; |
94 | System.arraycopy(in, 0, out, start, len); |
95 | newLen = len + start; |
96 | } |
97 | return newLen; |
98 | } |
99 | |
100 | /** |
101 | * Expands the compressed data. |
102 | * |
103 | * @param in the byte array with the compressed data |
104 | * @return the uncompressed data |
105 | */ |
106 | public byte[] expand(byte[] in) { |
107 | int algorithm = in[0]; |
108 | Compressor compress = getCompressor(algorithm); |
109 | try { |
110 | int len = readVariableInt(in, 1); |
111 | int start = 1 + getVariableIntLength(len); |
112 | byte[] buff = DataUtils.newBytes(len); |
113 | compress.expand(in, start, in.length - start, buff, 0, len); |
114 | return buff; |
115 | } catch (Exception e) { |
116 | throw DbException.get(ErrorCode.COMPRESSION_ERROR, e); |
117 | } |
118 | } |
119 | |
120 | /** |
121 | * INTERNAL |
122 | */ |
123 | public static void expand(byte[] in, byte[] out, int outPos) { |
124 | int algorithm = in[0]; |
125 | Compressor compress = getCompressor(algorithm); |
126 | try { |
127 | int len = readVariableInt(in, 1); |
128 | int start = 1 + getVariableIntLength(len); |
129 | compress.expand(in, start, in.length - start, out, outPos, len); |
130 | } catch (Exception e) { |
131 | throw DbException.get(ErrorCode.COMPRESSION_ERROR, e); |
132 | } |
133 | } |
134 | |
135 | /** |
136 | * Read a variable size integer using Rice coding. |
137 | * |
138 | * @param buff the buffer |
139 | * @param pos the position |
140 | * @return the integer |
141 | */ |
142 | public static int readVariableInt(byte[] buff, int pos) { |
143 | int x = buff[pos++] & 0xff; |
144 | if (x < 0x80) { |
145 | return x; |
146 | } |
147 | if (x < 0xc0) { |
148 | return ((x & 0x3f) << 8) + (buff[pos] & 0xff); |
149 | } |
150 | if (x < 0xe0) { |
151 | return ((x & 0x1f) << 16) + |
152 | ((buff[pos++] & 0xff) << 8) + |
153 | (buff[pos] & 0xff); |
154 | } |
155 | if (x < 0xf0) { |
156 | return ((x & 0xf) << 24) + |
157 | ((buff[pos++] & 0xff) << 16) + |
158 | ((buff[pos++] & 0xff) << 8) + |
159 | (buff[pos] & 0xff); |
160 | } |
161 | return ((buff[pos++] & 0xff) << 24) + |
162 | ((buff[pos++] & 0xff) << 16) + |
163 | ((buff[pos++] & 0xff) << 8) + |
164 | (buff[pos] & 0xff); |
165 | } |
166 | |
167 | /** |
168 | * Write a variable size integer using Rice coding. |
169 | * Negative values need 5 bytes. |
170 | * |
171 | * @param buff the buffer |
172 | * @param pos the position |
173 | * @param x the value |
174 | * @return the number of bytes written (0-5) |
175 | */ |
176 | public static int writeVariableInt(byte[] buff, int pos, int x) { |
177 | if (x < 0) { |
178 | buff[pos++] = (byte) 0xf0; |
179 | buff[pos++] = (byte) (x >> 24); |
180 | buff[pos++] = (byte) (x >> 16); |
181 | buff[pos++] = (byte) (x >> 8); |
182 | buff[pos] = (byte) x; |
183 | return 5; |
184 | } else if (x < 0x80) { |
185 | buff[pos] = (byte) x; |
186 | return 1; |
187 | } else if (x < 0x4000) { |
188 | buff[pos++] = (byte) (0x80 | (x >> 8)); |
189 | buff[pos] = (byte) x; |
190 | return 2; |
191 | } else if (x < 0x200000) { |
192 | buff[pos++] = (byte) (0xc0 | (x >> 16)); |
193 | buff[pos++] = (byte) (x >> 8); |
194 | buff[pos] = (byte) x; |
195 | return 3; |
196 | } else if (x < 0x10000000) { |
197 | buff[pos++] = (byte) (0xe0 | (x >> 24)); |
198 | buff[pos++] = (byte) (x >> 16); |
199 | buff[pos++] = (byte) (x >> 8); |
200 | buff[pos] = (byte) x; |
201 | return 4; |
202 | } else { |
203 | buff[pos++] = (byte) 0xf0; |
204 | buff[pos++] = (byte) (x >> 24); |
205 | buff[pos++] = (byte) (x >> 16); |
206 | buff[pos++] = (byte) (x >> 8); |
207 | buff[pos] = (byte) x; |
208 | return 5; |
209 | } |
210 | } |
211 | |
212 | /** |
213 | * Get a variable size integer length using Rice coding. |
214 | * Negative values need 5 bytes. |
215 | * |
216 | * @param x the value |
217 | * @return the number of bytes needed (0-5) |
218 | */ |
219 | public static int getVariableIntLength(int x) { |
220 | if (x < 0) { |
221 | return 5; |
222 | } else if (x < 0x80) { |
223 | return 1; |
224 | } else if (x < 0x4000) { |
225 | return 2; |
226 | } else if (x < 0x200000) { |
227 | return 3; |
228 | } else if (x < 0x10000000) { |
229 | return 4; |
230 | } else { |
231 | return 5; |
232 | } |
233 | } |
234 | |
235 | private static Compressor getCompressor(String algorithm) { |
236 | if (algorithm == null) { |
237 | algorithm = "LZF"; |
238 | } |
239 | int idx = algorithm.indexOf(' '); |
240 | String options = null; |
241 | if (idx > 0) { |
242 | options = algorithm.substring(idx + 1); |
243 | algorithm = algorithm.substring(0, idx); |
244 | } |
245 | int a = getCompressAlgorithm(algorithm); |
246 | Compressor compress = getCompressor(a); |
247 | compress.setOptions(options); |
248 | return compress; |
249 | } |
250 | |
251 | /** |
252 | * INTERNAL |
253 | */ |
254 | public static int getCompressAlgorithm(String algorithm) { |
255 | algorithm = StringUtils.toUpperEnglish(algorithm); |
256 | if ("NO".equals(algorithm)) { |
257 | return Compressor.NO; |
258 | } else if ("LZF".equals(algorithm)) { |
259 | return Compressor.LZF; |
260 | } else if ("DEFLATE".equals(algorithm)) { |
261 | return Compressor.DEFLATE; |
262 | } else { |
263 | throw DbException.get( |
264 | ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, |
265 | algorithm); |
266 | } |
267 | } |
268 | |
269 | private static Compressor getCompressor(int algorithm) { |
270 | switch (algorithm) { |
271 | case Compressor.NO: |
272 | return new CompressNo(); |
273 | case Compressor.LZF: |
274 | return new CompressLZF(); |
275 | case Compressor.DEFLATE: |
276 | return new CompressDeflate(); |
277 | default: |
278 | throw DbException.get( |
279 | ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, |
280 | "" + algorithm); |
281 | } |
282 | } |
283 | |
284 | /** |
285 | * INTERNAL |
286 | */ |
287 | public static OutputStream wrapOutputStream(OutputStream out, |
288 | String compressionAlgorithm, String entryName) { |
289 | try { |
290 | if ("GZIP".equals(compressionAlgorithm)) { |
291 | out = new GZIPOutputStream(out); |
292 | } else if ("ZIP".equals(compressionAlgorithm)) { |
293 | ZipOutputStream z = new ZipOutputStream(out); |
294 | z.putNextEntry(new ZipEntry(entryName)); |
295 | out = z; |
296 | } else if ("DEFLATE".equals(compressionAlgorithm)) { |
297 | out = new DeflaterOutputStream(out); |
298 | } else if ("LZF".equals(compressionAlgorithm)) { |
299 | out = new LZFOutputStream(out); |
300 | } else if (compressionAlgorithm != null) { |
301 | throw DbException.get( |
302 | ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, |
303 | compressionAlgorithm); |
304 | } |
305 | return out; |
306 | } catch (IOException e) { |
307 | throw DbException.convertIOException(e, null); |
308 | } |
309 | } |
310 | |
311 | /** |
312 | * INTERNAL |
313 | */ |
314 | public static InputStream wrapInputStream(InputStream in, |
315 | String compressionAlgorithm, String entryName) { |
316 | try { |
317 | if ("GZIP".equals(compressionAlgorithm)) { |
318 | in = new GZIPInputStream(in); |
319 | } else if ("ZIP".equals(compressionAlgorithm)) { |
320 | ZipInputStream z = new ZipInputStream(in); |
321 | while (true) { |
322 | ZipEntry entry = z.getNextEntry(); |
323 | if (entry == null) { |
324 | return null; |
325 | } |
326 | if (entryName.equals(entry.getName())) { |
327 | break; |
328 | } |
329 | } |
330 | in = z; |
331 | } else if ("DEFLATE".equals(compressionAlgorithm)) { |
332 | in = new InflaterInputStream(in); |
333 | } else if ("LZF".equals(compressionAlgorithm)) { |
334 | in = new LZFInputStream(in); |
335 | } else if (compressionAlgorithm != null) { |
336 | throw DbException.get( |
337 | ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, |
338 | compressionAlgorithm); |
339 | } |
340 | return in; |
341 | } catch (IOException e) { |
342 | throw DbException.convertIOException(e, null); |
343 | } |
344 | } |
345 | |
346 | } |
347 | |