001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.tools;
007:
008: import java.io.IOException;
009: import java.io.InputStream;
010: import java.io.OutputStream;
011: import java.sql.SQLException;
012: import java.util.zip.DeflaterOutputStream;
013: import java.util.zip.GZIPInputStream;
014: import java.util.zip.GZIPOutputStream;
015: import java.util.zip.InflaterInputStream;
016: import java.util.zip.ZipEntry;
017: import java.util.zip.ZipInputStream;
018: import java.util.zip.ZipOutputStream;
019:
020: import org.h2.compress.CompressDeflate;
021: import org.h2.compress.CompressLZF;
022: import org.h2.compress.CompressNo;
023: import org.h2.compress.Compressor;
024: import org.h2.compress.LZFInputStream;
025: import org.h2.compress.LZFOutputStream;
026: import org.h2.constant.ErrorCode;
027: import org.h2.message.Message;
028: import org.h2.util.StringUtils;
029:
030: /**
031: * A tool to losslessly compress data, and expand the compressed data again.
032: */
033: public class CompressTool {
034:
035: private static CompressTool instance = new CompressTool();
036: private static byte[] buffer;
037: private static final int MAX_BUFFER_SIZE = 64 * 1024 * 1024;
038:
039: private static byte[] getBuffer(int min) {
040: if (min > MAX_BUFFER_SIZE) {
041: return new byte[min];
042: }
043: if (buffer == null || buffer.length < min) {
044: buffer = new byte[min];
045: }
046: return buffer;
047: }
048:
049: private CompressTool() {
050: }
051:
052: /**
053: * Get the singleton.
054: *
055: * @return the singleton
056: */
057: public static CompressTool getInstance() {
058: return instance;
059: }
060:
061: /**
062: * Compressed the data using the specified algorithm. If no algorithm is
063: * supplied, LZF is used
064: *
065: * @param in the byte array with the original data
066: * @param algorithm the algorithm (LZF, DEFLATE)
067: * @return the compressed data
068: * @throws SQLException if a error occurs
069: */
070: public byte[] compress(byte[] in, String algorithm)
071: throws SQLException {
072: int len = in.length;
073: if (in.length < 5) {
074: algorithm = "NO";
075: }
076: Compressor compress = getCompressor(algorithm);
077: byte[] buff = getBuffer((len < 100 ? len + 100 : len) * 2);
078: int newLen = compress(in, in.length, compress, buff);
079: byte[] out = new byte[newLen];
080: System.arraycopy(buff, 0, out, 0, newLen);
081: return out;
082: }
083:
084: /**
085: * INTERNAL
086: */
087: public synchronized int compress(byte[] in, int len,
088: Compressor compress, byte[] out) {
089: int newLen = 0;
090: out[0] = (byte) compress.getAlgorithm();
091: int start = 1 + writeInt(out, 1, len);
092: newLen = compress.compress(in, len, out, start);
093: if (newLen > len + start || newLen <= 0) {
094: out[0] = Compressor.NO;
095: System.arraycopy(in, 0, out, start, len);
096: newLen = len + start;
097: }
098: return newLen;
099: }
100:
101: /**
102: * Expands the compressed data.
103: *
104: * @param in the byte array with the compressed data
105: * @return the uncompressed data
106: * @throws SQLException if a error occurs
107: */
108: public byte[] expand(byte[] in) throws SQLException {
109: int algorithm = in[0];
110: Compressor compress = getCompressor(algorithm);
111: try {
112: int len = readInt(in, 1);
113: int start = 1 + getLength(len);
114: byte[] buff = new byte[len];
115: compress.expand(in, start, in.length - start, buff, 0, len);
116: return buff;
117: } catch (Throwable e) {
118: throw Message.getSQLException(ErrorCode.COMPRESSION_ERROR,
119: null, e);
120: }
121: }
122:
123: /**
124: * INTERNAL
125: */
126: public void expand(byte[] in, byte[] out, int outPos)
127: throws SQLException {
128: int algorithm = in[0];
129: Compressor compress = getCompressor(algorithm);
130: try {
131: int len = readInt(in, 1);
132: int start = 1 + getLength(len);
133: compress.expand(in, start, in.length - start, out, outPos,
134: len);
135: } catch (Throwable e) {
136: throw Message.getSQLException(ErrorCode.COMPRESSION_ERROR,
137: null, e);
138: }
139: }
140:
141: private int readInt(byte[] buff, int pos) {
142: int x = buff[pos++] & 0xff;
143: if (x < 0x80) {
144: return x;
145: }
146: if (x < 0xc0) {
147: return ((x & 0x3f) << 8) + (buff[pos++] & 0xff);
148: }
149: if (x < 0xe0) {
150: return ((x & 0x1f) << 16) + ((buff[pos++] & 0xff) << 8)
151: + (buff[pos++] & 0xff);
152: }
153: if (x < 0xf0) {
154: return ((x & 0xf) << 24) + ((buff[pos++] & 0xff) << 16)
155: + ((buff[pos++] & 0xff) << 8)
156: + (buff[pos++] & 0xff);
157: }
158: return ((buff[pos++] & 0xff) << 24)
159: + ((buff[pos++] & 0xff) << 16)
160: + ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff);
161: }
162:
163: private int writeInt(byte[] buff, int pos, int x) {
164: if (x < 0) {
165: buff[pos++] = (byte) 0xf0;
166: buff[pos++] = (byte) (x >> 24);
167: buff[pos++] = (byte) (x >> 16);
168: buff[pos++] = (byte) (x >> 8);
169: buff[pos++] = (byte) x;
170: return 5;
171: } else if (x < 0x80) {
172: buff[pos++] = (byte) x;
173: return 1;
174: } else if (x < 0x4000) {
175: buff[pos++] = (byte) (0x80 | (x >> 8));
176: buff[pos++] = (byte) x;
177: return 2;
178: } else if (x < 0x200000) {
179: buff[pos++] = (byte) (0xc0 | (x >> 16));
180: buff[pos++] = (byte) (x >> 8);
181: buff[pos++] = (byte) x;
182: return 3;
183: } else if (x < 0x10000000) {
184: buff[pos++] = (byte) (0xe0 | (x >> 24));
185: buff[pos++] = (byte) (x >> 16);
186: buff[pos++] = (byte) (x >> 8);
187: buff[pos++] = (byte) x;
188: return 4;
189: } else {
190: buff[pos++] = (byte) 0xf0;
191: buff[pos++] = (byte) (x >> 24);
192: buff[pos++] = (byte) (x >> 16);
193: buff[pos++] = (byte) (x >> 8);
194: buff[pos++] = (byte) x;
195: return 5;
196: }
197: }
198:
199: private int getLength(int x) {
200: if (x < 0) {
201: return 5;
202: } else if (x < 0x80) {
203: return 1;
204: } else if (x < 0x4000) {
205: return 2;
206: } else if (x < 0x200000) {
207: return 3;
208: } else if (x < 0x10000000) {
209: return 4;
210: } else {
211: return 5;
212: }
213: }
214:
215: private Compressor getCompressor(String algorithm)
216: throws SQLException {
217: if (algorithm == null) {
218: algorithm = "LZF";
219: }
220: int idx = algorithm.indexOf(' ');
221: String options = null;
222: if (idx > 0) {
223: options = algorithm.substring(idx + 1);
224: algorithm = algorithm.substring(0, idx);
225: }
226: int a = getCompressAlgorithm(algorithm);
227: Compressor compress = getCompressor(a);
228: compress.setOptions(options);
229: return compress;
230: }
231:
232: /**
233: * INTERNAL
234: */
235: public int getCompressAlgorithm(String algorithm)
236: throws SQLException {
237: algorithm = StringUtils.toUpperEnglish(algorithm);
238: if ("NO".equals(algorithm)) {
239: return Compressor.NO;
240: } else if ("LZF".equals(algorithm)) {
241: return Compressor.LZF;
242: } else if ("DEFLATE".equals(algorithm)) {
243: return Compressor.DEFLATE;
244: } else {
245: throw Message.getSQLException(
246: ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1,
247: algorithm);
248: }
249: }
250:
251: private Compressor getCompressor(int algorithm) throws SQLException {
252: switch (algorithm) {
253: case Compressor.NO:
254: return new CompressNo();
255: case Compressor.LZF:
256: return new CompressLZF();
257: case Compressor.DEFLATE:
258: return new CompressDeflate();
259: default:
260: throw Message.getSQLException(
261: ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, ""
262: + algorithm);
263: }
264: }
265:
266: /**
267: * INTERNAL
268: */
269: public static OutputStream wrapOutputStream(OutputStream out,
270: String compressionAlgorithm, String entryName)
271: throws SQLException {
272: try {
273: if ("GZIP".equals(compressionAlgorithm)) {
274: out = new GZIPOutputStream(out);
275: } else if ("ZIP".equals(compressionAlgorithm)) {
276: ZipOutputStream z = new ZipOutputStream(out);
277: z.putNextEntry(new ZipEntry(entryName));
278: out = z;
279: } else if ("DEFLATE".equals(compressionAlgorithm)) {
280: out = new DeflaterOutputStream(out);
281: } else if ("LZF".equals(compressionAlgorithm)) {
282: out = new LZFOutputStream(out);
283: } else if (compressionAlgorithm != null) {
284: throw Message.getSQLException(
285: ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1,
286: compressionAlgorithm);
287: }
288: return out;
289: } catch (IOException e) {
290: throw Message.convertIOException(e, null);
291: }
292: }
293:
294: /**
295: * INTERNAL
296: */
297: public static InputStream wrapInputStream(InputStream in,
298: String compressionAlgorithm, String entryName)
299: throws SQLException {
300: try {
301: if ("GZIP".equals(compressionAlgorithm)) {
302: in = new GZIPInputStream(in);
303: } else if ("ZIP".equals(compressionAlgorithm)) {
304: ZipInputStream z = new ZipInputStream(in);
305: while (true) {
306: ZipEntry entry = z.getNextEntry();
307: if (entry == null) {
308: return null;
309: }
310: if (entryName.equals(entry.getName())) {
311: break;
312: }
313: }
314: in = z;
315: } else if ("DEFLATE".equals(compressionAlgorithm)) {
316: in = new InflaterInputStream(in);
317: } else if ("LZF".equals(compressionAlgorithm)) {
318: in = new LZFInputStream(in);
319: } else if (compressionAlgorithm != null) {
320: throw Message.getSQLException(
321: ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1,
322: compressionAlgorithm);
323: }
324: return in;
325: } catch (IOException e) {
326: throw Message.convertIOException(e, null);
327: }
328: }
329:
330: }
|