/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 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.api.ErrorCode;
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.engine.Constants;
import org.h2.message.DbException;
import org.h2.util.Bits;
import org.h2.util.StringUtils;
import org.h2.util.Utils;

A tool to losslessly compress data, and expand the compressed data again.
/** * 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.
Returns:a new instance
/** * 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
Params:
  • in – the byte array with the original data
  • algorithm – the algorithm (LZF, DEFLATE)
Returns:the compressed data
/** * 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); return Utils.copyBytes(buff, newLen); } 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.
Params:
  • in – the byte array with the compressed data
Returns:the uncompressed data
/** * 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
/** * 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.
Params:
  • buff – the buffer
  • pos – the position
Returns:the integer
/** * 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 Bits.readInt(buff, pos); }
Write a variable size integer using Rice coding. Negative values need 5 bytes.
Params:
  • buff – the buffer
  • pos – the position
  • x – the value
Returns:the number of bytes written (0-5)
/** * 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; Bits.writeInt(buff, pos, 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 < 0x20_0000) { buff[pos++] = (byte) (0xc0 | (x >> 16)); buff[pos++] = (byte) (x >> 8); buff[pos] = (byte) x; return 3; } else if (x < 0x1000_0000) { Bits.writeInt(buff, pos, x | 0xe000_0000); return 4; } else { buff[pos++] = (byte) 0xf0; Bits.writeInt(buff, pos, x); return 5; } }
Get a variable size integer length using Rice coding. Negative values need 5 bytes.
Params:
  • x – the value
Returns:the number of bytes needed (0-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 < 0x20_0000) { return 3; } else if (x < 0x1000_0000) { 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
/** * 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, Integer.toString(algorithm)); } }
INTERNAL
/** * 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
/** * 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); } } }