/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.xerial.snappy; | |
import java.io.IOException; | |
import java.io.UnsupportedEncodingException; | |
import java.net.URL; | |
import java.nio.ByteBuffer; | |
import java.nio.charset.Charset; | |
import java.util.Properties; | |
/** | |
* Snappy API for data compression/decompression | |
* | |
* @author leo | |
* | |
*/ | |
public class Snappy | |
{ | |
/** | |
* An instance of SnappyNativeAPI | |
*/ | |
private static SnappyNativeAPI impl = new SnappyNative(); | |
/** | |
* Copy bytes from source to destination | |
* | |
* @param src | |
* pointer to the source array | |
* @param offset | |
* byte offset in the source array | |
* @param byteLength | |
* the number of bytes to copy | |
* @param dest | |
* pointer to the destination array | |
* @param dest_offset | |
* byte offset in the destination array | |
* @throws IOException | |
*/ | |
public static void arrayCopy(Object src, int offset, int byteLength, Object dest, int dest_offset) | |
throws IOException { | |
((SnappyNativeAPI) impl).arrayCopy(src, offset, byteLength, dest, dest_offset); | |
} | |
/** | |
* High-level API for compressing the input byte array. This method performs | |
* array copy to generate the result. If you want to reduce the memory copy | |
* cost, use {@link #compress(byte[], int, int, byte[], int)} or | |
* {@link #compress(ByteBuffer, ByteBuffer)}. | |
* | |
* @param input | |
* the input data | |
* @return the compressed byte array | |
* @throws IOException | |
*/ | |
public static byte[] compress(byte[] input) throws IOException { | |
return rawCompress(input, input.length); | |
} | |
/** | |
* Compress the input buffer content in [inputOffset, | |
* ...inputOffset+inputLength) then output to the specified output buffer. | |
* | |
* @param input | |
* @param inputOffset | |
* @param inputLength | |
* @param output | |
* @param outputOffset | |
* @return byte size of the compressed data | |
* @throws IOException | |
* when failed to access the input/output buffer | |
*/ | |
public static int compress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) | |
throws IOException { | |
return rawCompress(input, inputOffset, inputLength, output, outputOffset); | |
} | |
/** | |
* Compress the content in the given input buffer. After the compression, | |
* you can retrieve the compressed data from the output buffer [pos() ... | |
* limit()) (compressed data size = limit() - pos() = remaining()) | |
* | |
* @param uncompressed | |
* buffer[pos() ... limit()) containing the input data | |
* @param compressed | |
* output of the compressed data. Uses range [pos()..]. | |
* @return byte size of the compressed data. | |
* | |
* @throws SnappyError | |
* when the input is not a direct buffer | |
*/ | |
public static int compress(ByteBuffer uncompressed, ByteBuffer compressed) throws IOException { | |
if (!uncompressed.isDirect()) | |
throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "input is not a direct buffer"); | |
if (!compressed.isDirect()) | |
throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "destination is not a direct buffer"); | |
// input: uncompressed[pos(), limit()) | |
// output: compressed | |
int uPos = uncompressed.position(); | |
int uLen = uncompressed.remaining(); | |
int compressedSize = ((SnappyNativeAPI) impl).rawCompress(uncompressed, uPos, uLen, compressed, | |
compressed.position()); | |
// pos limit | |
// [ ......BBBBBBB.........] | |
compressed.limit(compressed.position() + compressedSize); | |
return compressedSize; | |
} | |
/** | |
* Compress the input char array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(char[] input) { | |
return rawCompress(input, input.length * 2); // char uses 2 bytes | |
} | |
/** | |
* Compress the input double array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(double[] input) { | |
return rawCompress(input, input.length * 8); // double uses 8 bytes | |
} | |
/** | |
* Compress the input float array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(float[] input) { | |
return rawCompress(input, input.length * 4); // float uses 4 bytes | |
} | |
/** | |
* Compress the input int array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(int[] input) { | |
return rawCompress(input, input.length * 4); // int uses 4 bytes | |
} | |
/** | |
* Compress the input long array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(long[] input) { | |
return rawCompress(input, input.length * 8); // long uses 8 bytes | |
} | |
/** | |
* Compress the input short array | |
* | |
* @param input | |
* @return the compressed data | |
*/ | |
public static byte[] compress(short[] input) { | |
return rawCompress(input, input.length * 2); // short uses 2 bytes | |
} | |
/** | |
* Compress the input String | |
* | |
* @param s | |
* @return the compressed data | |
* @throws IOException | |
*/ | |
public static byte[] compress(String s) throws IOException { | |
try { | |
return compress(s, "UTF-8"); | |
} | |
catch (UnsupportedEncodingException e) { | |
throw new IllegalStateException("UTF-8 encoder is not found"); | |
} | |
} | |
/** | |
* Compress the input string using the given encoding | |
* | |
* @param s | |
* @param encoding | |
* @return the compressed data | |
* @throws UnsupportedEncodingException | |
* @throws IOException | |
*/ | |
public static byte[] compress(String s, String encoding) throws UnsupportedEncodingException, IOException { | |
byte[] data = s.getBytes(encoding); | |
return compress(data); | |
} | |
/** | |
* Compress the input string using the given encoding | |
* | |
* @param s | |
* @param encoding | |
* @return the compressed data | |
* @throws UnsupportedEncodingException | |
* @throws IOException | |
*/ | |
public static byte[] compress(String s, Charset encoding) throws IOException { | |
byte[] data = s.getBytes(encoding); | |
return compress(data); | |
} | |
/** | |
* Get the native library version of the snappy | |
* | |
* @return native library version | |
*/ | |
public static String getNativeLibraryVersion() { | |
URL versionFile = Snappy.class.getResource("/org/xerial/snappy/VERSION"); | |
String version = "unknown"; | |
try { | |
if (versionFile != null) { | |
Properties versionData = new Properties(); | |
versionData.load(versionFile.openStream()); | |
version = versionData.getProperty("version", version); | |
if (version.equals("unknown")) | |
version = versionData.getProperty("VERSION", version); | |
version = version.trim().replaceAll("[^0-9\\.]", ""); | |
} | |
} | |
catch (IOException e) { | |
e.printStackTrace(); | |
} | |
return version; | |
} | |
/** | |
* Returns true iff the contents of compressed buffer [offset, | |
* offset+length) can be uncompressed successfully. Does not return the | |
* uncompressed data. Takes time proportional to the input length, but is | |
* usually at least a factor of four faster than actual decompression. | |
*/ | |
public static boolean isValidCompressedBuffer(byte[] input, int offset, int length) throws IOException { | |
if (input == null) | |
throw new NullPointerException("input is null"); | |
return ((SnappyNativeAPI) impl).isValidCompressedBuffer(input, offset, length); | |
} | |
/** | |
* Returns true iff the contents of compressed buffer [offset, | |
* offset+length) can be uncompressed successfully. Does not return the | |
* uncompressed data. Takes time proportional to the input length, but is | |
* usually at least a factor of four faster than actual decompression. | |
*/ | |
public static boolean isValidCompressedBuffer(byte[] input) throws IOException { | |
return isValidCompressedBuffer(input, 0, input.length); | |
} | |
/** | |
* Returns true iff the contents of compressed buffer [pos() ... limit()) | |
* can be uncompressed successfully. Does not return the uncompressed data. | |
* Takes time proportional to the input length, but is usually at least a | |
* factor of four faster than actual decompression. | |
*/ | |
public static boolean isValidCompressedBuffer(ByteBuffer compressed) throws IOException { | |
return ((SnappyNativeAPI) impl).isValidCompressedBuffer(compressed, compressed.position(), | |
compressed.remaining()); | |
} | |
/** | |
* Get the maximum byte size needed for compressing data of the given byte | |
* size. | |
* | |
* @param byteSize | |
* byte size of the data to compress | |
* @return maximum byte size of the compressed data | |
*/ | |
public static int maxCompressedLength(int byteSize) { | |
return ((SnappyNativeAPI) impl).maxCompressedLength(byteSize); | |
} | |
/** | |
* Compress the input data and produce a byte array of the uncompressed data | |
* | |
* @param data | |
* input array. The input MUST be an array type | |
* @param byteSize | |
* the input byte size | |
* @return compressed data | |
*/ | |
public static byte[] rawCompress(Object data, int byteSize) { | |
byte[] buf = new byte[Snappy.maxCompressedLength(byteSize)]; | |
int compressedByteSize = ((SnappyNativeAPI) impl).rawCompress(data, 0, byteSize, buf, 0); | |
byte[] result = new byte[compressedByteSize]; | |
System.arraycopy(buf, 0, result, 0, compressedByteSize); | |
return result; | |
} | |
/** | |
* Compress the input buffer [offset,... ,offset+length) contents, then | |
* write the compressed data to the output buffer[offset, ...) | |
* | |
* @param input | |
* input array. This MUST be a primitive array type | |
* @param inputOffset | |
* byte offset at the output array | |
* @param inputLength | |
* byte length of the input data | |
* @param output | |
* output array. This MUST be a primitive array type | |
* @param outputOffset | |
* byte offset at the output array | |
* @return byte size of the compressed data | |
* @throws IOException | |
*/ | |
public static int rawCompress(Object input, int inputOffset, int inputLength, byte[] output, int outputOffset) | |
throws IOException { | |
if (input == null || output == null) | |
throw new NullPointerException("input or output is null"); | |
int compressedSize = ((SnappyNativeAPI) impl) | |
.rawCompress(input, inputOffset, inputLength, output, outputOffset); | |
return compressedSize; | |
} | |
/** | |
* Uncompress the content in the input buffer. The uncompressed data is | |
* written to the output buffer. | |
* | |
* Note that if you pass the wrong data or the range [inputOffset, | |
* inputOffset + inputLength) that cannot be uncompressed, your JVM might | |
* crash due to the access violation exception issued in the native code | |
* written in C++. To avoid this type of crash, use | |
* {@link #isValidCompressedBuffer(byte[], int, int)} first. | |
* | |
* @param input | |
* input byte array | |
* @param inputOffset | |
* byte offset in the input byte array | |
* @param inputLength | |
* byte length of the input data | |
* @param output | |
* output buffer, MUST be a primitive type array | |
* @param outputOffset | |
* byte offset in the output buffer | |
* @return the byte size of the uncompressed data | |
* @throws IOException | |
* when failed to uncompress the input data | |
*/ | |
public static int rawUncompress(byte[] input, int inputOffset, int inputLength, Object output, int outputOffset) | |
throws IOException { | |
if (input == null || output == null) | |
throw new NullPointerException("input or output is null"); | |
return ((SnappyNativeAPI) impl).rawUncompress(input, inputOffset, inputLength, output, outputOffset); | |
} | |
/** | |
* High-level API for uncompressing the input byte array. | |
* | |
* @param input | |
* @return the uncompressed byte array | |
* @throws IOException | |
*/ | |
public static byte[] uncompress(byte[] input) throws IOException { | |
byte[] result = new byte[Snappy.uncompressedLength(input)]; | |
int byteSize = Snappy.uncompress(input, 0, input.length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the content in the input buffer. The uncompressed data is | |
* written to the output buffer. | |
* | |
* Note that if you pass the wrong data or the range [inputOffset, | |
* inputOffset + inputLength) that cannot be uncompressed, your JVM might | |
* crash due to the access violation exception issued in the native code | |
* written in C++. To avoid this type of crash, use | |
* {@link #isValidCompressedBuffer(byte[], int, int)} first. | |
* | |
* @param input | |
* @param inputOffset | |
* @param inputLength | |
* @param output | |
* @param outputOffset | |
* @return the byte size of the uncompressed data | |
* @throws IOException | |
*/ | |
public static int uncompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset) | |
throws IOException { | |
return rawUncompress(input, inputOffset, inputLength, output, outputOffset); | |
} | |
/** | |
* Uncompress the content in the input buffer. The result is dumped to the | |
* specified output buffer. | |
* | |
* Note that if you pass the wrong data or the range [pos(), limit()) that | |
* cannot be uncompressed, your JVM might crash due to the access violation | |
* exception issued in the native code written in C++. To avoid this type of | |
* crash, use {@link #isValidCompressedBuffer(ByteBuffer)} first. | |
* | |
* | |
* @param compressed | |
* buffer[pos() ... limit()) containing the input data | |
* @param uncompressed | |
* output of the the uncompressed data. It uses buffer[pos()..] | |
* @return uncompressed data size | |
* | |
* @throws IOException | |
* when failed to uncompress the given input | |
* @throws SnappyError | |
* when the input is not a direct buffer | |
*/ | |
public static int uncompress(ByteBuffer compressed, ByteBuffer uncompressed) throws IOException { | |
if (!compressed.isDirect()) | |
throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "input is not a direct buffer"); | |
if (!uncompressed.isDirect()) | |
throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "destination is not a direct buffer"); | |
int cPos = compressed.position(); | |
int cLen = compressed.remaining(); | |
// pos limit | |
// [ ......UUUUUU.........] | |
int decompressedSize = ((SnappyNativeAPI) impl).rawUncompress(compressed, cPos, cLen, uncompressed, | |
uncompressed.position()); | |
uncompressed.limit(uncompressed.position() + decompressedSize); | |
return decompressedSize; | |
} | |
/** | |
* Uncompress the input data as char array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static char[] uncompressCharArray(byte[] input) throws IOException { | |
return uncompressCharArray(input, 0, input.length); | |
} | |
/** | |
* Uncompress the input[offset, .., offset+length) as a char array | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static char[] uncompressCharArray(byte[] input, int offset, int length) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, offset, length); | |
char[] result = new char[uncompressedLength / 2]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, offset, length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the input as a double array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static double[] uncompressDoubleArray(byte[] input) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, 0, input.length); | |
double[] result = new double[uncompressedLength / 8]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, 0, input.length, result, 0); | |
return result; | |
} | |
/** | |
* Get the uncompressed byte size of the given compressed input. This | |
* operation takes O(1) time. | |
* | |
* @param input | |
* @return uncompressed byte size of the the given input data | |
* @throws IOException | |
* when failed to uncompress the given input. The error code is | |
* {@link SnappyErrorCode#PARSING_ERROR} | |
*/ | |
public static int uncompressedLength(byte[] input) throws IOException { | |
return ((SnappyNativeAPI) impl).uncompressedLength(input, 0, input.length); | |
} | |
/** | |
* Get the uncompressed byte size of the given compressed input. This | |
* operation takes O(1) time. | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return uncompressed byte size of the the given input data | |
* @throws IOException | |
* when failed to uncompress the given input. The error code is | |
* {@link SnappyErrorCode#PARSING_ERROR} | |
*/ | |
public static int uncompressedLength(byte[] input, int offset, int length) throws IOException { | |
if (input == null) | |
throw new NullPointerException("input is null"); | |
return ((SnappyNativeAPI) impl).uncompressedLength(input, offset, length); | |
} | |
/** | |
* Get the uncompressed byte size of the given compressed input. This | |
* operation taks O(1) time. | |
* | |
* @param compressed | |
* input data [pos() ... limit()) | |
* @return uncompressed byte length of the given input | |
* @throws IOException | |
* when failed to uncompress the given input. The error code is | |
* {@link SnappyErrorCode#PARSING_ERROR} | |
* @throws SnappyError | |
* when the input is not a direct buffer | |
*/ | |
public static int uncompressedLength(ByteBuffer compressed) throws IOException { | |
if (!compressed.isDirect()) | |
throw new SnappyError(SnappyErrorCode.NOT_A_DIRECT_BUFFER, "input is not a direct buffer"); | |
return ((SnappyNativeAPI) impl).uncompressedLength(compressed, compressed.position(), compressed.remaining()); | |
} | |
/** | |
* Uncompress the input as a float array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static float[] uncompressFloatArray(byte[] input) throws IOException { | |
return uncompressFloatArray(input, 0, input.length); | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a float array | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static float[] uncompressFloatArray(byte[] input, int offset, int length) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, offset, length); | |
float[] result = new float[uncompressedLength / 4]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, offset, length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the input data as an int array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static int[] uncompressIntArray(byte[] input) throws IOException { | |
return uncompressIntArray(input, 0, input.length); | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as an int array | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static int[] uncompressIntArray(byte[] input, int offset, int length) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, offset, length); | |
int[] result = new int[uncompressedLength / 4]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, offset, length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the input data as a long array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static long[] uncompressLongArray(byte[] input) throws IOException { | |
return uncompressLongArray(input, 0, input.length); | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a long array | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static long[] uncompressLongArray(byte[] input, int offset, int length) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, offset, length); | |
long[] result = new long[uncompressedLength / 8]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, offset, length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the input as a short array | |
* | |
* @param input | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static short[] uncompressShortArray(byte[] input) throws IOException { | |
return uncompressShortArray(input, 0, input.length); | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a short array | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static short[] uncompressShortArray(byte[] input, int offset, int length) throws IOException { | |
int uncompressedLength = Snappy.uncompressedLength(input, offset, length); | |
short[] result = new short[uncompressedLength / 2]; | |
int byteSize = ((SnappyNativeAPI) impl).rawUncompress(input, offset, length, result, 0); | |
return result; | |
} | |
/** | |
* Uncompress the input as a String | |
* | |
* @param input | |
* @return the uncompressed dasta | |
* @throws IOException | |
*/ | |
public static String uncompressString(byte[] input) throws IOException { | |
try { | |
return uncompressString(input, "UTF-8"); | |
} | |
catch (UnsupportedEncodingException e) { | |
throw new IllegalStateException("UTF-8 decoder is not found"); | |
} | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a String | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static String uncompressString(byte[] input, int offset, int length) throws IOException { | |
try { | |
return uncompressString(input, offset, length, "UTF-8"); | |
} | |
catch (UnsupportedEncodingException e) { | |
throw new IllegalStateException("UTF-8 decoder is not found"); | |
} | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a String of the given | |
* encoding | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @param encoding | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static String uncompressString(byte[] input, int offset, int length, String encoding) throws IOException, | |
UnsupportedEncodingException { | |
byte[] uncompressed = new byte[uncompressedLength(input, offset, length)]; | |
int compressedSize = uncompress(input, offset, length, uncompressed, 0); | |
return new String(uncompressed, encoding); | |
} | |
/** | |
* Uncompress the input[offset, offset+length) as a String of the given | |
* encoding | |
* | |
* @param input | |
* @param offset | |
* @param length | |
* @param encoding | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static String uncompressString(byte[] input, int offset, int length, Charset encoding) throws IOException, | |
UnsupportedEncodingException { | |
byte[] uncompressed = new byte[uncompressedLength(input, offset, length)]; | |
int compressedSize = uncompress(input, offset, length, uncompressed, 0); | |
return new String(uncompressed, encoding); | |
} | |
/** | |
* Uncompress the input as a String of the given encoding | |
* | |
* @param input | |
* @param encoding | |
* @return the uncompressed data | |
* @throws IOException | |
* @throws UnsupportedEncodingException | |
*/ | |
public static String uncompressString(byte[] input, String encoding) throws IOException, | |
UnsupportedEncodingException { | |
byte[] uncompressed = uncompress(input); | |
return new String(uncompressed, encoding); | |
} | |
/** | |
* Uncompress the input as a String of the given encoding | |
* | |
* @param input | |
* @param encoding | |
* @return the uncompressed data | |
* @throws IOException | |
*/ | |
public static String uncompressString(byte[] input, Charset encoding) throws IOException, | |
UnsupportedEncodingException { | |
byte[] uncompressed = uncompress(input); | |
return new String(uncompressed, encoding); | |
} | |
} |