fuzz-net.jpountz.fuzz.XXHash32Test#safe_array
diff --git a/src/java/net/jpountz/lz4/AbstractLZ4JNIFastResetCompressor.java b/src/java/net/jpountz/lz4/AbstractLZ4JNIFastResetCompressor.java
new file mode 100644
index 00000000..e57ddc47
--- /dev/null
+++ b/src/java/net/jpountz/lz4/AbstractLZ4JNIFastResetCompressor.java
@@ -0,0 +1,182 @@
+package net.jpountz.lz4;
+
+/*
+ * Copyright 2020 Adrien Grand and the lz4-java contributors.
+ *
+ * Licensed 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.
+ */
+
+import net.jpountz.util.ByteBufferUtils;
+import net.jpountz.util.SafeUtils;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.ReentrantLock;
+
+abstract class AbstractLZ4JNIFastResetCompressor extends LZ4Compressor implements AutoCloseable {
+
+ private static final String IN_USE_ERROR = "This compressor is not thread-safe and is already in use";
+ private static final String CLOSED_ERROR = "Compressor has been closed";
+ private static final String UNSUPPORTED_BUFFER_ERROR = "ByteBuffer must be direct or array-backed";
+
+ private final ReentrantLock lock = new ReentrantLock();
+ private long statePtr;
+
+ AbstractLZ4JNIFastResetCompressor(long statePtr, String allocationFailureMessage) {
+ if (statePtr == 0) {
+ throw new LZ4Exception(allocationFailureMessage);
+ }
+ this.statePtr = statePtr;
+ }
+
+ /**
+ * Compresses {@code src[srcOff:srcOff+srcLen]} into
+ * {@code dest[destOff:destOff+maxDestLen]}.
+ *
+ * @param src source data
+ * @param srcOff the start offset in src
+ * @param srcLen the number of bytes to compress
+ * @param dest destination buffer
+ * @param destOff the start offset in dest
+ * @param maxDestLen the maximum number of bytes to write in dest
+ * @return the compressed size
+ * @throws LZ4Exception if maxDestLen is too small
+ * @throws IllegalStateException if the compressor has been closed or is already in use
+ */
+ @Override
+ public final int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) {
+ if (!lock.tryLock()) {
+ throw new IllegalStateException(IN_USE_ERROR);
+ }
+
+ try {
+ long ptr = checkOpen();
+ SafeUtils.checkRange(src, srcOff, srcLen);
+ SafeUtils.checkRange(dest, destOff, maxDestLen);
+
+ final int result = compressNative(
+ ptr, src, null, srcOff, srcLen,
+ dest, null, destOff, maxDestLen);
+ return checkResult(result);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Compresses {@code src[srcOff:srcOff+srcLen]} into
+ * {@code dest[destOff:destOff+maxDestLen]}.
+ *
+ * Both buffers must be either direct or array-backed.
+ * {@link ByteBuffer} positions remain unchanged.
+ *
+ * @param src source data
+ * @param srcOff the start offset in src
+ * @param srcLen the number of bytes to compress
+ * @param dest destination buffer
+ * @param destOff the start offset in dest
+ * @param maxDestLen the maximum number of bytes to write in dest
+ * @return the compressed size
+ * @throws LZ4Exception if maxDestLen is too small
+ * @throws IllegalArgumentException if src or dest is neither array-backed nor direct
+ * @throws IllegalStateException if the compressor has been closed or is already in use
+ */
+ @Override
+ public final int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) {
+ if (!lock.tryLock()) {
+ throw new IllegalStateException(IN_USE_ERROR);
+ }
+
+ try {
+ long ptr = checkOpen();
+ checkByteBuffer(src);
+ checkByteBuffer(dest);
+ ByteBufferUtils.checkNotReadOnly(dest);
+ ByteBufferUtils.checkRange(src, srcOff, srcLen);
+ ByteBufferUtils.checkRange(dest, destOff, maxDestLen);
+
+ byte[] srcArr = src.hasArray() ? src.array() : null;
+ byte[] destArr = dest.hasArray() ? dest.array() : null;
+ ByteBuffer srcBuf = srcArr == null ? src : null;
+ ByteBuffer destBuf = destArr == null ? dest : null;
+ int srcBufferOff = srcOff + (srcArr != null ? src.arrayOffset() : 0);
+ int destBufferOff = destOff + (destArr != null ? dest.arrayOffset() : 0);
+
+ final int result = compressNative(
+ ptr, srcArr, srcBuf, srcBufferOff, srcLen,
+ destArr, destBuf, destBufferOff, maxDestLen);
+ return checkResult(result);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public final boolean isClosed() {
+ lock.lock();
+ try {
+ return statePtr == 0;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Closes this compressor and releases native resources.
+ * After calling this method, all compress methods will throw {@link IllegalStateException}.
+ *
+ * @throws IllegalStateException if the compressor is in use by another thread
+ */
+ @Override
+ public final void close() {
+ if (!lock.tryLock()) {
+ throw new IllegalStateException(IN_USE_ERROR);
+ }
+
+ try {
+ long ptr = statePtr;
+ statePtr = 0;
+ if (ptr != 0) {
+ freeState(ptr);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private long checkOpen() {
+ if (statePtr == 0) {
+ throw new IllegalStateException(CLOSED_ERROR);
+ }
+ return statePtr;
+ }
+
+ private static int checkResult(int result) {
+ if (result <= 0) {
+ throw new LZ4Exception("maxDestLen is too small");
+ }
+ return result;
+ }
+
+ private static void checkByteBuffer(ByteBuffer buffer) {
+ if (!(buffer.hasArray() || buffer.isDirect())) {
+ throw new IllegalArgumentException(UNSUPPORTED_BUFFER_ERROR);
+ }
+ }
+
+ protected abstract int compressNative(long ptr, byte[] srcArr, ByteBuffer srcBuf, int srcOff, int srcLen,
+ byte[] destArr, ByteBuffer destBuf, int destOff, int maxDestLen);
+
+ protected abstract void freeState(long ptr);
+}
+
+
+
diff --git a/src/java/net/jpountz/lz4/LZ4Constants.java b/src/java/net/jpountz/lz4/LZ4Constants.java
index 3f33b37c..6bab187d 100644
--- a/src/java/net/jpountz/lz4/LZ4Constants.java
+++ b/src/java/net/jpountz/lz4/LZ4Constants.java
@@ -22,6 +22,9 @@ enum LZ4Constants {
static final int DEFAULT_COMPRESSION_LEVEL = 8 + 1;
static final int MAX_COMPRESSION_LEVEL = 16 + 1;
+ static final int DEFAULT_ACCELERATION = 1;
+ static final int MIN_ACCELERATION = 1;
+ static final int MAX_ACCELERATION = 65537;
static final int MEMORY_USAGE = 14;
static final int NOT_COMPRESSIBLE_DETECTION_LEVEL = 6;
diff --git a/src/java/net/jpountz/lz4/LZ4Factory.java b/src/java/net/jpountz/lz4/LZ4Factory.java
index 6110b2cc..00d74b5c 100644
--- a/src/java/net/jpountz/lz4/LZ4Factory.java
+++ b/src/java/net/jpountz/lz4/LZ4Factory.java
@@ -26,6 +26,8 @@
import static net.jpountz.lz4.LZ4Constants.DEFAULT_COMPRESSION_LEVEL;
import static net.jpountz.lz4.LZ4Constants.MAX_COMPRESSION_LEVEL;
+import static net.jpountz.lz4.LZ4Constants.MIN_ACCELERATION;
+import static net.jpountz.lz4.LZ4Constants.MAX_ACCELERATION;
/**
* Entry point for the LZ4 API.
@@ -319,6 +321,122 @@ public LZ4Compressor highCompressor(int compressionLevel) {
return highCompressors[compressionLevel];
}
+ /**
+ * Creates a new {@link LZ4JNIFastResetCompressor} with default acceleration (1).
+ *
+ * This compressor pre-allocates native state once and uses
+ * {@code LZ4_compress_fast_extState_fastReset} for each compression, avoiding
+ * the expensive full state initialization that the default {@link #fastCompressor()}
+ * performs on every call.
+ *
+ * Unlike {@link #fastCompressor()}, this compressor is NOT thread-safe and
+ * holds native resources that must be freed. Always use try-with-resources or
+ * call {@link LZ4JNIFastResetCompressor#close() close()} explicitly.
+ *
+ * Only available for the native instance.
+ *
+ * @return a new fast-reset compressor
+ * @throws UnsupportedOperationException if this is not the native instance
+ * @see LZ4JNIFastResetCompressor
+ * @see #fastCompressor()
+ * @see #fastResetCompressor(int)
+ */
+ public LZ4JNIFastResetCompressor fastResetCompressor() {
+ enforceNativeInstance("fastResetCompressor");
+ return new LZ4JNIFastResetCompressor();
+ }
+
+ /**
+ * Creates a new {@link LZ4JNIFastResetCompressor} with the specified acceleration.
+ *
+ * This compressor pre-allocates native state once and uses
+ * {@code LZ4_compress_fast_extState_fastReset} for each compression, avoiding
+ * the expensive full state initialization that the default {@link #fastCompressor()}
+ * performs on every call.
+ *
+ * Unlike {@link #fastCompressor()}, this compressor is NOT thread-safe and
+ * holds native resources that must be freed. Always use try-with-resources or
+ * call {@link LZ4JNIFastResetCompressor#close() close()} explicitly.
+ *
+ * Only available for the native instance.
+ *
+ *
Acceleration follows the upstream LZ4 contract:
+ * - It should be in range [1, 65537].
+ * - A value lower than 1 is treated as 1.
+ * - A value greater than 65537 is treated as 65537.
+ *
+ *
+ * @param acceleration acceleration factor (1 = default, higher = faster but less compression)
+ * @return a new fast-reset compressor
+ * @throws UnsupportedOperationException if this is not the native instance
+ * @see LZ4JNIFastResetCompressor
+ * @see #fastCompressor()
+ */
+ public LZ4JNIFastResetCompressor fastResetCompressor(int acceleration) {
+ enforceNativeInstance("fastResetCompressor");
+ if (acceleration < MIN_ACCELERATION) {
+ acceleration = MIN_ACCELERATION;
+ } else if (acceleration > MAX_ACCELERATION) {
+ acceleration = MAX_ACCELERATION;
+ }
+ return new LZ4JNIFastResetCompressor(acceleration);
+ }
+
+ /**
+ * Creates a new {@link LZ4JNIHCFastResetCompressor} with default compression level (9).
+ *
+ * This compressor pre-allocates native HC state once and uses
+ * {@code LZ4_compress_HC_extStateHC_fastReset} for each compression, avoiding
+ * the expensive full state initialization that the default {@link #highCompressor()}
+ * performs on every call.
+ *
+ * Unlike {@link #highCompressor()}, this compressor is NOT thread-safe and
+ * holds native resources that must be freed. Always use try-with-resources or
+ * call {@link LZ4JNIHCFastResetCompressor#close() close()} explicitly.
+ *
+ * Only available for the native instance.
+ *
+ * @return a new HC fast-reset compressor
+ * @throws UnsupportedOperationException if this is not the native instance
+ * @see LZ4JNIHCFastResetCompressor
+ * @see #highCompressor()
+ * @see #highFastResetCompressor(int)
+ */
+ public LZ4JNIHCFastResetCompressor highFastResetCompressor() {
+ enforceNativeInstance("highFastResetCompressor");
+ return new LZ4JNIHCFastResetCompressor();
+ }
+
+ /**
+ * Creates a new {@link LZ4JNIHCFastResetCompressor} with the specified compression level.
+ *
+ * This compressor pre-allocates native HC state once and uses
+ * {@code LZ4_compress_HC_extStateHC_fastReset} for each compression, avoiding
+ * the expensive full state initialization that the default {@link #highCompressor()}
+ * performs on every call.
+ *
+ * Unlike {@link #highCompressor()}, this compressor is NOT thread-safe and
+ * holds native resources that must be freed. Always use try-with-resources or
+ * call {@link LZ4JNIHCFastResetCompressor#close() close()} explicitly.
+ *
+ * Only available for the native instance.
+ *
+ * @param compressionLevel compression level (1-17, higher = better compression)
+ * @return a new HC fast-reset compressor
+ * @throws UnsupportedOperationException if this is not the native instance
+ * @see LZ4JNIHCFastResetCompressor
+ * @see #highCompressor(int)
+ */
+ public LZ4JNIHCFastResetCompressor highFastResetCompressor(int compressionLevel) {
+ enforceNativeInstance("highFastResetCompressor");
+ if (compressionLevel > MAX_COMPRESSION_LEVEL) {
+ compressionLevel = MAX_COMPRESSION_LEVEL;
+ } else if (compressionLevel < 1) {
+ compressionLevel = DEFAULT_COMPRESSION_LEVEL;
+ }
+ return new LZ4JNIHCFastResetCompressor(compressionLevel);
+ }
+
/**
* Returns a {@link LZ4FastDecompressor} instance.
* Use of this method is deprecated for the {@link #nativeInstance() native instance}.
@@ -375,4 +493,10 @@ public String toString() {
return getClass().getSimpleName() + ":" + impl;
}
+ private void enforceNativeInstance(String methodName) throws UnsupportedOperationException {
+ if (!"JNI".equals(impl)) {
+ throw new UnsupportedOperationException(
+ methodName + "() is only available for the native instance. Use LZ4Factory.nativeInstance() to get a native factory");
+ }
+ }
}
diff --git a/src/java/net/jpountz/lz4/LZ4JNI.java b/src/java/net/jpountz/lz4/LZ4JNI.java
index 0b5c1617..dfb99283 100644
--- a/src/java/net/jpountz/lz4/LZ4JNI.java
+++ b/src/java/net/jpountz/lz4/LZ4JNI.java
@@ -39,5 +39,89 @@ enum LZ4JNI {
static native int LZ4_decompress_safe(byte[] srcArray, ByteBuffer srcBuffer, int srcOff, int srcLen, byte[] destArray, ByteBuffer destBuffer, int destOff, int maxDestLen);
static native int LZ4_compressBound(int len);
+ /**
+ * Creates a new LZ4 stream object.
+ *
+ * The allocated native memory must be freed with {@link #LZ4_freeStream(long)} when no longer needed.
+ *
+ * @return pointer to the allocated LZ4_stream_t or 0 on failure
+ */
+ static native long LZ4_createStream();
+
+ /**
+ * Frees the native memory allocated for the LZ4 stream object.
+ *
+ * @param streamPtr pointer to LZ4_stream_t
+ * @return 0 on success
+ */
+ static native int LZ4_freeStream(long streamPtr);
+
+ /**
+ * Compresses using a pre-initialized state with fast-reset semantics.
+ *
+ * A variant of {@code LZ4_compress_fast_extState()} that avoids an expensive
+ * full state initialization on every call.
+ *
+ * The state must have been properly initialized once (e.g. via {@link #LZ4_createStream()}).
+ *
+ * @param statePtr pointer to a properly initialized LZ4_stream_t
+ * @param srcArray source data (byte array), or null if using srcBuffer
+ * @param srcBuffer source data (direct ByteBuffer), or null if using srcArray
+ * @param srcOff offset in source
+ * @param srcLen length to compress
+ * @param destArray destination buffer (byte array), or null if using destBuffer
+ * @param destBuffer destination buffer (direct ByteBuffer), or null if using destArray
+ * @param destOff offset in destination
+ * @param maxDestLen maximum bytes to write
+ * @param acceleration acceleration factor (1 = default, higher = faster but less compression)
+ * @return compressed size, or 0 on failure
+ */
+ static native int LZ4_compress_fast_extState_fastReset(long statePtr,
+ byte[] srcArray, ByteBuffer srcBuffer, int srcOff, int srcLen,
+ byte[] destArray, ByteBuffer destBuffer, int destOff, int maxDestLen,
+ int acceleration);
+
+ /**
+ * Creates a new LZ4 HC stream object.
+ *
+ * The allocated native memory must be freed with {@link #LZ4_freeStreamHC(long)} when no longer needed.
+ *
+ * @return pointer to the allocated LZ4_streamHC_t or 0 on failure
+ */
+ static native long LZ4_createStreamHC();
+
+ /**
+ * Frees the native memory allocated for the LZ4 HC stream object.
+ *
+ * @param streamPtr pointer to LZ4_streamHC_t
+ * @return 0 on success
+ */
+ static native int LZ4_freeStreamHC(long streamPtr);
+
+ /**
+ * Compresses using a pre-initialized HC state with fast-reset semantics.
+ *
+ * A variant of {@code LZ4_compress_HC_extStateHC()} that avoids an expensive
+ * full state initialization on every call.
+ *
+ * The state must have been properly initialized once (e.g. via {@link #LZ4_createStreamHC()}).
+ *
+ * @param statePtr pointer to a properly initialized LZ4_streamHC_t
+ * @param srcArray source data (byte array), or null if using srcBuffer
+ * @param srcBuffer source data (direct ByteBuffer), or null if using srcArray
+ * @param srcOff offset in source
+ * @param srcLen length to compress
+ * @param destArray destination buffer (byte array), or null if using destBuffer
+ * @param destBuffer destination buffer (direct ByteBuffer), or null if using destArray
+ * @param destOff offset in destination
+ * @param maxDestLen maximum bytes to write
+ * @param compressionLevel compression level (1-17, higher = better compression)
+ * @return compressed size, or 0 on failure
+ */
+ static native int LZ4_compress_HC_extStateHC_fastReset(long statePtr,
+ byte[] srcArray, ByteBuffer srcBuffer, int srcOff, int srcLen,
+ byte[] destArray, ByteBuffer destBuffer, int destOff, int maxDestLen,
+ int compressionLevel);
+
}
diff --git a/src/java/net/jpountz/lz4/LZ4JNIFastResetCompressor.java b/src/java/net/jpountz/lz4/LZ4JNIFastResetCompressor.java
new file mode 100644
index 00000000..d70251d0
--- /dev/null
+++ b/src/java/net/jpountz/lz4/LZ4JNIFastResetCompressor.java
@@ -0,0 +1,110 @@
+package net.jpountz.lz4;
+
+/*
+ * Copyright 2020 Adrien Grand and the lz4-java contributors.
+ *
+ * Licensed 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.
+ */
+
+import java.nio.ByteBuffer;
+
+import static net.jpountz.lz4.LZ4Constants.MIN_ACCELERATION;
+import static net.jpountz.lz4.LZ4Constants.MAX_ACCELERATION;
+
+/**
+ * An optimized LZ4 compressor that uses native {@code LZ4_compress_fast_extState_fastReset}.
+ *
+ * This compressor pre-allocates an {@code LZ4_stream_t} once and reuses it for every compression
+ * call through {@code LZ4_compress_fast_extState_fastReset}. This avoids the expensive full
+ * state initialization that {@link LZ4JNICompressor LZ4_compress_default()} performs on every call.
+ *
+ * Each compression call is independent, making the output identical to {@link LZ4JNICompressor}
+ * for the same acceleration level when operating on array-backed or direct buffers.
+ * {@link ByteBuffer} inputs must be array-backed or direct.
+ *
+ * Thread Safety: This class is NOT thread-safe. Each instance holds
+ * mutable native state and must be used by only one thread at a time. Concurrent use
+ * or close attempts fail fast with {@link IllegalStateException}.
+ *
+ * Resource Management: This class holds native memory that must be freed.
+ * Always use try-with-resources or explicitly call {@link #close()}.
+ *
+ *
Example usage:
+ * {@code
+ * LZ4Factory factory = LZ4Factory.nativeInstance();
+ * try (LZ4JNIFastResetCompressor compressor = factory.fastResetCompressor()) {
+ * byte[] compressed = new byte[compressor.maxCompressedLength(data.length)];
+ * int compressedLen = compressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ * // ... use compressed[0..compressedLen-1]
+ * }
+ * }
+ *
+ * @see LZ4Factory#fastResetCompressor()
+ * @see LZ4Factory#fastResetCompressor(int)
+ * @see LZ4JNICompressor
+ */
+public final class LZ4JNIFastResetCompressor extends AbstractLZ4JNIFastResetCompressor {
+
+ private final int acceleration;
+
+ /**
+ * Creates a new fast-reset compressor with default acceleration (1).
+ * Package-private: use {@link LZ4Factory#fastResetCompressor()}.
+ */
+ LZ4JNIFastResetCompressor() {
+ this(LZ4Constants.DEFAULT_ACCELERATION);
+ }
+
+ /**
+ * Creates a new fast-reset compressor with specified acceleration.
+ * Package-private: use {@link LZ4Factory#fastResetCompressor(int)}.
+ *
+ * @param acceleration acceleration factor (1 = default, higher = faster but less compression)
+ */
+ LZ4JNIFastResetCompressor(int acceleration) {
+ super(LZ4JNI.LZ4_createStream(), "Failed to allocate LZ4 state");
+ if (acceleration < MIN_ACCELERATION) {
+ acceleration = MIN_ACCELERATION;
+ } else if (acceleration > MAX_ACCELERATION) {
+ acceleration = MAX_ACCELERATION;
+ }
+ this.acceleration = acceleration;
+ }
+
+ /**
+ * Returns the acceleration factor.
+ *
+ * @return acceleration factor (1 = default)
+ */
+ public int getAcceleration() {
+ return acceleration;
+ }
+
+ @Override
+ protected int compressNative(long ptr, byte[] srcArr, ByteBuffer srcBuf, int srcOff, int srcLen,
+ byte[] destArr, ByteBuffer destBuf, int destOff, int maxDestLen) {
+ return LZ4JNI.LZ4_compress_fast_extState_fastReset(
+ ptr, srcArr, srcBuf, srcOff, srcLen,
+ destArr, destBuf, destOff, maxDestLen, acceleration);
+ }
+
+ @Override
+ protected void freeState(long ptr) {
+ LZ4JNI.LZ4_freeStream(ptr);
+ }
+
+ @Override
+ public String toString() {
+ return "LZ4JNIFastResetCompressor[acceleration=" + acceleration + ", closed=" + isClosed() + "]";
+ }
+}
diff --git a/src/java/net/jpountz/lz4/LZ4JNIHCFastResetCompressor.java b/src/java/net/jpountz/lz4/LZ4JNIHCFastResetCompressor.java
new file mode 100644
index 00000000..b6c2cb34
--- /dev/null
+++ b/src/java/net/jpountz/lz4/LZ4JNIHCFastResetCompressor.java
@@ -0,0 +1,110 @@
+package net.jpountz.lz4;
+
+/*
+ * Copyright 2020 Adrien Grand and the lz4-java contributors.
+ *
+ * Licensed 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.
+ */
+
+import static net.jpountz.lz4.LZ4Constants.DEFAULT_COMPRESSION_LEVEL;
+import static net.jpountz.lz4.LZ4Constants.MAX_COMPRESSION_LEVEL;
+
+import java.nio.ByteBuffer;
+
+/**
+ * An optimized LZ4 HC compressor that uses native {@code LZ4_compress_HC_extStateHC_fastReset}.
+ *
+ * This compressor pre-allocates an {@code LZ4_streamHC_t} once and reuses it for every compression
+ * call through {@code LZ4_compress_HC_extStateHC_fastReset}. This avoids the expensive full
+ * state initialization that {@link LZ4HCJNICompressor LZ4_compress_HC()} performs on every call.
+ *
+ * Each compression call is independent, making the output identical to {@link LZ4HCJNICompressor}
+ * for the same compression level when operating on array-backed or direct buffers.
+ * {@link ByteBuffer} inputs must be array-backed or direct.
+ *
+ * Thread Safety: This class is NOT thread-safe. Each instance holds
+ * mutable native state and must be used by only one thread at a time. Concurrent use
+ * or close attempts fail fast with {@link IllegalStateException}.
+ *
+ * Resource Management: This class holds native memory that must be freed.
+ * Always use try-with-resources or explicitly call {@link #close()}.
+ *
+ *
Example usage:
+ * {@code
+ * LZ4Factory factory = LZ4Factory.nativeInstance();
+ * try (LZ4JNIHCFastResetCompressor compressor = factory.highFastResetCompressor()) {
+ * byte[] compressed = new byte[compressor.maxCompressedLength(data.length)];
+ * int compressedLen = compressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ * // ... use compressed[0..compressedLen-1]
+ * }
+ * }
+ *
+ * @see LZ4Factory#highFastResetCompressor()
+ * @see LZ4Factory#highFastResetCompressor(int)
+ * @see LZ4HCJNICompressor
+ */
+public final class LZ4JNIHCFastResetCompressor extends AbstractLZ4JNIFastResetCompressor {
+
+ private final int compressionLevel;
+
+ /**
+ * Creates a new HC fast-reset compressor with default compression level.
+ * Package-private: use {@link LZ4Factory#highFastResetCompressor()}.
+ */
+ LZ4JNIHCFastResetCompressor() {
+ this(DEFAULT_COMPRESSION_LEVEL);
+ }
+
+ /**
+ * Creates a new HC fast-reset compressor with specified compression level.
+ * Package-private: use {@link LZ4Factory#highFastResetCompressor(int)}.
+ *
+ * @param compressionLevel compression level (1-17, higher = better compression)
+ */
+ LZ4JNIHCFastResetCompressor(int compressionLevel) {
+ super(LZ4JNI.LZ4_createStreamHC(), "Failed to allocate LZ4 HC state");
+ if (compressionLevel > MAX_COMPRESSION_LEVEL) {
+ compressionLevel = MAX_COMPRESSION_LEVEL;
+ } else if (compressionLevel < 1) {
+ compressionLevel = DEFAULT_COMPRESSION_LEVEL;
+ }
+ this.compressionLevel = compressionLevel;
+ }
+
+ /**
+ * Returns the compression level.
+ *
+ * @return compression level (default = 9)
+ */
+ public int getCompressionLevel() {
+ return compressionLevel;
+ }
+
+ @Override
+ protected int compressNative(long ptr, byte[] srcArr, ByteBuffer srcBuf, int srcOff, int srcLen,
+ byte[] destArr, ByteBuffer destBuf, int destOff, int maxDestLen) {
+ return LZ4JNI.LZ4_compress_HC_extStateHC_fastReset(
+ ptr, srcArr, srcBuf, srcOff, srcLen,
+ destArr, destBuf, destOff, maxDestLen, compressionLevel);
+ }
+
+ @Override
+ protected void freeState(long ptr) {
+ LZ4JNI.LZ4_freeStreamHC(ptr);
+ }
+
+ @Override
+ public String toString() {
+ return "LZ4JNIHCFastResetCompressor[compressionLevel=" + compressionLevel + ", closed=" + isClosed() + "]";
+ }
+}
diff --git a/src/jni/net_jpountz_lz4_LZ4JNI.c b/src/jni/net_jpountz_lz4_LZ4JNI.c
index 3935b32a..82710eaf 100644
--- a/src/jni/net_jpountz_lz4_LZ4JNI.c
+++ b/src/jni/net_jpountz_lz4_LZ4JNI.c
@@ -14,11 +14,26 @@
* limitations under the License.
*/
+#define LZ4_STATIC_LINKING_ONLY // exposes LZ4_compress_fast_extState_fastReset
#include "lz4.h"
+#define LZ4_HC_STATIC_LINKING_ONLY // exposes LZ4_compress_HC_extStateHC_fastReset
#include "lz4hc.h"
#include
static jclass OutOfMemoryError;
+static jclass IllegalArgumentException;
+static jclass IllegalStateException;
+
+static int init_class_ref(JNIEnv *env, jclass *target, const char *className) {
+ jclass localClass = (*env)->FindClass(env, className);
+ if (localClass == NULL) {
+ return 0;
+ }
+
+ *target = (jclass) (*env)->NewGlobalRef(env, localClass);
+ (*env)->DeleteLocalRef(env, localClass);
+ return *target != NULL;
+}
/*
* Class: net_jpountz_lz4_LZ4
@@ -27,13 +42,53 @@ static jclass OutOfMemoryError;
*/
JNIEXPORT void JNICALL Java_net_jpountz_lz4_LZ4JNI_init
(JNIEnv *env, jclass cls) {
- OutOfMemoryError = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
+ if (!init_class_ref(env, &OutOfMemoryError, "java/lang/OutOfMemoryError")) {
+ return;
+ }
+ if (!init_class_ref(env, &IllegalArgumentException, "java/lang/IllegalArgumentException")) {
+ return;
+ }
+ if (!init_class_ref(env, &IllegalStateException, "java/lang/IllegalStateException")) {
+ return;
+ }
}
static void throw_OOM(JNIEnv *env) {
(*env)->ThrowNew(env, OutOfMemoryError, "Out of memory");
}
+static void throw_IAE(JNIEnv *env, const char* msg) {
+ (*env)->ThrowNew(env, IllegalArgumentException, msg);
+}
+
+static void throw_ISE(JNIEnv *env, const char* msg) {
+ (*env)->ThrowNew(env, IllegalStateException, msg);
+}
+
+/**
+ * Validates that offset and length are non-negative and don't overflow.
+ * Returns 1 if valid, 0 otherwise (and throws exception).
+ */
+static int validate_range(JNIEnv *env, jint off, jint len) {
+ if (off < 0) {
+ throw_IAE(env, "Offset must be non-negative");
+ return 0;
+ }
+
+ if (len < 0) {
+ throw_IAE(env, "Length must be non-negative");
+ return 0;
+ }
+
+ // Check for integer overflow: off + len
+ if (len > 0 && off > INT32_MAX - len) {
+ throw_IAE(env, "Offset + length would overflow");
+ return 0;
+ }
+
+ return 1;
+}
+
/*
* Class: net_jpountz_lz4_LZ4JNI
* Method: LZ4_compress_limitedOutput
@@ -237,3 +292,181 @@ JNIEXPORT jint JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1compressBound
return LZ4_compressBound(len);
}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_createStream
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1createStream
+ (JNIEnv *env, jclass cls) {
+
+ LZ4_stream_t* stream = LZ4_createStream();
+ return (jlong)(intptr_t)stream;
+}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_freeStream
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1freeStream
+ (JNIEnv *env, jclass cls, jlong streamPtr) {
+
+ if (streamPtr == 0) {
+ return 0;
+ }
+
+ LZ4_stream_t* stream = (LZ4_stream_t*)(intptr_t)streamPtr;
+ return LZ4_freeStream(stream);
+}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_compress_fast_extState_fastReset
+ * Signature: (J[BLjava/nio/ByteBuffer;II[BLjava/nio/ByteBuffer;III)I
+ */
+JNIEXPORT jint JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1compress_1fast_1extState_1fastReset
+ (JNIEnv *env, jclass cls, jlong statePtr,
+ jbyteArray srcArray, jobject srcBuffer, jint srcOff, jint srcLen,
+ jbyteArray destArray, jobject destBuffer, jint destOff, jint maxDestLen, jint acceleration) {
+
+ if (statePtr == 0) {
+ throw_ISE(env, "Compressor state has been freed");
+ return 0;
+ }
+
+ if (!validate_range(env, srcOff, srcLen) || !validate_range(env, destOff, maxDestLen)) {
+ return 0;
+ }
+
+ void* state = (void*)(intptr_t)statePtr;
+ char* in;
+ char* out;
+ jint compressed;
+
+ if (srcArray != NULL) {
+ in = (char*) (*env)->GetPrimitiveArrayCritical(env, srcArray, 0);
+ } else {
+ in = (char*) (*env)->GetDirectBufferAddress(env, srcBuffer);
+ }
+
+ if (in == NULL) {
+ throw_OOM(env);
+ return 0;
+ }
+
+ if (destArray != NULL) {
+ out = (char*) (*env)->GetPrimitiveArrayCritical(env, destArray, 0);
+ } else {
+ out = (char*) (*env)->GetDirectBufferAddress(env, destBuffer);
+ }
+
+ if (out == NULL) {
+ if (srcArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, srcArray, in, 0);
+ }
+ throw_OOM(env);
+ return 0;
+ }
+
+ compressed = LZ4_compress_fast_extState_fastReset(state, in + srcOff, out + destOff, srcLen, maxDestLen, acceleration);
+
+ if (srcArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, srcArray, in, 0);
+ }
+ if (destArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, destArray, out, 0);
+ }
+
+ return compressed;
+}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_createStreamHC
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1createStreamHC
+ (JNIEnv *env, jclass cls) {
+
+ LZ4_streamHC_t* stream = LZ4_createStreamHC();
+ return (jlong)(intptr_t)stream;
+}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_freeStreamHC
+ * Signature: (J)I
+ */
+JNIEXPORT jint JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1freeStreamHC
+ (JNIEnv *env, jclass cls, jlong streamPtr) {
+
+ if (streamPtr == 0) {
+ return 0;
+ }
+
+ LZ4_streamHC_t* stream = (LZ4_streamHC_t*)(intptr_t)streamPtr;
+ return LZ4_freeStreamHC(stream);
+}
+
+/*
+ * Class: net_jpountz_lz4_LZ4JNI
+ * Method: LZ4_compress_HC_extStateHC_fastReset
+ * Signature: (J[BLjava/nio/ByteBuffer;II[BLjava/nio/ByteBuffer;III)I
+ */
+JNIEXPORT jint JNICALL Java_net_jpountz_lz4_LZ4JNI_LZ4_1compress_1HC_1extStateHC_1fastReset
+ (JNIEnv *env, jclass cls, jlong statePtr,
+ jbyteArray srcArray, jobject srcBuffer, jint srcOff, jint srcLen,
+ jbyteArray destArray, jobject destBuffer, jint destOff, jint maxDestLen, jint compressionLevel) {
+
+ if (statePtr == 0) {
+ throw_ISE(env, "Compressor state has been freed");
+ return 0;
+ }
+
+ if (!validate_range(env, srcOff, srcLen) || !validate_range(env, destOff, maxDestLen)) {
+ return 0;
+ }
+
+ void* state = (void*)(intptr_t)statePtr;
+ char* in;
+ char* out;
+ jint compressed;
+
+ if (srcArray != NULL) {
+ in = (char*) (*env)->GetPrimitiveArrayCritical(env, srcArray, 0);
+ } else {
+ in = (char*) (*env)->GetDirectBufferAddress(env, srcBuffer);
+ }
+
+ if (in == NULL) {
+ throw_OOM(env);
+ return 0;
+ }
+
+ if (destArray != NULL) {
+ out = (char*) (*env)->GetPrimitiveArrayCritical(env, destArray, 0);
+ } else {
+ out = (char*) (*env)->GetDirectBufferAddress(env, destBuffer);
+ }
+
+ if (out == NULL) {
+ if (srcArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, srcArray, in, 0);
+ }
+ throw_OOM(env);
+ return 0;
+ }
+
+ compressed = LZ4_compress_HC_extStateHC_fastReset(state, in + srcOff, out + destOff, srcLen, maxDestLen, compressionLevel);
+
+ if (srcArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, srcArray, in, 0);
+ }
+ if (destArray != NULL) {
+ (*env)->ReleasePrimitiveArrayCritical(env, destArray, out, 0);
+ }
+
+ return compressed;
+}
diff --git a/src/test/net/jpountz/fuzz/LZ4CompressorTest.java b/src/test/net/jpountz/fuzz/LZ4CompressorTest.java
index 8b1b588b..56122d57 100644
--- a/src/test/net/jpountz/fuzz/LZ4CompressorTest.java
+++ b/src/test/net/jpountz/fuzz/LZ4CompressorTest.java
@@ -2,9 +2,7 @@
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.junit.FuzzTest;
-import net.jpountz.lz4.LZ4Compressor;
-import net.jpountz.lz4.LZ4Exception;
-import net.jpountz.lz4.LZ4Factory;
+import net.jpountz.lz4.*;
import java.nio.ByteBuffer;
@@ -177,4 +175,106 @@ public void unsafe_high_l17_bytebuffer(FuzzedDataProvider provider) {
public void native_high_l17_bytebuffer(FuzzedDataProvider provider) {
testByteBuffer(provider, LZ4Factory.nativeInsecureInstance().highCompressor(17));
}
+
+ // fastResetCompressor: array
+ @FuzzTest
+ public void native_fastReset_array(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor()) {
+ testArray(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_fastReset_a9_array(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor(9)) {
+ testArray(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_fastReset_a17_array(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor(17)) {
+ testArray(provider, compressor);
+ }
+ }
+
+ // fastResetCompressor: bytebuffer
+ @FuzzTest
+ public void native_fastReset_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor()) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_fastReset_a9_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor(9)) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_fastReset_a17_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor(17)) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ // highFastResetCompressor: array
+ @FuzzTest
+ public void native_highFastReset_array(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor()) {
+ testArray(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l1_array(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(1)) {
+ testArray(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l9_array(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(9)) {
+ testArray(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l17_array(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(17)) {
+ testArray(provider, compressor);
+ }
+ }
+
+ // highFastResetCompressor: bytebuffer
+ @FuzzTest
+ public void native_highFastReset_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor()) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l1_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(1)) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l9_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(9)) {
+ testByteBuffer(provider, compressor);
+ }
+ }
+
+ @FuzzTest
+ public void native_highFastReset_l17_bytebuffer(FuzzedDataProvider provider) {
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor(17)) {
+ testByteBuffer(provider, compressor);
+ }
+ }
}
diff --git a/src/test/net/jpountz/lz4/LZ4FastResetTest.java b/src/test/net/jpountz/lz4/LZ4FastResetTest.java
new file mode 100644
index 00000000..80a8c7a2
--- /dev/null
+++ b/src/test/net/jpountz/lz4/LZ4FastResetTest.java
@@ -0,0 +1,340 @@
+package net.jpountz.lz4;
+
+/*
+ * Copyright 2020 Adrien Grand and the lz4-java contributors.
+ *
+ * Licensed 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.
+ */
+
+import junit.framework.TestCase;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertArrayEquals;
+
+public class LZ4FastResetTest extends TestCase {
+
+ public void testFastResetCompressorLifecycleAndRoundTrip() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+ byte[] data = repeatedData();
+
+ try (LZ4JNIFastResetCompressor compressor = factory.fastResetCompressor()) {
+ assertEquals(LZ4Constants.DEFAULT_ACCELERATION, compressor.getAcceleration());
+ assertFalse(compressor.isClosed());
+
+ byte[] compressed = new byte[compressor.maxCompressedLength(data.length)];
+ int compressedLength = compressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ byte[] restored = new byte[data.length];
+
+ int restoredLength = factory.safeDecompressor().decompress(compressed, 0, compressedLength, restored, 0);
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, restored);
+
+ compressor.close();
+ assertTrue(compressor.isClosed());
+ compressor.close();
+
+ try {
+ compressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ fail();
+ } catch (IllegalStateException expected) {
+ // expected
+ }
+ }
+ }
+
+ public void testFastResetAccelerationNormalization() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+
+ try (LZ4JNIFastResetCompressor defaultCompressor = factory.fastResetCompressor();
+ LZ4JNIFastResetCompressor negativeCompressor = factory.fastResetCompressor(-17);
+ LZ4JNIFastResetCompressor maxCompressor = factory.fastResetCompressor(Integer.MAX_VALUE)) {
+ assertEquals(LZ4Constants.DEFAULT_ACCELERATION, defaultCompressor.getAcceleration());
+ assertEquals(LZ4Constants.MIN_ACCELERATION, negativeCompressor.getAcceleration());
+ assertEquals(LZ4Constants.MAX_ACCELERATION, maxCompressor.getAcceleration());
+ }
+ }
+
+ public void testFastResetConstructorNormalization() {
+ try (LZ4JNIFastResetCompressor negativeCompressor = new LZ4JNIFastResetCompressor(-17);
+ LZ4JNIFastResetCompressor maxCompressor = new LZ4JNIFastResetCompressor(Integer.MAX_VALUE)) {
+ assertEquals(LZ4Constants.MIN_ACCELERATION, negativeCompressor.getAcceleration());
+ assertEquals(LZ4Constants.MAX_ACCELERATION, maxCompressor.getAcceleration());
+ }
+ }
+
+ public void testHighFastResetCompressorLifecycleAndLevelNormalization() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+ byte[] data = repeatedData();
+
+ try (LZ4JNIHCFastResetCompressor defaultCompressor = factory.highFastResetCompressor();
+ LZ4JNIHCFastResetCompressor lowCompressor = factory.highFastResetCompressor(0);
+ LZ4JNIHCFastResetCompressor highCompressor = factory.highFastResetCompressor(99)) {
+ assertEquals(LZ4Constants.DEFAULT_COMPRESSION_LEVEL, defaultCompressor.getCompressionLevel());
+ assertEquals(LZ4Constants.DEFAULT_COMPRESSION_LEVEL, lowCompressor.getCompressionLevel());
+ assertEquals(LZ4Constants.MAX_COMPRESSION_LEVEL, highCompressor.getCompressionLevel());
+
+ byte[] compressed = new byte[highCompressor.maxCompressedLength(data.length)];
+ int compressedLength = highCompressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ byte[] restored = new byte[data.length];
+
+ int restoredLength = factory.safeDecompressor().decompress(compressed, 0, compressedLength, restored, 0);
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, restored);
+
+ highCompressor.close();
+ assertTrue(highCompressor.isClosed());
+ highCompressor.close();
+
+ try {
+ highCompressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ fail();
+ } catch (IllegalStateException expected) {
+ // expected
+ }
+ }
+ }
+
+ public void testHighFastResetConstructorNormalization() {
+ try (LZ4JNIHCFastResetCompressor lowCompressor = new LZ4JNIHCFastResetCompressor(0);
+ LZ4JNIHCFastResetCompressor highCompressor = new LZ4JNIHCFastResetCompressor(99)) {
+ assertEquals(LZ4Constants.DEFAULT_COMPRESSION_LEVEL, lowCompressor.getCompressionLevel());
+ assertEquals(LZ4Constants.MAX_COMPRESSION_LEVEL, highCompressor.getCompressionLevel());
+ }
+ }
+
+ public void testFastResetByteBufferRoundTripWithDirectBuffers() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+ byte[] data = repeatedData();
+ ByteBuffer src = directBuffer(data);
+ ByteBuffer compressed = ByteBuffer.allocateDirect(LZ4Utils.maxCompressedLength(data.length));
+ ByteBuffer restored = ByteBuffer.allocateDirect(data.length);
+
+ try (LZ4JNIFastResetCompressor compressor = factory.fastResetCompressor(9)) {
+ int compressedLength = compressor.compress(src, 0, src.remaining(), compressed, 0, compressed.capacity());
+ int restoredLength = factory.safeDecompressor().decompress(compressed, 0, compressedLength, restored, 0, restored.capacity());
+
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, readBuffer(restored, restoredLength));
+ }
+ }
+
+ public void testFastResetByteBufferRejectsUnsupportedSourceBuffer() {
+ ByteBuffer src = ByteBuffer.wrap(repeatedData()).asReadOnlyBuffer();
+ ByteBuffer dest = ByteBuffer.allocate(LZ4Utils.maxCompressedLength(src.remaining()));
+
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor()) {
+ compressor.compress(src, 0, src.remaining(), dest, 0, dest.remaining());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ // expected
+ }
+ }
+
+ public void testFastResetByteBufferAfterClose() {
+ ByteBuffer src = ByteBuffer.wrap(repeatedData());
+ ByteBuffer dest = ByteBuffer.allocate(LZ4Utils.maxCompressedLength(src.remaining()));
+
+ try (LZ4JNIFastResetCompressor compressor = LZ4Factory.nativeInstance().fastResetCompressor()) {
+ compressor.close();
+ try {
+ compressor.compress(src, 0, src.remaining(), dest, 0, dest.remaining());
+ fail();
+ } catch (IllegalStateException expected) {
+ // expected
+ }
+ }
+ }
+
+ public void testFastResetRepeatedCompressCallsReuseState() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+
+ try (LZ4JNIFastResetCompressor compressor = factory.fastResetCompressor(9)) {
+ assertRepeatedCompressionReuse(compressor, factory.safeDecompressor());
+ }
+ }
+
+ public void testFastResetRepeatedCompressCallsRecoverAfterFailure() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+
+ try (LZ4JNIFastResetCompressor compressor = factory.fastResetCompressor()) {
+ assertCompressionRecoversAfterFailure(compressor, factory.safeDecompressor());
+ }
+ }
+
+ public void testHighFastResetByteBufferRoundTripWithDirectBuffers() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+ byte[] data = repeatedData();
+ ByteBuffer src = directBuffer(data);
+ ByteBuffer compressed = ByteBuffer.allocateDirect(LZ4Utils.maxCompressedLength(data.length));
+ ByteBuffer restored = ByteBuffer.allocateDirect(data.length);
+
+ try (LZ4JNIHCFastResetCompressor compressor = factory.highFastResetCompressor(9)) {
+ int compressedLength = compressor.compress(src, 0, src.remaining(), compressed, 0, compressed.capacity());
+ int restoredLength = factory.safeDecompressor().decompress(compressed, 0, compressedLength, restored, 0, restored.capacity());
+
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, readBuffer(restored, restoredLength));
+ }
+ }
+
+ public void testHighFastResetByteBufferRejectsUnsupportedSourceBuffer() {
+ ByteBuffer src = ByteBuffer.wrap(repeatedData()).asReadOnlyBuffer();
+ ByteBuffer dest = ByteBuffer.allocate(LZ4Utils.maxCompressedLength(src.remaining()));
+
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor()) {
+ compressor.compress(src, 0, src.remaining(), dest, 0, dest.remaining());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ // expected
+ }
+ }
+
+ public void testHighFastResetByteBufferAfterClose() {
+ ByteBuffer src = ByteBuffer.wrap(repeatedData());
+ ByteBuffer dest = ByteBuffer.allocate(LZ4Utils.maxCompressedLength(src.remaining()));
+
+ try (LZ4JNIHCFastResetCompressor compressor = LZ4Factory.nativeInstance().highFastResetCompressor()) {
+ compressor.close();
+ try {
+ compressor.compress(src, 0, src.remaining(), dest, 0, dest.remaining());
+ fail();
+ } catch (IllegalStateException expected) {
+ // expected
+ }
+ }
+ }
+
+ public void testHighFastResetRepeatedCompressCallsReuseState() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+
+ try (LZ4JNIHCFastResetCompressor compressor = factory.highFastResetCompressor(9)) {
+ assertRepeatedCompressionReuse(compressor, factory.safeDecompressor());
+ }
+ }
+
+ public void testHighFastResetRepeatedCompressCallsRecoverAfterFailure() {
+ LZ4Factory factory = LZ4Factory.nativeInstance();
+
+ try (LZ4JNIHCFastResetCompressor compressor = factory.highFastResetCompressor()) {
+ assertCompressionRecoversAfterFailure(compressor, factory.safeDecompressor());
+ }
+ }
+
+ public void testFastResetMethodsRequireNativeFactory() {
+ assertFastResetUnsupported(LZ4Factory.safeInstance());
+ assertFastResetUnsupported(LZ4Factory.unsafeInstance());
+ assertFastResetUnsupported(LZ4Factory.unsafeInsecureInstance());
+ }
+
+ private static byte[] repeatedData() {
+ byte[] data = new byte[1024];
+ for (int i = 0; i < data.length; ++i) {
+ data[i] = (byte) ('a' + (i % 7));
+ }
+ return data;
+ }
+
+ private static ByteBuffer directBuffer(byte[] data) {
+ ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
+ buffer.put(data);
+ buffer.flip();
+ return buffer;
+ }
+
+ private static byte[] alternateData() {
+ byte[] data = new byte[333];
+ for (int i = 0; i < data.length; ++i) {
+ data[i] = (byte) ((i * 31) ^ 0x5A);
+ }
+ return data;
+ }
+
+ private static byte[] readBuffer(ByteBuffer buffer, int length) {
+ ByteBuffer duplicate = buffer.duplicate();
+ duplicate.position(0);
+ duplicate.limit(length);
+ byte[] bytes = new byte[length];
+ duplicate.get(bytes);
+ return bytes;
+ }
+
+ private static void assertRepeatedCompressionReuse(LZ4Compressor compressor, LZ4SafeDecompressor decompressor) {
+ byte[] first = repeatedData();
+ byte[] second = alternateData();
+
+ assertArrayRoundTrip(compressor, decompressor, first);
+ assertArrayRoundTrip(compressor, decompressor, second);
+ assertDirectByteBufferRoundTrip(compressor, decompressor, second);
+ assertDirectByteBufferRoundTrip(compressor, decompressor, first);
+ }
+
+ private static void assertCompressionRecoversAfterFailure(LZ4Compressor compressor, LZ4SafeDecompressor decompressor) {
+ byte[] data = repeatedData();
+ byte[] tooSmallDest = new byte[1];
+
+ try {
+ compressor.compress(data, 0, data.length, tooSmallDest, 0, tooSmallDest.length);
+ fail();
+ } catch (LZ4Exception expected) {
+ // expected
+ }
+
+ assertArrayRoundTrip(compressor, decompressor, alternateData());
+ assertDirectByteBufferRoundTrip(compressor, decompressor, data);
+ }
+
+ private static void assertArrayRoundTrip(LZ4Compressor compressor, LZ4SafeDecompressor decompressor, byte[] data) {
+ byte[] compressed = new byte[compressor.maxCompressedLength(data.length)];
+ int compressedLength = compressor.compress(data, 0, data.length, compressed, 0, compressed.length);
+ byte[] restored = new byte[data.length];
+ int restoredLength = decompressor.decompress(compressed, 0, compressedLength, restored, 0);
+
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, restored);
+ }
+
+ private static void assertDirectByteBufferRoundTrip(LZ4Compressor compressor, LZ4SafeDecompressor decompressor, byte[] data) {
+ ByteBuffer src = directBuffer(data);
+ ByteBuffer compressed = ByteBuffer.allocateDirect(compressor.maxCompressedLength(data.length));
+ ByteBuffer restored = ByteBuffer.allocateDirect(data.length);
+
+ int srcPosition = src.position();
+ int compressedPosition = compressed.position();
+ int restoredPosition = restored.position();
+
+ int compressedLength = compressor.compress(src, 0, src.remaining(), compressed, 0, compressed.capacity());
+ int restoredLength = decompressor.decompress(compressed, 0, compressedLength, restored, 0, restored.capacity());
+
+ assertEquals(srcPosition, src.position());
+ assertEquals(compressedPosition, compressed.position());
+ assertEquals(restoredPosition, restored.position());
+ assertEquals(data.length, restoredLength);
+ assertArrayEquals(data, readBuffer(restored, restoredLength));
+ }
+
+ private static void assertFastResetUnsupported(LZ4Factory factory) {
+ try (LZ4JNIFastResetCompressor ignored = factory.fastResetCompressor()) {
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ // expected
+ }
+
+ try (LZ4JNIHCFastResetCompressor ignored = factory.highFastResetCompressor()) {
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ // expected
+ }
+ }
+}
+