/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.tools;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.InflaterInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.h2.compress.CompressDeflate;
import org.h2.compress.CompressLZF;
import org.h2.compress.CompressNo;
import org.h2.compress.Compressor;
import org.h2.compress.LZFInputStream;
import org.h2.compress.LZFOutputStream;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
import org.h2.message.DbException;
import org.h2.util.Utils;
import org.h2.util.StringUtils;
/**
* A tool to losslessly compress data, and expand the compressed data again.
*/
public class CompressTool {
private static final int MAX_BUFFER_SIZE = 3 * Constants.IO_BUFFER_SIZE_COMPRESS;
private byte[] cachedBuffer;
private CompressTool() {
// don't allow construction
}
private byte[] getBuffer(int min) {
if (min > MAX_BUFFER_SIZE) {
return Utils.newBytes(min);
}
if (cachedBuffer == null || cachedBuffer.length < min) {
cachedBuffer = Utils.newBytes(min);
}
return cachedBuffer;
}
/**
* Get a new instance. Each instance uses a separate buffer, so multiple
* instances can be used concurrently. However each instance alone is not
* multithreading safe.
*
* @return a new instance
*/
public static CompressTool getInstance() {
return new CompressTool();
}
/**
* Compressed the data using the specified algorithm. If no algorithm is
* supplied, LZF is used
*
* @param in the byte array with the original data
* @param algorithm the algorithm (LZF, DEFLATE)
* @return the compressed data
*/
public byte[] compress(byte[] in, String algorithm) {
int len = in.length;
if (in.length < 5) {
algorithm = "NO";
}
Compressor compress = getCompressor(algorithm);
byte[] buff = getBuffer((len < 100 ? len + 100 : len) * 2);
int newLen = compress(in, in.length, compress, buff);
byte[] out = Utils.newBytes(newLen);
System.arraycopy(buff, 0, out, 0, newLen);
return out;
}
private static int compress(byte[] in, int len, Compressor compress, byte[] out) {
int newLen = 0;
out[0] = (byte) compress.getAlgorithm();
int start = 1 + writeVariableInt(out, 1, len);
newLen = compress.compress(in, len, out, start);
if (newLen > len + start || newLen <= 0) {
out[0] = Compressor.NO;
System.arraycopy(in, 0, out, start, len);
newLen = len + start;
}
return newLen;
}
/**
* Expands the compressed data.
*
* @param in the byte array with the compressed data
* @return the uncompressed data
*/
public byte[] expand(byte[] in) {
int algorithm = in[0];
Compressor compress = getCompressor(algorithm);
try {
int len = readVariableInt(in, 1);
int start = 1 + getVariableIntLength(len);
byte[] buff = Utils.newBytes(len);
compress.expand(in, start, in.length - start, buff, 0, len);
return buff;
} catch (Exception e) {
throw DbException.get(ErrorCode.COMPRESSION_ERROR, e);
}
}
/**
* INTERNAL
*/
public static void expand(byte[] in, byte[] out, int outPos) {
int algorithm = in[0];
Compressor compress = getCompressor(algorithm);
try {
int len = readVariableInt(in, 1);
int start = 1 + getVariableIntLength(len);
compress.expand(in, start, in.length - start, out, outPos, len);
} catch (Exception e) {
throw DbException.get(ErrorCode.COMPRESSION_ERROR, e);
}
}
/**
* Read a variable size integer using Rice coding.
*
* @param buff the buffer
* @param pos the position
* @return the integer
*/
public static int readVariableInt(byte[] buff, int pos) {
int x = buff[pos++] & 0xff;
if (x < 0x80) {
return x;
}
if (x < 0xc0) {
return ((x & 0x3f) << 8) + (buff[pos] & 0xff);
}
if (x < 0xe0) {
return ((x & 0x1f) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff);
}
if (x < 0xf0) {
return ((x & 0xf) << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8)
+ (buff[pos] & 0xff);
}
return ((buff[pos++] & 0xff) << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8)
+ (buff[pos] & 0xff);
}
/**
* Write a variable size integer using Rice coding.
* Negative values need 5 bytes.
*
* @param buff the buffer
* @param pos the position
* @param x the value
* @return the number of bytes written (0-5)
*/
public static int writeVariableInt(byte[] buff, int pos, int x) {
if (x < 0) {
buff[pos++] = (byte) 0xf0;
buff[pos++] = (byte) (x >> 24);
buff[pos++] = (byte) (x >> 16);
buff[pos++] = (byte) (x >> 8);
buff[pos] = (byte) x;
return 5;
} else if (x < 0x80) {
buff[pos] = (byte) x;
return 1;
} else if (x < 0x4000) {
buff[pos++] = (byte) (0x80 | (x >> 8));
buff[pos] = (byte) x;
return 2;
} else if (x < 0x200000) {
buff[pos++] = (byte) (0xc0 | (x >> 16));
buff[pos++] = (byte) (x >> 8);
buff[pos] = (byte) x;
return 3;
} else if (x < 0x10000000) {
buff[pos++] = (byte) (0xe0 | (x >> 24));
buff[pos++] = (byte) (x >> 16);
buff[pos++] = (byte) (x >> 8);
buff[pos] = (byte) x;
return 4;
} else {
buff[pos++] = (byte) 0xf0;
buff[pos++] = (byte) (x >> 24);
buff[pos++] = (byte) (x >> 16);
buff[pos++] = (byte) (x >> 8);
buff[pos] = (byte) x;
return 5;
}
}
/**
* Get a variable size integer length using Rice coding.
* Negative values need 5 bytes.
*
* @param x the value
* @return the number of bytes needed (0-5)
*/
public static int getVariableIntLength(int x) {
if (x < 0) {
return 5;
} else if (x < 0x80) {
return 1;
} else if (x < 0x4000) {
return 2;
} else if (x < 0x200000) {
return 3;
} else if (x < 0x10000000) {
return 4;
} else {
return 5;
}
}
private static Compressor getCompressor(String algorithm) {
if (algorithm == null) {
algorithm = "LZF";
}
int idx = algorithm.indexOf(' ');
String options = null;
if (idx > 0) {
options = algorithm.substring(idx + 1);
algorithm = algorithm.substring(0, idx);
}
int a = getCompressAlgorithm(algorithm);
Compressor compress = getCompressor(a);
compress.setOptions(options);
return compress;
}
/**
* INTERNAL
*/
public static int getCompressAlgorithm(String algorithm) {
algorithm = StringUtils.toUpperEnglish(algorithm);
if ("NO".equals(algorithm)) {
return Compressor.NO;
} else if ("LZF".equals(algorithm)) {
return Compressor.LZF;
} else if ("DEFLATE".equals(algorithm)) {
return Compressor.DEFLATE;
} else {
throw DbException.get(ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, algorithm);
}
}
private static Compressor getCompressor(int algorithm) {
switch (algorithm) {
case Compressor.NO:
return new CompressNo();
case Compressor.LZF:
return new CompressLZF();
case Compressor.DEFLATE:
return new CompressDeflate();
default:
throw DbException.get(ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, "" + algorithm);
}
}
/**
* INTERNAL
*/
public static OutputStream wrapOutputStream(OutputStream out, String compressionAlgorithm, String entryName) {
try {
if ("GZIP".equals(compressionAlgorithm)) {
out = new GZIPOutputStream(out);
} else if ("ZIP".equals(compressionAlgorithm)) {
ZipOutputStream z = new ZipOutputStream(out);
z.putNextEntry(new ZipEntry(entryName));
out = z;
} else if ("DEFLATE".equals(compressionAlgorithm)) {
out = new DeflaterOutputStream(out);
} else if ("LZF".equals(compressionAlgorithm)) {
out = new LZFOutputStream(out);
} else if (compressionAlgorithm != null) {
throw DbException.get(ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, compressionAlgorithm);
}
return out;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
/**
* INTERNAL
*/
public static InputStream wrapInputStream(InputStream in, String compressionAlgorithm, String entryName) {
try {
if ("GZIP".equals(compressionAlgorithm)) {
in = new GZIPInputStream(in);
} else if ("ZIP".equals(compressionAlgorithm)) {
ZipInputStream z = new ZipInputStream(in);
while (true) {
ZipEntry entry = z.getNextEntry();
if (entry == null) {
return null;
}
if (entryName.equals(entry.getName())) {
break;
}
}
in = z;
} else if ("DEFLATE".equals(compressionAlgorithm)) {
in = new InflaterInputStream(in);
} else if ("LZF".equals(compressionAlgorithm)) {
in = new LZFInputStream(in);
} else if (compressionAlgorithm != null) {
throw DbException.get(ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, compressionAlgorithm);
}
return in;
} catch (IOException e) {
throw DbException.convertIOException(e, null);
}
}
}