package cassandra.protocol.internal;
import cassandra.CassandraOptions;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.jpountz.lz4.LZ4Exception;
import net.jpountz.lz4.LZ4Factory;
import org.xerial.snappy.Snappy;
import java.io.IOException;
public interface Compressor {
ByteBuf compress(ByteBuf bytes) throws IOException;
ByteBuf decompress(ByteBuf bytes) throws IOException;
public static class Factory {
public static boolean canUse(CassandraOptions.Compression compression) {
switch (compression) {
case SNAPPY:
return SnappyCompressor.INSTANCE != null;
case LZ4:
return LZ4Compressor.INSTANCE != null;
default:
return false;
}
}
public static Compressor compressor(CassandraOptions.Compression compression) {
if (compression == CassandraOptions.Compression.SNAPPY && SnappyCompressor.INSTANCE != null) {
return SnappyCompressor.INSTANCE;
} else if (compression == CassandraOptions.Compression.LZ4 && LZ4Compressor.INSTANCE != null) {
return LZ4Compressor.INSTANCE;
} else {
return NoopCompressor.INSTANCE;
}
}
}
public static class SnappyCompressor implements Compressor {
public static final SnappyCompressor INSTANCE;
static {
SnappyCompressor instance;
try {
instance = new SnappyCompressor();
} catch (NoClassDefFoundError e) {
instance = null;
} catch (Throwable cause) {
instance = null;
}
INSTANCE = instance;
}
@Override
public ByteBuf compress(ByteBuf bytes) throws IOException {
byte[] input = new byte[bytes.readableBytes()];
bytes.readBytes(input);
byte[] output = new byte[Snappy.maxCompressedLength(input.length)];
int written = Snappy.compress(input, 0, input.length, output, 0);
return Unpooled.wrappedBuffer(output, 0, written);
}
@Override
public ByteBuf decompress(ByteBuf bytes) throws IOException {
byte[] input = new byte[bytes.readableBytes()];
bytes.readBytes(input);
if (!Snappy.isValidCompressedBuffer(input, 0, input.length)) {
throw new IllegalStateException("Provided message does not appear to be Snappy compressed");
}
byte[] output = new byte[Snappy.uncompressedLength(input)];
int size = Snappy.uncompress(input, 0, input.length, output, 0);
return Unpooled.wrappedBuffer(output, 0, size);
}
}
public static class LZ4Compressor implements Compressor {
public static final LZ4Compressor INSTANCE;
static {
LZ4Compressor instance;
try {
instance = new LZ4Compressor();
} catch (NoClassDefFoundError e) {
instance = null;
} catch (Throwable cause) {
instance = null;
}
INSTANCE = instance;
}
private static final int INTEGER_BYTES = 4;
private final net.jpountz.lz4.LZ4Compressor compressor;
private final net.jpountz.lz4.LZ4FastDecompressor decompressor;
private LZ4Compressor() {
LZ4Factory factory = LZ4Factory.fastestInstance();
compressor = factory.fastCompressor();
decompressor = factory.fastDecompressor();
}
@Override
public ByteBuf compress(ByteBuf bytes) throws IOException {
byte[] input = new byte[bytes.readableBytes()];
bytes.readBytes(input);
int maxCompressedLength = compressor.maxCompressedLength(input.length);
byte[] output = new byte[INTEGER_BYTES + maxCompressedLength];
output[0] = (byte)(input.length >>> 24);
output[1] = (byte)(input.length >>> 16);
output[2] = (byte)(input.length >>> 8);
output[3] = (byte)(input.length);
try {
int written = compressor.compress(input, 0, input.length, output, INTEGER_BYTES, maxCompressedLength);
return Unpooled.wrappedBuffer(output, 0, INTEGER_BYTES + written);
} catch (LZ4Exception e) {
throw new IOException(e);
}
}
@Override
public ByteBuf decompress(ByteBuf bytes) throws IOException {
byte[] input = new byte[bytes.readableBytes()];
bytes.readBytes(input);
int uncompressedLength = ((input[0] & 0xFF) << 24)
| ((input[1] & 0xFF) << 16)
| ((input[2] & 0xFF) << 8)
| ((input[3] & 0xFF));
byte[] output = new byte[uncompressedLength];
try {
int read = decompressor.decompress(input, INTEGER_BYTES, output, 0, uncompressedLength);
if (read != input.length - INTEGER_BYTES) {
throw new IOException("Compressed lengths mismatch");
}
return Unpooled.wrappedBuffer(output);
} catch (LZ4Exception e) {
throw new IOException(e);
}
}
}
public static class NoopCompressor implements Compressor {
public static final NoopCompressor INSTANCE = new NoopCompressor();
@Override
public ByteBuf compress(ByteBuf bytes) throws IOException {
return bytes;
}
@Override
public ByteBuf decompress(ByteBuf bytes) throws IOException {
return bytes;
}
}
}