From 64abd43f4615b242a021a395f86bdb3e69122bec Mon Sep 17 00:00:00 2001 From: Yee Date: Wed, 15 Aug 2018 23:33:36 +0800 Subject: [PATCH] Lz4 --- build.third_step6.gradle | 5 +- .../util/darwin/x86_64/liblz4-java.dylib | Bin 0 -> 71996 bytes .../jpountz/util/linux/aarch64/liblz4-java.so | Bin 0 -> 79568 bytes .../jpountz/util/linux/amd64/liblz4-java.so | Bin 0 -> 56964 bytes .../jpountz/util/linux/i386/liblz4-java.so | Bin 0 -> 68840 bytes .../jpountz/util/linux/ppc64le/liblz4-java.so | Bin 0 -> 104776 bytes .../jpountz/util/linux/s390x/liblz4-java.so | Bin 0 -> 74491 bytes .../jpountz/util/win32/amd64/liblz4-java.so | Bin 0 -> 83925 bytes .../net/jpountz/lz4/LZ4BlockInputStream.java | 301 ++++++++++ .../net/jpountz/lz4/LZ4BlockOutputStream.java | 279 +++++++++ .../net/jpountz/lz4/LZ4ByteBufferUtils.java | 237 ++++++++ .../third/net/jpountz/lz4/LZ4Compressor.java | 168 ++++++ .../third/net/jpountz/lz4/LZ4Constants.java | 53 ++ .../net/jpountz/lz4/LZ4Decompressor.java | 25 + .../third/net/jpountz/lz4/LZ4Exception.java | 36 ++ .../fr/third/net/jpountz/lz4/LZ4Factory.java | 309 ++++++++++ .../net/jpountz/lz4/LZ4FastDecompressor.java | 135 +++++ .../net/jpountz/lz4/LZ4FrameInputStream.java | 351 +++++++++++ .../net/jpountz/lz4/LZ4FrameOutputStream.java | 434 ++++++++++++++ .../net/jpountz/lz4/LZ4HCJNICompressor.java | 86 +++ .../jpountz/lz4/LZ4HCJavaSafeCompressor.java | 550 ++++++++++++++++++ .../lz4/LZ4HCJavaUnsafeCompressor.java | 550 ++++++++++++++++++ .../com/fr/third/net/jpountz/lz4/LZ4JNI.java | 41 ++ .../net/jpountz/lz4/LZ4JNICompressor.java | 80 +++ .../jpountz/lz4/LZ4JNIFastDecompressor.java | 82 +++ .../jpountz/lz4/LZ4JNISafeDecompressor.java | 81 +++ .../jpountz/lz4/LZ4JavaSafeCompressor.java | 511 ++++++++++++++++ .../lz4/LZ4JavaSafeFastDecompressor.java | 205 +++++++ .../lz4/LZ4JavaSafeSafeDecompressor.java | 213 +++++++ .../jpountz/lz4/LZ4JavaUnsafeCompressor.java | 511 ++++++++++++++++ .../lz4/LZ4JavaUnsafeFastDecompressor.java | 205 +++++++ .../lz4/LZ4JavaUnsafeSafeDecompressor.java | 213 +++++++ .../net/jpountz/lz4/LZ4SafeDecompressor.java | 155 +++++ .../third/net/jpountz/lz4/LZ4SafeUtils.java | 179 ++++++ .../lz4/LZ4UnknownSizeDecompressor.java | 27 + .../third/net/jpountz/lz4/LZ4UnsafeUtils.java | 200 +++++++ .../fr/third/net/jpountz/lz4/LZ4Utils.java | 68 +++ .../com/fr/third/net/jpountz/lz4/package.html | 55 ++ .../net/jpountz/util/ByteBufferUtils.java | 92 +++ .../com/fr/third/net/jpountz/util/Native.java | 133 +++++ .../fr/third/net/jpountz/util/SafeUtils.java | 95 +++ .../third/net/jpountz/util/UnsafeUtils.java | 147 +++++ .../com/fr/third/net/jpountz/util/Utils.java | 36 ++ .../fr/third/net/jpountz/util/package.html | 22 + .../xxhash/AbstractStreamingXXHash32Java.java | 39 ++ .../xxhash/AbstractStreamingXXHash64Java.java | 40 ++ .../net/jpountz/xxhash/StreamingXXHash32.java | 119 ++++ .../jpountz/xxhash/StreamingXXHash32JNI.java | 71 +++ .../xxhash/StreamingXXHash32JavaSafe.java | 142 +++++ .../xxhash/StreamingXXHash32JavaUnsafe.java | 142 +++++ .../net/jpountz/xxhash/StreamingXXHash64.java | 119 ++++ .../jpountz/xxhash/StreamingXXHash64JNI.java | 71 +++ .../xxhash/StreamingXXHash64JavaSafe.java | 166 ++++++ .../xxhash/StreamingXXHash64JavaUnsafe.java | 166 ++++++ .../fr/third/net/jpountz/xxhash/XXHash32.java | 71 +++ .../third/net/jpountz/xxhash/XXHash32JNI.java | 52 ++ .../net/jpountz/xxhash/XXHash32JavaSafe.java | 154 +++++ .../jpountz/xxhash/XXHash32JavaUnsafe.java | 154 +++++ .../fr/third/net/jpountz/xxhash/XXHash64.java | 71 +++ .../third/net/jpountz/xxhash/XXHash64JNI.java | 52 ++ .../net/jpountz/xxhash/XXHash64JavaSafe.java | 192 ++++++ .../jpountz/xxhash/XXHash64JavaUnsafe.java | 192 ++++++ .../net/jpountz/xxhash/XXHashConstants.java | 31 + .../net/jpountz/xxhash/XXHashFactory.java | 257 ++++++++ .../third/net/jpountz/xxhash/XXHashJNI.java | 43 ++ .../fr/third/net/jpountz/xxhash/package.html | 65 +++ 66 files changed, 9278 insertions(+), 1 deletion(-) create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/darwin/x86_64/liblz4-java.dylib create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/linux/aarch64/liblz4-java.so create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/linux/amd64/liblz4-java.so create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/linux/i386/liblz4-java.so create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/linux/ppc64le/liblz4-java.so create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/linux/s390x/liblz4-java.so create mode 100755 fine-lz4/resources/com/fr/third/net/jpountz/util/win32/amd64/liblz4-java.so create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockInputStream.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockOutputStream.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4ByteBufferUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Compressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Constants.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Decompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Exception.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Factory.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FastDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameInputStream.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameOutputStream.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJNICompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNICompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNIFastDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNISafeDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeCompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnknownSizeDecompressor.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnsafeUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Utils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/lz4/package.html create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/ByteBufferUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/Native.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/SafeUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/UnsafeUtils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/Utils.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/util/package.html create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaSafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaUnsafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaSafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaUnsafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaSafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaUnsafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaSafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaUnsafe.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashConstants.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashFactory.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashJNI.java create mode 100644 fine-lz4/src/com/fr/third/net/jpountz/xxhash/package.html diff --git a/build.third_step6.gradle b/build.third_step6.gradle index 46f7ebe3b..63e2d5fc1 100644 --- a/build.third_step6.gradle +++ b/build.third_step6.gradle @@ -39,7 +39,8 @@ sourceSets{ "${srcDir}/fine-redisson/src", "${srcDir}/fine-socketio/src", "${srcDir}/fine-itext/src", - "${srcDir}/fine-kryo/src" + "${srcDir}/fine-kryo/src", + "${srcDir}/fine-lz4/src" ] } } @@ -99,6 +100,8 @@ task copyFiles(type:Copy,dependsOn:'compileJava'){ with dataContent.call("${srcDir}/fine-jedis/resources") with dataContent.call("${srcDir}/fine-cssparser/src") with dataContent.call("${srcDir}/fine-kryo/src") + with dataContent.call("${srcDir}/fine-lz4/src") + with dataContent.call("${srcDir}/fine-lz4/resources") into "${classesDir}" } } diff --git a/fine-lz4/resources/com/fr/third/net/jpountz/util/darwin/x86_64/liblz4-java.dylib b/fine-lz4/resources/com/fr/third/net/jpountz/util/darwin/x86_64/liblz4-java.dylib new file mode 100755 index 0000000000000000000000000000000000000000..fc98306c8aa13574f4c0ad2bc23b6afc9df0afe8 GIT binary patch literal 71996 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

2?rlh`L; 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 %xM#+~7T?0!a?i>}?*nCCZ=;vN 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 z%0A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 UgT4Ry$^QKZoMFy9W#Ie&UpIy7D*ylh literal 0 HcmV?d00001 diff --git a/fine-lz4/resources/com/fr/third/net/jpountz/util/linux/amd64/liblz4-java.so b/fine-lz4/resources/com/fr/third/net/jpountz/util/linux/amd64/liblz4-java.so new file mode 100755 index 0000000000000000000000000000000000000000..8ee7d308fd9bc5a74e58efa8adcf0a2d58d215ff GIT binary patch literal 56964 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%0A;4!w;4@RXG(W`W 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 literal 0 HcmV?d00001 diff --git a/fine-lz4/resources/com/fr/third/net/jpountz/util/linux/s390x/liblz4-java.so b/fine-lz4/resources/com/fr/third/net/jpountz/util/linux/s390x/liblz4-java.so new file mode 100755 index 0000000000000000000000000000000000000000..78f67eccfdcdfe8bfffcf4f122ef00348a11a0d9 GIT binary patch literal 74491 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 z zgJ)fl%6kQ2-Tu18(gX6|@aQkK^zuBB_qKMSyuBWM>4R}k zzOTO5)EhMGq*VTe9-oI0O&?3aU+&>w8?<=*BL7+sUt#se`bd?(*~6ciwDey9-|69} zSbEkgtNiCF%+-U%ORoH1dGx!ZmJfcB-{;{!x7*-_Pr(=uf8`E?XMLb5IMu_S+h_2h zR`6$e_~}*;tV31>jUN7?q^0MXw&2Si{>%=87e6idriWh{Gx#9<3LfzA57k(D+P&ak zJ^X_`7QX=ee|Y$*ZoN9eJ05;dnWZNV7oHTciF8nSu1A0UdV3E!3#&Z*oh1g(Iw@7y z;^Ft&awWbO-sR!nthRjc$HHek{EAYGN3KF&d)0>xo^^k!@EwmnvBThl@GBbY;UC&& z`C#Wo(;_yJ4vKvBdbQWyyAk>eJU*|uSp{F`;a@MY^!ea@{V!tZgY~MaNZL2c^HI^) zzz;oEwzjvmuU)lj=1LWhFPUEzuUx!rc|5M_60M2V4Q=g-)_D7x_{zrSrbL^{f{MrM zn&Yb*n^)8}#_QUfTifEbYu78LQd=4m?TNaXXUv&57tUFT;xeUcCTQbKyS8pnW=gki z?V2?k$l^=IWhOX{v?R{v?kix;;R!)iPnad%mX*IwYRi3x6GXNX#}%{V?9}faM66Yrt?yMIgKRo z>gq)ZWTcJ97gm*Da9;epr3=uxX#n@SuB|yE<0~|e0h0A<#}-h_qlW8)F)wU*P5tpPb`|Rmhg|U_$P^@m8>h=*03?r zylPoHS=TIUC;L%kHs#J*+rlh+LUXB0q_nZBwyhoAG_^N0t)({cmFsJz^=lg`C>lM# za&g7{`0Sap)3m*4{_J>T!C=-L^;Ga% z*|Nc}mv}tZG<4ch1#WDONSNu1uV_zFik6rAz&cd1k26Gcv!5hqEJ{ zd5Ezem9J>6R8(XZ?>EsQt>wij<7z_)G6RH!3b9jdWm2KFO|DC2>PdT;lk@Z6|E`RZR>W-f4|Q zHbj_&&-Qf8^0jJXb8THVLA02uU~<$-^opI!>fuifhFZLgs?AOdK|^@>+E-LE%WW$Y zQ?!<>7fE1UKzOV(k1S>mSlC|`Dq$i zs!ar&8J%@SeQq;MU!7TH&)kmA59ZDx(8ZUmi&xnZ;rzzhwl;=?B9j-2vzvtuOkPkK zqP1)>PkL72`-wJ`?kTRZif5%|ix25M-0G*BWI8*yQ;@i+hZ^q4H*KKdZz+5?YjfD% S-p9~lTrrgC3|0<_&i@B4Q{p!O literal 0 HcmV?d00001 diff --git a/fine-lz4/resources/com/fr/third/net/jpountz/util/win32/amd64/liblz4-java.so b/fine-lz4/resources/com/fr/third/net/jpountz/util/win32/amd64/liblz4-java.so new file mode 100755 index 0000000000000000000000000000000000000000..18e2fb4b656e004bf43a6a9eaf2433e58e51a4df GIT binary patch literal 83925 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¬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 literal 0 HcmV?d00001 diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockInputStream.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockInputStream.java new file mode 100644 index 000000000..c554ce5bd --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockInputStream.java @@ -0,0 +1,301 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_LEVEL_BASE; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_LZ4; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.COMPRESSION_METHOD_RAW; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.DEFAULT_SEED; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.HEADER_LENGTH; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.MAGIC; +import static com.fr.third.net.jpountz.lz4.LZ4BlockOutputStream.MAGIC_LENGTH; + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Checksum; + +import com.fr.third.net.jpountz.util.SafeUtils; +import com.fr.third.net.jpountz.xxhash.StreamingXXHash32; +import com.fr.third.net.jpountz.xxhash.XXHash32; +import com.fr.third.net.jpountz.xxhash.XXHashFactory; + +/** + * {@link InputStream} implementation to decode data written with + * {@link LZ4BlockOutputStream}. This class is not thread-safe and does not + * support {@link #mark(int)}/{@link #reset()}. + * @see LZ4BlockOutputStream + */ +public final class LZ4BlockInputStream extends FilterInputStream { + + private final LZ4FastDecompressor decompressor; + private final Checksum checksum; + private final boolean stopOnEmptyBlock; + private byte[] buffer; + private byte[] compressedBuffer; + private int originalLen; + private int o; + private boolean finished; + + /** + * Creates a new LZ4 input stream to read from the specified underlying InputStream. + * + * @param in the {@link InputStream} to poll + * @param decompressor the {@link LZ4FastDecompressor decompressor} instance to + * use + * @param checksum the {@link Checksum} instance to use, must be + * equivalent to the instance which has been used to + * write the stream + * @param stopOnEmptyBlock whether read is stopped on an empty block + */ + public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor, Checksum checksum, boolean stopOnEmptyBlock) { + super(in); + this.decompressor = decompressor; + this.checksum = checksum; + this.stopOnEmptyBlock = stopOnEmptyBlock; + this.buffer = new byte[0]; + this.compressedBuffer = new byte[HEADER_LENGTH]; + o = originalLen = 0; + finished = false; + } + + /** + * Creates a new LZ4 input stream to read from the specified underlying InputStream. + * + * @param in the {@link InputStream} to poll + * @param decompressor the {@link LZ4FastDecompressor decompressor} instance to + * use + * @param checksum the {@link Checksum} instance to use, must be + * equivalent to the instance which has been used to + * write the stream + * + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum, boolean) + */ + public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor, Checksum checksum) { + this(in, decompressor, checksum, true); + } + + /** + * Creates a new LZ4 input stream to read from the specified underlying InputStream, using {@link XXHash32} for checksuming. + * + * @param in the {@link InputStream} to poll + * @param decompressor the {@link LZ4FastDecompressor decompressor} instance to + * use + * + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum, boolean) + * @see StreamingXXHash32#asChecksum() + */ + public LZ4BlockInputStream(InputStream in, LZ4FastDecompressor decompressor) { + this(in, decompressor, XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum(), true); + } + + /** + * Creates a new LZ4 input stream to read from the specified underlying InputStream, using {@link XXHash32} for checksuming. + * + * @param in the {@link InputStream} to poll + * @param stopOnEmptyBlock whether read is stopped on an empty block + * + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor, Checksum, boolean) + * @see LZ4Factory#fastestInstance() + * @see StreamingXXHash32#asChecksum() + */ + public LZ4BlockInputStream(InputStream in, boolean stopOnEmptyBlock) { + this(in, LZ4Factory.fastestInstance().fastDecompressor(), XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum(), stopOnEmptyBlock); + } + + /** + * Creates a new LZ4 input stream to read from the specified underlying InputStream, using {@link XXHash32} for checksuming. + * + * @param in the {@link InputStream} to poll + * + * @see #LZ4BlockInputStream(InputStream, LZ4FastDecompressor) + * @see LZ4Factory#fastestInstance() + */ + public LZ4BlockInputStream(InputStream in) { + this(in, LZ4Factory.fastestInstance().fastDecompressor()); + } + + @Override + public int available() throws IOException { + return originalLen - o; + } + + @Override + public int read() throws IOException { + if (finished) { + return -1; + } + if (o == originalLen) { + refill(); + } + if (finished) { + return -1; + } + return buffer[o++] & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + SafeUtils.checkRange(b, off, len); + if (finished) { + return -1; + } + if (o == originalLen) { + refill(); + } + if (finished) { + return -1; + } + len = Math.min(len, originalLen - o); + System.arraycopy(buffer, o, b, off, len); + o += len; + return len; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0 || finished) { + return 0; + } + if (o == originalLen) { + refill(); + } + if (finished) { + return 0; + } + final int skipped = (int) Math.min(n, originalLen - o); + o += skipped; + return skipped; + } + + private void refill() throws IOException { + try { + readFully(compressedBuffer, HEADER_LENGTH); + } catch (EOFException e) { + if (!stopOnEmptyBlock) { + finished = true; + } else { + throw e; + } + return; + } + for (int i = 0; i < MAGIC_LENGTH; ++i) { + if (compressedBuffer[i] != MAGIC[i]) { + throw new IOException("Stream is corrupted"); + } + } + final int token = compressedBuffer[MAGIC_LENGTH] & 0xFF; + final int compressionMethod = token & 0xF0; + final int compressionLevel = COMPRESSION_LEVEL_BASE + (token & 0x0F); + if (compressionMethod != COMPRESSION_METHOD_RAW && compressionMethod != COMPRESSION_METHOD_LZ4) { + throw new IOException("Stream is corrupted"); + } + final int compressedLen = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 1); + originalLen = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 5); + final int check = SafeUtils.readIntLE(compressedBuffer, MAGIC_LENGTH + 9); + assert HEADER_LENGTH == MAGIC_LENGTH + 13; + if (originalLen > 1 << compressionLevel + || originalLen < 0 + || compressedLen < 0 + || (originalLen == 0 && compressedLen != 0) + || (originalLen != 0 && compressedLen == 0) + || (compressionMethod == COMPRESSION_METHOD_RAW && originalLen != compressedLen)) { + throw new IOException("Stream is corrupted"); + } + if (originalLen == 0 && compressedLen == 0) { + if (check != 0) { + throw new IOException("Stream is corrupted"); + } + if (!stopOnEmptyBlock) { + refill(); + } else { + finished = true; + } + return; + } + if (buffer.length < originalLen) { + buffer = new byte[Math.max(originalLen, buffer.length * 3 / 2)]; + } + switch (compressionMethod) { + case COMPRESSION_METHOD_RAW: + readFully(buffer, originalLen); + break; + case COMPRESSION_METHOD_LZ4: + if (compressedBuffer.length < compressedLen) { + compressedBuffer = new byte[Math.max(compressedLen, compressedBuffer.length * 3 / 2)]; + } + readFully(compressedBuffer, compressedLen); + try { + final int compressedLen2 = decompressor.decompress(compressedBuffer, 0, buffer, 0, originalLen); + if (compressedLen != compressedLen2) { + throw new IOException("Stream is corrupted"); + } + } catch (LZ4Exception e) { + throw new IOException("Stream is corrupted", e); + } + break; + default: + throw new AssertionError(); + } + checksum.reset(); + checksum.update(buffer, 0, originalLen); + if ((int) checksum.getValue() != check) { + throw new IOException("Stream is corrupted"); + } + o = 0; + } + + private void readFully(byte[] b, int len) throws IOException { + int read = 0; + while (read < len) { + final int r = in.read(b, read, len - read); + if (r < 0) { + throw new EOFException("Stream ended prematurely"); + } + read += r; + } + assert len == read; + } + + @Override + public boolean markSupported() { + return false; + } + + @SuppressWarnings("sync-override") + @Override + public void mark(int readlimit) { + // unsupported + } + + @SuppressWarnings("sync-override") + @Override + public void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(in=" + in + + ", decompressor=" + decompressor + ", checksum=" + checksum + ")"; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockOutputStream.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockOutputStream.java new file mode 100644 index 000000000..8ab77beeb --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4BlockOutputStream.java @@ -0,0 +1,279 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Checksum; + +import com.fr.third.net.jpountz.util.SafeUtils; +import com.fr.third.net.jpountz.xxhash.StreamingXXHash32; +import com.fr.third.net.jpountz.xxhash.XXHashFactory; + +/** + * Streaming LZ4 (not compatible with the LZ4 Frame format). + * This class compresses data into fixed-size blocks of compressed data. + * This class uses its own format and is not compatible with the LZ4 Frame format. + * For interoperability with other LZ4 tools, use {@link LZ4FrameOutputStream}, + * which is compatible with the LZ4 Frame format. This class remains for backward compatibility. + * @see LZ4BlockInputStream + * @see LZ4FrameOutputStream + */ +public final class LZ4BlockOutputStream extends FilterOutputStream { + + static final byte[] MAGIC = new byte[] { 'L', 'Z', '4', 'B', 'l', 'o', 'c', 'k' }; + static final int MAGIC_LENGTH = MAGIC.length; + + static final int HEADER_LENGTH = + MAGIC_LENGTH // magic bytes + + 1 // token + + 4 // compressed length + + 4 // decompressed length + + 4; // checksum + + static final int COMPRESSION_LEVEL_BASE = 10; + static final int MIN_BLOCK_SIZE = 64; + static final int MAX_BLOCK_SIZE = 1 << (COMPRESSION_LEVEL_BASE + 0x0F); + + static final int COMPRESSION_METHOD_RAW = 0x10; + static final int COMPRESSION_METHOD_LZ4 = 0x20; + + static final int DEFAULT_SEED = 0x9747b28c; + + private static int compressionLevel(int blockSize) { + if (blockSize < MIN_BLOCK_SIZE) { + throw new IllegalArgumentException("blockSize must be >= " + MIN_BLOCK_SIZE + ", got " + blockSize); + } else if (blockSize > MAX_BLOCK_SIZE) { + throw new IllegalArgumentException("blockSize must be <= " + MAX_BLOCK_SIZE + ", got " + blockSize); + } + int compressionLevel = 32 - Integer.numberOfLeadingZeros(blockSize - 1); // ceil of log2 + assert (1 << compressionLevel) >= blockSize; + assert blockSize * 2 > (1 << compressionLevel); + compressionLevel = Math.max(0, compressionLevel - COMPRESSION_LEVEL_BASE); + assert compressionLevel >= 0 && compressionLevel <= 0x0F; + return compressionLevel; + } + + private final int blockSize; + private final int compressionLevel; + private final LZ4Compressor compressor; + private final Checksum checksum; + private final byte[] buffer; + private final byte[] compressedBuffer; + private final boolean syncFlush; + private boolean finished; + private int o; + + /** + * Creates a new {@link OutputStream} with configurable block size. Large + * blocks require more memory at compression and decompression time but + * should improve the compression ratio. + * + * @param out the {@link OutputStream} to feed + * @param blockSize the maximum number of bytes to try to compress at once, + * must be >= 64 and <= 32 M + * @param compressor the {@link LZ4Compressor} instance to use to compress + * data + * @param checksum the {@link Checksum} instance to use to check data for + * integrity. + * @param syncFlush true if pending data should also be flushed on {@link #flush()} + */ + public LZ4BlockOutputStream(OutputStream out, int blockSize, LZ4Compressor compressor, Checksum checksum, boolean syncFlush) { + super(out); + this.blockSize = blockSize; + this.compressor = compressor; + this.checksum = checksum; + this.compressionLevel = compressionLevel(blockSize); + this.buffer = new byte[blockSize]; + final int compressedBlockSize = HEADER_LENGTH + compressor.maxCompressedLength(blockSize); + this.compressedBuffer = new byte[compressedBlockSize]; + this.syncFlush = syncFlush; + o = 0; + finished = false; + System.arraycopy(MAGIC, 0, compressedBuffer, 0, MAGIC_LENGTH); + } + + /** + * Creates a new instance which checks stream integrity using + * {@link StreamingXXHash32} and doesn't sync flush. + * + * @param out the {@link OutputStream} to feed + * @param blockSize the maximum number of bytes to try to compress at once, + * must be >= 64 and <= 32 M + * @param compressor the {@link LZ4Compressor} instance to use to compress + * data + * + * @see #LZ4BlockOutputStream(OutputStream, int, LZ4Compressor, Checksum, boolean) + * @see StreamingXXHash32#asChecksum() + */ + public LZ4BlockOutputStream(OutputStream out, int blockSize, LZ4Compressor compressor) { + this(out, blockSize, compressor, XXHashFactory.fastestInstance().newStreamingHash32(DEFAULT_SEED).asChecksum(), false); + } + + /** + * Creates a new instance which compresses with the standard LZ4 compression + * algorithm. + * + * @param out the {@link OutputStream} to feed + * @param blockSize the maximum number of bytes to try to compress at once, + * must be >= 64 and <= 32 M + * + * @see #LZ4BlockOutputStream(OutputStream, int, LZ4Compressor) + * @see LZ4Factory#fastCompressor() + */ + public LZ4BlockOutputStream(OutputStream out, int blockSize) { + this(out, blockSize, LZ4Factory.fastestInstance().fastCompressor()); + } + + /** + * Creates a new instance which compresses into blocks of 64 KB. + * + * @param out the {@link OutputStream} to feed + * + * @see #LZ4BlockOutputStream(OutputStream, int) + */ + public LZ4BlockOutputStream(OutputStream out) { + this(out, 1 << 16); + } + + private void ensureNotFinished() { + if (finished) { + throw new IllegalStateException("This stream is already closed"); + } + } + + @Override + public void write(int b) throws IOException { + ensureNotFinished(); + if (o == blockSize) { + flushBufferedData(); + } + buffer[o++] = (byte) b; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + SafeUtils.checkRange(b, off, len); + ensureNotFinished(); + + while (o + len > blockSize) { + final int l = blockSize - o; + System.arraycopy(b, off, buffer, o, blockSize - o); + o = blockSize; + flushBufferedData(); + off += l; + len -= l; + } + System.arraycopy(b, off, buffer, o, len); + o += len; + } + + @Override + public void write(byte[] b) throws IOException { + ensureNotFinished(); + write(b, 0, b.length); + } + + @Override + public void close() throws IOException { + if (!finished) { + finish(); + } + if (out != null) { + out.close(); + out = null; + } + } + + private void flushBufferedData() throws IOException { + if (o == 0) { + return; + } + checksum.reset(); + checksum.update(buffer, 0, o); + final int check = (int) checksum.getValue(); + int compressedLength = compressor.compress(buffer, 0, o, compressedBuffer, HEADER_LENGTH); + final int compressMethod; + if (compressedLength >= o) { + compressMethod = COMPRESSION_METHOD_RAW; + compressedLength = o; + System.arraycopy(buffer, 0, compressedBuffer, HEADER_LENGTH, o); + } else { + compressMethod = COMPRESSION_METHOD_LZ4; + } + + compressedBuffer[MAGIC_LENGTH] = (byte) (compressMethod | compressionLevel); + writeIntLE(compressedLength, compressedBuffer, MAGIC_LENGTH + 1); + writeIntLE(o, compressedBuffer, MAGIC_LENGTH + 5); + writeIntLE(check, compressedBuffer, MAGIC_LENGTH + 9); + assert MAGIC_LENGTH + 13 == HEADER_LENGTH; + out.write(compressedBuffer, 0, HEADER_LENGTH + compressedLength); + o = 0; + } + + /** + * Flushes this compressed {@link OutputStream}. + * + * If the stream has been created with syncFlush=true, pending + * data will be compressed and appended to the underlying {@link OutputStream} + * before calling {@link OutputStream#flush()} on the underlying stream. + * Otherwise, this method just flushes the underlying stream, so pending + * data might not be available for reading until {@link #finish()} or + * {@link #close()} is called. + */ + @Override + public void flush() throws IOException { + if (out != null) { + if (syncFlush) { + flushBufferedData(); + } + out.flush(); + } + } + + /** + * Same as {@link #close()} except that it doesn't close the underlying stream. + * This can be useful if you want to keep on using the underlying stream. + * + * @throws IOException if an I/O error occurs. + */ + public void finish() throws IOException { + ensureNotFinished(); + flushBufferedData(); + compressedBuffer[MAGIC_LENGTH] = (byte) (COMPRESSION_METHOD_RAW | compressionLevel); + writeIntLE(0, compressedBuffer, MAGIC_LENGTH + 1); + writeIntLE(0, compressedBuffer, MAGIC_LENGTH + 5); + writeIntLE(0, compressedBuffer, MAGIC_LENGTH + 9); + assert MAGIC_LENGTH + 13 == HEADER_LENGTH; + out.write(compressedBuffer, 0, HEADER_LENGTH); + finished = true; + out.flush(); + } + + private static void writeIntLE(int i, byte[] buf, int off) { + buf[off++] = (byte) i; + buf[off++] = (byte) (i >>> 8); + buf[off++] = (byte) (i >>> 16); + buf[off++] = (byte) (i >>> 24); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(out=" + out + ", blockSize=" + blockSize + + ", compressor=" + compressor + ", checksum=" + checksum + ")"; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4ByteBufferUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4ByteBufferUtils.java new file mode 100644 index 000000000..29769aa7e --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4ByteBufferUtils.java @@ -0,0 +1,237 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.lz4.LZ4Constants.COPY_LENGTH; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.LAST_LITERALS; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.ML_BITS; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.ML_MASK; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.RUN_MASK; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.readByte; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.readInt; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.readLong; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.writeByte; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.writeInt; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.writeLong; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +enum LZ4ByteBufferUtils { + ; + static int hash(ByteBuffer buf, int i) { + return LZ4Utils.hash(readInt(buf, i)); + } + + static int hash64k(ByteBuffer buf, int i) { + return LZ4Utils.hash64k(readInt(buf, i)); + } + + static boolean readIntEquals(ByteBuffer buf, int i, int j) { + return buf.getInt(i) == buf.getInt(j); + } + + static void safeIncrementalCopy(ByteBuffer dest, int matchOff, int dOff, int matchLen) { + for (int i = 0; i < matchLen; ++i) { + dest.put(dOff + i, dest.get(matchOff + i)); + } + } + + static void wildIncrementalCopy(ByteBuffer dest, int matchOff, int dOff, int matchCopyEnd) { + if (dOff - matchOff < 4) { + for (int i = 0; i < 4; ++i) { + writeByte(dest, dOff+i, readByte(dest, matchOff+i)); + } + dOff += 4; + matchOff += 4; + int dec = 0; + assert dOff >= matchOff && dOff - matchOff < 8; + switch (dOff - matchOff) { + case 1: + matchOff -= 3; + break; + case 2: + matchOff -= 2; + break; + case 3: + matchOff -= 3; + dec = -1; + break; + case 5: + dec = 1; + break; + case 6: + dec = 2; + break; + case 7: + dec = 3; + break; + default: + break; + } + writeInt(dest, dOff, readInt(dest, matchOff)); + dOff += 4; + matchOff -= dec; + } else if (dOff - matchOff < COPY_LENGTH) { + writeLong(dest, dOff, readLong(dest, matchOff)); + dOff += dOff - matchOff; + } + while (dOff < matchCopyEnd) { + writeLong(dest, dOff, readLong(dest, matchOff)); + dOff += 8; + matchOff += 8; + } + } + + static int commonBytes(ByteBuffer src, int ref, int sOff, int srcLimit) { + int matchLen = 0; + while (sOff <= srcLimit - 8) { + if (readLong(src, sOff) == readLong(src, ref)) { + matchLen += 8; + ref += 8; + sOff += 8; + } else { + final int zeroBits; + if (src.order() == ByteOrder.BIG_ENDIAN) { + zeroBits = Long.numberOfLeadingZeros(readLong(src, sOff) ^ readLong(src, ref)); + } else { + zeroBits = Long.numberOfTrailingZeros(readLong(src, sOff) ^ readLong(src, ref)); + } + return matchLen + (zeroBits >>> 3); + } + } + while (sOff < srcLimit && readByte(src, ref++) == readByte(src, sOff++)) { + ++matchLen; + } + return matchLen; + } + + static int commonBytesBackward(ByteBuffer b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && b.get(--o1) == b.get(--o2)) { + ++count; + } + return count; + } + + static void safeArraycopy(ByteBuffer src, int sOff, ByteBuffer dest, int dOff, int len) { + for (int i = 0; i < len; ++i) { + dest.put(dOff + i, src.get(sOff + i)); + } + } + + static void wildArraycopy(ByteBuffer src, int sOff, ByteBuffer dest, int dOff, int len) { + assert src.order().equals(dest.order()); + try { + for (int i = 0; i < len; i += 8) { + dest.putLong(dOff + i, src.getLong(sOff + i)); + } + } catch (IndexOutOfBoundsException e) { + throw new LZ4Exception("Malformed input at offset " + sOff); + } + } + + static int encodeSequence(ByteBuffer src, int anchor, int matchOff, int matchRef, int matchLen, ByteBuffer dest, int dOff, int destEnd) { + final int runLen = matchOff - anchor; + final int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + int token; + if (runLen >= RUN_MASK) { + token = (byte) (RUN_MASK << ML_BITS); + dOff = writeLen(runLen - RUN_MASK, dest, dOff); + } else { + token = runLen << ML_BITS; + } + + // copy literals + wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + // encode offset + final int matchDec = matchOff - matchRef; + dest.put(dOff++, (byte) matchDec); + dest.put(dOff++, (byte) (matchDec >>> 8)); + + // encode match len + matchLen -= 4; + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + if (matchLen >= ML_MASK) { + token |= ML_MASK; + dOff = writeLen(matchLen - RUN_MASK, dest, dOff); + } else { + token |= matchLen; + } + + dest.put(tokenOff, (byte) token); + + return dOff; + } + + static int lastLiterals(ByteBuffer src, int sOff, int srcLen, ByteBuffer dest, int dOff, int destEnd) { + final int runLen = srcLen; + + if (dOff + runLen + 1 + (runLen + 255 - RUN_MASK) / 255 > destEnd) { + throw new LZ4Exception(); + } + + if (runLen >= RUN_MASK) { + dest.put(dOff++, (byte) (RUN_MASK << ML_BITS)); + dOff = writeLen(runLen - RUN_MASK, dest, dOff); + } else { + dest.put(dOff++, (byte) (runLen << ML_BITS)); + } + // copy literals + safeArraycopy(src, sOff, dest, dOff, runLen); + dOff += runLen; + + return dOff; + } + + static int writeLen(int len, ByteBuffer dest, int dOff) { + while (len >= 0xFF) { + dest.put(dOff++, (byte) 0xFF); + len -= 0xFF; + } + dest.put(dOff++, (byte) len); + return dOff; + } + + static class Match { + int start, ref, len; + + void fix(int correction) { + start += correction; + ref += correction; + len -= correction; + } + + int end() { + return start + len; + } + } + + static void copyTo(Match m1, Match m2) { + m2.len = m1.len; + m2.start = m1.start; + m2.ref = m1.ref; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Compressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Compressor.java new file mode 100644 index 000000000..2ad6724db --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Compressor.java @@ -0,0 +1,168 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 java.util.Arrays; + +/** + * LZ4 compressor. + *

+ * Instances of this class are thread-safe. + */ +public abstract class LZ4Compressor { + + /** + * Returns the maximum compressed length for an input of size length. + * + * @param length the input size in bytes + * @return the maximum compressed length in bytes + */ + @SuppressWarnings("static-method") + public final int maxCompressedLength(int length) { + return LZ4Utils.maxCompressedLength(length); + } + + /** + * Compresses src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the compressed + * length. + * + * This method will throw a {@link LZ4Exception} if this compressor is unable + * to compress the input into less than maxDestLen bytes. To + * prevent this exception to be thrown, you should make sure that + * maxDestLen >= maxCompressedLength(srcLen). + * + * @param src the source data + * @param srcOff the start offset in src + * @param srcLen the number of bytes to compress + * @param dest the destination buffer + * @param destOff the start offset in dest + * @param maxDestLen the maximum number of bytes to write in dest + * @throws LZ4Exception if maxDestLen is too small + * @return the compressed size + */ + public abstract int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen); + + /** + * Compresses src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the compressed + * length. + * + * This method will throw a {@link LZ4Exception} if this compressor is unable + * to compress the input into less than maxDestLen bytes. To + * prevent this exception to be thrown, you should make sure that + * maxDestLen >= maxCompressedLength(srcLen). + * + * {@link ByteBuffer} positions remain unchanged. + * + * @param src the source data + * @param srcOff the start offset in src + * @param srcLen the number of bytes to compress + * @param dest the destination buffer + * @param destOff the start offset in dest + * @param maxDestLen the maximum number of bytes to write in dest + * @throws LZ4Exception if maxDestLen is too small + * @return the compressed size + */ + public abstract int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen); + + /** + * Convenience method, equivalent to calling + * {@link #compress(byte[], int, int, byte[], int, int) compress(src, srcOff, srcLen, dest, destOff, dest.length - destOff)}. + * + * @param src the source data + * @param srcOff the start offset in src + * @param srcLen the number of bytes to compress + * @param dest the destination buffer + * @param destOff the start offset in dest + * @throws LZ4Exception if dest is too small + * @return the compressed size + */ + public final int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff) { + return compress(src, srcOff, srcLen, dest, destOff, dest.length - destOff); + } + + /** + * Convenience method, equivalent to calling + * {@link #compress(byte[], int, int, byte[], int) compress(src, 0, src.length, dest, 0)}. + * + * @param src the source data + * @param dest the destination buffer + * @throws LZ4Exception if dest is too small + * @return the compressed size + */ + public final int compress(byte[] src, byte[] dest) { + return compress(src, 0, src.length, dest, 0); + } + + /** + * Convenience method which returns src[srcOff:srcOff+srcLen] + * compressed. + *

Warning: this method has an + * important overhead due to the fact that it needs to allocate a buffer to + * compress into, and then needs to resize this buffer to the actual + * compressed length.

+ *

Here is how this method is implemented:

+ *
+   * final int maxCompressedLength = maxCompressedLength(srcLen);
+   * final byte[] compressed = new byte[maxCompressedLength];
+   * final int compressedLength = compress(src, srcOff, srcLen, compressed, 0);
+   * return Arrays.copyOf(compressed, compressedLength);
+   * 
+ * + * @param src the source data + * @param srcOff the start offset in src + * @param srcLen the number of bytes to compress + * @return the compressed data + */ + public final byte[] compress(byte[] src, int srcOff, int srcLen) { + final int maxCompressedLength = maxCompressedLength(srcLen); + final byte[] compressed = new byte[maxCompressedLength]; + final int compressedLength = compress(src, srcOff, srcLen, compressed, 0); + return Arrays.copyOf(compressed, compressedLength); + } + + /** + * Convenience method, equivalent to calling + * {@link #compress(byte[], int, int) compress(src, 0, src.length)}. + * + * @param src the source data + * @return the compressed data + */ + public final byte[] compress(byte[] src) { + return compress(src, 0, src.length); + } + + /** + * Compresses src into dest. Calling this method + * will update the positions of both {@link ByteBuffer}s. + * + * @param src the source data + * @param dest the destination buffer + * @throws LZ4Exception if dest is too small + */ + public final void compress(ByteBuffer src, ByteBuffer dest) { + final int cpLen = compress(src, src.position(), src.remaining(), dest, dest.position(), dest.remaining()); + src.position(src.limit()); + dest.position(dest.position() + cpLen); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Constants.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Constants.java new file mode 100644 index 000000000..7295dbf80 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Constants.java @@ -0,0 +1,53 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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. + */ + + +enum LZ4Constants { + ; + + static final int DEFAULT_COMPRESSION_LEVEL = 8+1; + static final int MAX_COMPRESSION_LEVEL = 16+1; + + static final int MEMORY_USAGE = 14; + static final int NOT_COMPRESSIBLE_DETECTION_LEVEL = 6; + + static final int MIN_MATCH = 4; + + static final int HASH_LOG = MEMORY_USAGE - 2; + static final int HASH_TABLE_SIZE = 1 << HASH_LOG; + + static final int SKIP_STRENGTH = Math.max(NOT_COMPRESSIBLE_DETECTION_LEVEL, 2); + static final int COPY_LENGTH = 8; + static final int LAST_LITERALS = 5; + static final int MF_LIMIT = COPY_LENGTH + MIN_MATCH; + static final int MIN_LENGTH = MF_LIMIT + 1; + + static final int MAX_DISTANCE = 1 << 16; + + static final int ML_BITS = 4; + static final int ML_MASK = (1 << ML_BITS) - 1; + static final int RUN_BITS = 8 - ML_BITS; + static final int RUN_MASK = (1 << RUN_BITS) - 1; + + static final int LZ4_64K_LIMIT = (1 << 16) + (MF_LIMIT - 1); + static final int HASH_LOG_64K = HASH_LOG + 1; + static final int HASH_TABLE_SIZE_64K = 1 << HASH_LOG_64K; + + static final int HASH_LOG_HC = 15; + static final int HASH_TABLE_SIZE_HC = 1 << HASH_LOG_HC; + static final int OPTIMAL_ML = ML_MASK - 1 + MIN_MATCH; + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Decompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Decompressor.java new file mode 100644 index 000000000..05e98c10f --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Decompressor.java @@ -0,0 +1,25 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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. + */ + +/** + * @deprecated Use {@link LZ4FastDecompressor} instead. + */ +@Deprecated +public interface LZ4Decompressor { + + int decompress(byte[] src, int srcOff, byte[] dest, int destOff, int destLen); + +} \ No newline at end of file diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Exception.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Exception.java new file mode 100644 index 000000000..78355d46f --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Exception.java @@ -0,0 +1,36 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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. + */ + +/** + * LZ4 compression or decompression error. + */ +public class LZ4Exception extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public LZ4Exception(String msg, Throwable t) { + super(msg, t); + } + + public LZ4Exception(String msg) { + super(msg); + } + + public LZ4Exception() { + super(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Factory.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Factory.java new file mode 100644 index 000000000..c0e6df16a --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Factory.java @@ -0,0 +1,309 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +import com.fr.third.net.jpountz.util.Native; +import com.fr.third.net.jpountz.util.Utils; + +/** + * Entry point for the LZ4 API. + *

+ * This class has 3 instances

+ *

+ * Only the {@link #safeInstance() safe instance} is guaranteed to work on your + * JVM, as a consequence it is advised to use the {@link #fastestInstance()} or + * {@link #fastestJavaInstance()} to pull a {@link LZ4Factory} instance. + *

+ * All methods from this class are very costly, so you should get an instance + * once, and then reuse it whenever possible. This is typically done by storing + * a {@link LZ4Factory} instance in a static field. + */ +public final class LZ4Factory { + + private static LZ4Factory instance(String impl) { + try { + return new LZ4Factory(impl); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private static LZ4Factory NATIVE_INSTANCE, + JAVA_UNSAFE_INSTANCE, + JAVA_SAFE_INSTANCE; + + /** + * Returns a {@link LZ4Factory} instance that returns compressors and + * decompressors that are native bindings to the original C library. + *

+ * Please note that this instance has some traps you should be aware of:

    + *
  1. Upon loading this instance, files will be written to the temporary + * directory of the system. Although these files are supposed to be deleted + * when the JVM exits, they might remain on systems that don't support + * removal of files being used such as Windows. + *
  2. The instance can only be loaded once per JVM. This can be a problem + * if your application uses multiple class loaders (such as most servlet + * containers): this instance will only be available to the children of the + * class loader which has loaded it. As a consequence, it is advised to + * either not use this instance in webapps or to put this library in the lib + * directory of your servlet container so that it is loaded by the system + * class loader. + *
+ * + * @return a {@link LZ4Factory} instance that returns compressors and + * decompressors that are native bindings to the original C library + */ + public static synchronized LZ4Factory nativeInstance() { + if (NATIVE_INSTANCE == null) { + NATIVE_INSTANCE = instance("JNI"); + } + return NATIVE_INSTANCE; + } + + /** + * Returns a {@link LZ4Factory} instance that returns compressors and + * decompressors that are written with Java's official API. + * + * @return a {@link LZ4Factory} instance that returns compressors and + * decompressors that are written with Java's official API. + */ + public static synchronized LZ4Factory safeInstance() { + if (JAVA_SAFE_INSTANCE == null) { + JAVA_SAFE_INSTANCE = instance("JavaSafe"); + } + return JAVA_SAFE_INSTANCE; + } + + /** + * Returns a {@link LZ4Factory} instance that returns compressors and + * decompressors that may use {@link sun.misc.Unsafe} to speed up compression + * and decompression. + * + * @return a {@link LZ4Factory} instance that returns compressors and + * decompressors that may use {@link sun.misc.Unsafe} to speed up compression + * and decompression. + */ + public static synchronized LZ4Factory unsafeInstance() { + if (JAVA_UNSAFE_INSTANCE == null) { + JAVA_UNSAFE_INSTANCE = instance("JavaUnsafe"); + } + return JAVA_UNSAFE_INSTANCE; + } + + /** + * Returns the fastest available {@link LZ4Factory} instance which does not + * rely on JNI bindings. It first tries to load the + * {@link #unsafeInstance() unsafe instance}, and then the + * {@link #safeInstance() safe Java instance} if the JVM doesn't have a + * working {@link sun.misc.Unsafe}. + * + * @return the fastest available {@link LZ4Factory} instance which does not + * rely on JNI bindings. + */ + public static LZ4Factory fastestJavaInstance() { + if (Utils.isUnalignedAccessAllowed()) { + try { + return unsafeInstance(); + } catch (Throwable t) { + return safeInstance(); + } + } else { + return safeInstance(); + } + } + + /** + * Returns the fastest available {@link LZ4Factory} instance. If the class + * loader is the system class loader and if the + * {@link #nativeInstance() native instance} loads successfully, then the + * {@link #nativeInstance() native instance} is returned, otherwise the + * {@link #fastestJavaInstance() fastest Java instance} is returned. + *

+ * Please read {@link #nativeInstance() javadocs of nativeInstance()} before + * using this method. + * + * @return the fastest available {@link LZ4Factory} instance + */ + public static LZ4Factory fastestInstance() { + if (Native.isLoaded() + || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader()) { + try { + return nativeInstance(); + } catch (Throwable t) { + return fastestJavaInstance(); + } + } else { + return fastestJavaInstance(); + } + } + + @SuppressWarnings("unchecked") + private static T classInstance(String cls) throws NoSuchFieldException, SecurityException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException { + ClassLoader loader = LZ4Factory.class.getClassLoader(); + loader = loader == null ? ClassLoader.getSystemClassLoader() : loader; + final Class c = loader.loadClass(cls); + Field f = c.getField("INSTANCE"); + return (T) f.get(null); + } + + private final String impl; + private final LZ4Compressor fastCompressor; + private final LZ4Compressor highCompressor; + private final LZ4FastDecompressor fastDecompressor; + private final LZ4SafeDecompressor safeDecompressor; + private final LZ4Compressor[] highCompressors = new LZ4Compressor[LZ4Constants.MAX_COMPRESSION_LEVEL+1]; + + private LZ4Factory(String impl) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException { + this.impl = impl; + fastCompressor = classInstance("com.fr.third.net.jpountz.lz4.LZ4" + impl + "Compressor"); + highCompressor = classInstance("com.fr.third.net.jpountz.lz4.LZ4HC" + impl + "Compressor"); + fastDecompressor = classInstance("com.fr.third.net.jpountz.lz4.LZ4" + impl + "FastDecompressor"); + safeDecompressor = classInstance("com.fr.third.net.jpountz.lz4.LZ4" + impl + "SafeDecompressor"); + Constructor highConstructor = highCompressor.getClass().getDeclaredConstructor(int.class); + highCompressors[LZ4Constants.DEFAULT_COMPRESSION_LEVEL] = highCompressor; + for(int level = 1; level <= LZ4Constants.MAX_COMPRESSION_LEVEL; level++) { + if(level == LZ4Constants.DEFAULT_COMPRESSION_LEVEL) continue; + highCompressors[level] = highConstructor.newInstance(level); + } + + // quickly test that everything works as expected + final byte[] original = new byte[] {'a','b','c','d',' ',' ',' ',' ',' ',' ','a','b','c','d','e','f','g','h','i','j'}; + for (LZ4Compressor compressor : Arrays.asList(fastCompressor, highCompressor)) { + final int maxCompressedLength = compressor.maxCompressedLength(original.length); + final byte[] compressed = new byte[maxCompressedLength]; + final int compressedLength = compressor.compress(original, 0, original.length, compressed, 0, maxCompressedLength); + final byte[] restored = new byte[original.length]; + fastDecompressor.decompress(compressed, 0, restored, 0, original.length); + if (!Arrays.equals(original, restored)) { + throw new AssertionError(); + } + Arrays.fill(restored, (byte) 0); + final int decompressedLength = safeDecompressor.decompress(compressed, 0, compressedLength, restored, 0); + if (decompressedLength != original.length || !Arrays.equals(original, restored)) { + throw new AssertionError(); + } + } + + } + + /** + * Returns a blazing fast {@link LZ4Compressor}. + * + * @return a blazing fast {@link LZ4Compressor} + */ + public LZ4Compressor fastCompressor() { + return fastCompressor; + } + + /** + * Returns a {@link LZ4Compressor} which requires more memory than + * {@link #fastCompressor()} and is slower but compresses more efficiently. + * + * @return a {@link LZ4Compressor} which requires more memory than + * {@link #fastCompressor()} and is slower but compresses more efficiently. + */ + public LZ4Compressor highCompressor() { + return highCompressor; + } + + /** + * Returns a {@link LZ4Compressor} which requires more memory than + * {@link #fastCompressor()} and is slower but compresses more efficiently. + * The compression level can be customized. + *

For current implementations, the following is true about compression level:

    + *
  1. It should be in range [1, 17]
  2. + *
  3. A compression level higher than 17 would be treated as 17.
  4. + *
  5. A compression level lower than 1 would be treated as 9.
  6. + *
+ * + * @param compressionLevel the compression level between [1, 17]; the higher the level, the higher the compression ratio + * @return a {@link LZ4Compressor} which requires more memory than + * {@link #fastCompressor()} and is slower but compresses more efficiently. + */ + public LZ4Compressor highCompressor(int compressionLevel) { + if(compressionLevel > LZ4Constants.MAX_COMPRESSION_LEVEL) { + compressionLevel = LZ4Constants.MAX_COMPRESSION_LEVEL; + } else if (compressionLevel < 1) { + compressionLevel = LZ4Constants.DEFAULT_COMPRESSION_LEVEL; + } + return highCompressors[compressionLevel]; + } + + /** + * Returns a {@link LZ4FastDecompressor} instance. + * + * @return a {@link LZ4FastDecompressor} instance + */ + public LZ4FastDecompressor fastDecompressor() { + return fastDecompressor; + } + + /** + * Returns a {@link LZ4SafeDecompressor} instance. + * + * @return a {@link LZ4SafeDecompressor} instance + */ + public LZ4SafeDecompressor safeDecompressor() { + return safeDecompressor; + } + + /** + * Returns a {@link LZ4UnknownSizeDecompressor} instance. + * @deprecated use {@link #safeDecompressor()} + * + * @return a {@link LZ4UnknownSizeDecompressor} instance + */ + public LZ4UnknownSizeDecompressor unknownSizeDecompressor() { + return safeDecompressor(); + } + + /** + * Returns a {@link LZ4Decompressor} instance. + * @deprecated use {@link #fastDecompressor()} + * + * @return a {@link LZ4Decompressor} instance + */ + public LZ4Decompressor decompressor() { + return fastDecompressor(); + } + + /** + * Prints the fastest instance. + * + * @param args no argument required + */ + public static void main(String[] args) { + System.out.println("Fastest instance is " + fastestInstance()); + System.out.println("Fastest Java instance is " + fastestJavaInstance()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + impl; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FastDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FastDecompressor.java new file mode 100644 index 000000000..88e9559c6 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FastDecompressor.java @@ -0,0 +1,135 @@ +package com.fr.third.net.jpountz.lz4; + +import java.nio.ByteBuffer; + +/* + * 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. + */ + +/** + * LZ4 decompressor that requires the size of the original input to be known. + * Use {@link LZ4SafeDecompressor} if you only know the size of the + * compressed stream. + *

+ * Instances of this class are thread-safe. + */ +public abstract class LZ4FastDecompressor implements LZ4Decompressor { + + /** Decompresses src[srcOff:] into dest[destOff:destOff+destLen] + * and returns the number of bytes read from src. + * destLen must be exactly the size of the decompressed data. + * + * @param src the compressed data + * @param srcOff the start offset in src + * @param dest the destination buffer to store the decompressed data + * @param destOff the start offset in dest + * @param destLen the exact size of the original input + * @return the number of bytes read to restore the original input + */ + public abstract int decompress(byte[] src, int srcOff, byte[] dest, int destOff, int destLen); + + /** Decompresses src[srcOff:] into dest[destOff:destOff+destLen] + * and returns the number of bytes read from src. + * destLen must be exactly the size of the decompressed data. + * The positions and limits of the {@link ByteBuffer}s remain unchanged. + * + * @param src the compressed data + * @param srcOff the start offset in src + * @param dest the destination buffer to store the decompressed data + * @param destOff the start offset in dest + * @param destLen the exact size of the original input + * @return the number of bytes read to restore the original input + */ + public abstract int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff, int destLen); + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], int, byte[], int, int) decompress(src, 0, dest, 0, destLen)}. + * + * @param src the compressed data + * @param dest the destination buffer to store the decompressed data + * @param destLen the exact size of the original input + * @return the number of bytes read to restore the original input + */ + public final int decompress(byte[] src, byte[] dest, int destLen) { + return decompress(src, 0, dest, 0, destLen); + } + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], byte[], int) decompress(src, dest, dest.length)}. + * + * @param src the compressed data + * @param dest the destination buffer to store the decompressed data + * @return the number of bytes read to restore the original input + */ + public final int decompress(byte[] src, byte[] dest) { + return decompress(src, dest, dest.length); + } + + /** + * Convenience method which returns src[srcOff:?] + * decompressed. + *

Warning: this method has an + * important overhead due to the fact that it needs to allocate a buffer to + * decompress into.

+ *

Here is how this method is implemented:

+ *
+   * final byte[] decompressed = new byte[destLen];
+   * decompress(src, srcOff, decompressed, 0, destLen);
+   * return decompressed;
+   * 
+ * + * @param src the compressed data + * @param srcOff the start offset in src + * @param destLen the exact size of the original input + * @return the decompressed data + */ + public final byte[] decompress(byte[] src, int srcOff, int destLen) { + final byte[] decompressed = new byte[destLen]; + decompress(src, srcOff, decompressed, 0, destLen); + return decompressed; + } + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], int, int) decompress(src, 0, destLen)}. + * + * @param src the compressed data + * @param destLen the exact size of the original input + * @return the decompressed data + */ + public final byte[] decompress(byte[] src, int destLen) { + return decompress(src, 0, destLen); + } + + /** + * Decompresses src into dest. dest's + * {@link ByteBuffer#remaining()} must be exactly the size of the decompressed + * data. This method moves the positions of the buffers. + * + * @param src the compressed data + * @param dest the destination buffer to store the decompressed data + */ + public final void decompress(ByteBuffer src, ByteBuffer dest) { + final int read = decompress(src, src.position(), dest, dest.position(), dest.remaining()); + dest.position(dest.limit()); + src.position(src.position() + read); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameInputStream.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameInputStream.java new file mode 100644 index 000000000..dda725536 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameInputStream.java @@ -0,0 +1,351 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.xxhash.XXHash32; +import com.fr.third.net.jpountz.xxhash.XXHashFactory; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Locale; + +/** + * Implementation of the v1.5.1 LZ4 Frame format. This class is NOT thread safe. + *

+ * Not Supported:

    + *
  • Dependent blocks
  • + *
  • Legacy streams
  • + *
+ *

+ * Originally based on kafka's KafkaLZ4BlockInputStream. + * + * @see LZ4 Framing Format Spec 1.5.1 + */ +public class LZ4FrameInputStream extends FilterInputStream { + + static final String PREMATURE_EOS = "Stream ended prematurely"; + static final String NOT_SUPPORTED = "Stream unsupported"; + static final String BLOCK_HASH_MISMATCH = "Block checksum mismatch"; + static final String DESCRIPTOR_HASH_MISMATCH = "Stream frame descriptor corrupted"; + static final int MAGIC_SKIPPABLE_BASE = 0x184D2A50; + + private final LZ4SafeDecompressor decompressor; + private final XXHash32 checksum; + private final byte[] headerArray = new byte[LZ4FrameOutputStream.LZ4_MAX_HEADER_LENGTH]; + private final ByteBuffer headerBuffer = ByteBuffer.wrap(headerArray).order(ByteOrder.LITTLE_ENDIAN); + private byte[] compressedBuffer; + private ByteBuffer buffer = null; + private byte[] rawBuffer = null; + private int maxBlockSize = -1; + private long expectedContentSize = -1L; + private long totalContentSize = 0L; + + private LZ4FrameOutputStream.FrameInfo frameInfo = null; + + /** + * Creates a new {@link InputStream} that will decompress data using fastest instances of {@link LZ4SafeDecompressor} and {@link XXHash32}. + * + * @param in the stream to decompress + * @throws IOException if an I/O error occurs + * + * @see #LZ4FrameInputStream(InputStream, LZ4SafeDecompressor, XXHash32) + * @see LZ4Factory#fastestInstance() + * @see XXHashFactory#fastestInstance() + */ + public LZ4FrameInputStream(InputStream in) throws IOException { + this(in, LZ4Factory.fastestInstance().safeDecompressor(), XXHashFactory.fastestInstance().hash32()); + } + + /** + * Creates a new {@link InputStream} that will decompress data using the LZ4 algorithm. + * + * @param in the stream to decompress + * @param decompressor the decompressor to use + * @param checksum the hash function to use + * @throws IOException if an I/O error occurs + */ + public LZ4FrameInputStream(InputStream in, LZ4SafeDecompressor decompressor, XXHash32 checksum) throws IOException { + super(in); + this.decompressor = decompressor; + this.checksum = checksum; + nextFrameInfo(); + } + + + + /** + * Try and load in the next valid frame info. This will skip over skippable frames. + * @return True if a frame was loaded. False if there are no more frames in the stream. + * @throws IOException On input stream read exception + */ + private boolean nextFrameInfo() throws IOException { + while (true) { + int size = 0; + do { + final int mySize = in.read(readNumberBuff.array(), size, LZ4FrameOutputStream.INTEGER_BYTES - size); + if (mySize < 0) { + return false; + } + size += mySize; + } while (size < LZ4FrameOutputStream.INTEGER_BYTES); + final int magic = readNumberBuff.getInt(0); + if (magic == LZ4FrameOutputStream.MAGIC) { + readHeader(); + return true; + } else if ((magic >>> 4) == (MAGIC_SKIPPABLE_BASE >>> 4)) { + skippableFrame(); + } else { + throw new IOException(NOT_SUPPORTED); + } + } + } + + private void skippableFrame() throws IOException { + int skipSize = readInt(in); + final byte[] skipBuffer = new byte[1 << 10]; + while (skipSize > 0) { + final int mySize = in.read(skipBuffer, 0, Math.min(skipSize, skipBuffer.length)); + if (mySize < 0) { + throw new IOException(PREMATURE_EOS); + } + skipSize -= mySize; + } + } + + /** + * Reads the frame descriptor from the underlying {@link InputStream}. + * + * @throws IOException + */ + private void readHeader() throws IOException { + headerBuffer.rewind(); + + final int flgRead = in.read(); + if (flgRead < 0) { + throw new IOException(PREMATURE_EOS); + } + final int bdRead = in.read(); + if (bdRead < 0) { + throw new IOException(PREMATURE_EOS); + } + + final byte flgByte = (byte)(flgRead & 0xFF); + final LZ4FrameOutputStream.FLG flg = LZ4FrameOutputStream.FLG.fromByte(flgByte); + headerBuffer.put(flgByte); + final byte bdByte = (byte)(bdRead & 0xFF); + final LZ4FrameOutputStream.BD bd = LZ4FrameOutputStream.BD.fromByte(bdByte); + headerBuffer.put(bdByte); + + this.frameInfo = new LZ4FrameOutputStream.FrameInfo(flg, bd); + + if (flg.isEnabled(LZ4FrameOutputStream.FLG.Bits.CONTENT_SIZE)) { + expectedContentSize = readLong(in); + headerBuffer.putLong(expectedContentSize); + } + totalContentSize = 0L; + + // check stream descriptor hash + final byte hash = (byte) ((checksum.hash(headerArray, 0, headerBuffer.position(), 0) >> 8) & 0xFF); + final int expectedHash = in.read(); + if (expectedHash < 0) { + throw new IOException(PREMATURE_EOS); + } + + if (hash != (byte)(expectedHash & 0xFF)) { + throw new IOException(DESCRIPTOR_HASH_MISMATCH); + } + + maxBlockSize = frameInfo.getBD().getBlockMaximumSize(); + compressedBuffer = new byte[maxBlockSize]; // Reused during different compressions + rawBuffer = new byte[maxBlockSize]; + buffer = ByteBuffer.wrap(rawBuffer); + buffer.limit(0); + } + + private final ByteBuffer readNumberBuff = ByteBuffer.allocate(LZ4FrameOutputStream.LONG_BYTES).order(ByteOrder.LITTLE_ENDIAN); + + private long readLong(InputStream stream) throws IOException { + int offset = 0; + do { + final int mySize = stream.read(readNumberBuff.array(), offset, LZ4FrameOutputStream.LONG_BYTES - offset); + if (mySize < 0) { + throw new IOException(PREMATURE_EOS); + } + offset += mySize; + } while (offset < LZ4FrameOutputStream.LONG_BYTES); + return readNumberBuff.getLong(0); + } + + private int readInt(InputStream stream) throws IOException { + int offset = 0; + do { + final int mySize = stream.read(readNumberBuff.array(), offset, LZ4FrameOutputStream.INTEGER_BYTES - offset); + if (mySize < 0) { + throw new IOException(PREMATURE_EOS); + } + offset += mySize; + } while (offset < LZ4FrameOutputStream.INTEGER_BYTES); + return readNumberBuff.getInt(0); + } + + /** + * Decompress (if necessary) buffered data, optionally computes and validates a XXHash32 checksum, and writes the + * result to a buffer. + * + * @throws IOException + */ + private void readBlock() throws IOException { + int blockSize = readInt(in); + final boolean compressed = (blockSize & LZ4FrameOutputStream.LZ4_FRAME_INCOMPRESSIBLE_MASK) == 0; + blockSize &= ~LZ4FrameOutputStream.LZ4_FRAME_INCOMPRESSIBLE_MASK; + + // Check for EndMark + if (blockSize == 0) { + if (frameInfo.isEnabled(LZ4FrameOutputStream.FLG.Bits.CONTENT_CHECKSUM)) { + final int contentChecksum = readInt(in); + if (contentChecksum != frameInfo.currentStreamHash()) { + throw new IOException("Content checksum mismatch"); + } + } + if (frameInfo.isEnabled(LZ4FrameOutputStream.FLG.Bits.CONTENT_SIZE) && expectedContentSize != totalContentSize) { + throw new IOException("Size check mismatch"); + } + frameInfo.finish(); + return; + } + + final byte[] tmpBuffer; // Use a temporary buffer, potentially one used for compression + if (compressed) { + tmpBuffer = compressedBuffer; + } else { + tmpBuffer = rawBuffer; + } + if (blockSize > maxBlockSize) { + throw new IOException(String.format(Locale.ROOT, "Block size %s exceeded max: %s", blockSize, maxBlockSize)); + } + + int offset = 0; + while (offset < blockSize) { + final int lastRead = in.read(tmpBuffer, offset, blockSize - offset); + if (lastRead < 0) { + throw new IOException(PREMATURE_EOS); + } + offset += lastRead; + } + + // verify block checksum + if (frameInfo.isEnabled(LZ4FrameOutputStream.FLG.Bits.BLOCK_CHECKSUM)) { + final int hashCheck = readInt(in); + if (hashCheck != checksum.hash(tmpBuffer, 0, blockSize, 0)) { + throw new IOException(BLOCK_HASH_MISMATCH); + } + } + + final int currentBufferSize; + if (compressed) { + try { + currentBufferSize = decompressor.decompress(tmpBuffer, 0, blockSize, rawBuffer, 0, rawBuffer.length); + } catch (LZ4Exception e) { + throw new IOException(e); + } + } else { + currentBufferSize = blockSize; + } + if (frameInfo.isEnabled(LZ4FrameOutputStream.FLG.Bits.CONTENT_CHECKSUM)) { + frameInfo.updateStreamHash(rawBuffer, 0, currentBufferSize); + } + totalContentSize += currentBufferSize; + buffer.limit(currentBufferSize); + buffer.rewind(); + } + + @Override + public int read() throws IOException { + while (buffer.remaining() == 0) { + if (frameInfo.isFinished()) { + if (!nextFrameInfo()) { + return -1; + } + } + readBlock(); + } + return (int)buffer.get() & 0xFF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if ((off < 0) || (len < 0) || (off + len > b.length)) { + throw new IndexOutOfBoundsException(); + } + while (buffer.remaining() == 0) { + if (frameInfo.isFinished()) { + if (!nextFrameInfo()) { + return -1; + } + } + readBlock(); + } + len = Math.min(len, buffer.remaining()); + buffer.get(b, off, len); + return len; + } + + @Override + public long skip(long n) throws IOException { + if (n <= 0) { + return 0; + } + while (buffer.remaining() == 0) { + if (frameInfo.isFinished()) { + if (!nextFrameInfo()) { + return 0; + } + } + readBlock(); + } + n = Math.min(n, buffer.remaining()); + buffer.position(buffer.position() + (int)n); + return n; + } + + @Override + public int available() throws IOException { + return buffer.remaining(); + } + + @Override + public void close() throws IOException { + super.close(); + } + + @Override + public synchronized void mark(int readlimit) { + throw new UnsupportedOperationException("mark not supported"); + } + + @Override + public synchronized void reset() throws IOException { + throw new UnsupportedOperationException("reset not supported"); + } + + @Override + public boolean markSupported() { + return false; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameOutputStream.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameOutputStream.java new file mode 100644 index 000000000..a1ec09add --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4FrameOutputStream.java @@ -0,0 +1,434 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.xxhash.StreamingXXHash32; +import com.fr.third.net.jpountz.xxhash.XXHash32; +import com.fr.third.net.jpountz.xxhash.XXHashFactory; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Locale; + +/** + * Implementation of the v1.5.1 LZ4 Frame format. This class is NOT thread safe. + *

+ * Not Supported:

    + *
  • Dependent blocks
  • + *
  • Legacy streams
  • + *
  • Multiple frames (one LZ4FrameOutputStream is one frame)
  • + *
+ *

+ * Originally based on kafka's KafkaLZ4BlockOutputStream. + * + * @see LZ4 Framing Format Spec 1.5.1 + */ +public class LZ4FrameOutputStream extends FilterOutputStream { + + static final int INTEGER_BYTES = Integer.SIZE >>> 3; // or Integer.BYTES in Java 1.8 + static final int LONG_BYTES = Long.SIZE >>> 3; // or Long.BYTES in Java 1.8 + + static final int MAGIC = 0x184D2204; + static final int LZ4_MAX_HEADER_LENGTH = + 4 + // magic + 1 + // FLG + 1 + // BD + 8 + // Content Size + 1; // HC + static final int LZ4_FRAME_INCOMPRESSIBLE_MASK = 0x80000000; + static final FLG.Bits[] DEFAULT_FEATURES = new FLG.Bits[]{FLG.Bits.BLOCK_INDEPENDENCE}; + + static final String CLOSED_STREAM = "The stream is already closed"; + + public static enum BLOCKSIZE { + SIZE_64KB(4), SIZE_256KB(5), SIZE_1MB(6), SIZE_4MB(7); + private final int indicator; + BLOCKSIZE(int indicator) { + this.indicator = indicator; + } + public int getIndicator() { + return this.indicator; + } + public static BLOCKSIZE valueOf(int indicator) { + switch(indicator) { + case 7: return SIZE_4MB; + case 6: return SIZE_1MB; + case 5: return SIZE_256KB; + case 4: return SIZE_64KB; + default: throw new IllegalArgumentException(String.format(Locale.ROOT, "Block size must be 4-7. Cannot use value of [%d]", indicator)); + } + } + } + + private final LZ4Compressor compressor; + private final XXHash32 checksum; + private final ByteBuffer buffer; // Buffer for uncompressed input data + private final byte[] compressedBuffer; // Only allocated once so it can be reused + private final int maxBlockSize; + private final long knownSize; + private final ByteBuffer intLEBuffer = ByteBuffer.allocate(INTEGER_BYTES).order(ByteOrder.LITTLE_ENDIAN); + + private FrameInfo frameInfo = null; + + + /** + * Creates a new {@link OutputStream} that will compress data of unknown size using the LZ4 algorithm. + * + * @param out the output stream to compress + * @param blockSize the BLOCKSIZE to use + * @param bits a set of features to use + * @throws IOException if an I/O error occurs + * + * @see #LZ4FrameOutputStream(OutputStream, BLOCKSIZE, long, FLG.Bits...) + */ + public LZ4FrameOutputStream(OutputStream out, BLOCKSIZE blockSize, FLG.Bits... bits) throws IOException { + this(out, blockSize, -1L, bits); + } + + /** + * Creates a new {@link OutputStream} that will compress data using using fastest instances of {@link LZ4Compressor} and {@link XXHash32}. + * + * @param out the output stream to compress + * @param blockSize the BLOCKSIZE to use + * @param knownSize the size of the uncompressed data. A value less than zero means unknown. + * @param bits a set of features to use + * @throws IOException if an I/O error occurs + */ + public LZ4FrameOutputStream(OutputStream out, BLOCKSIZE blockSize, long knownSize, FLG.Bits... bits) throws IOException { + super(out); + compressor = LZ4Factory.fastestInstance().fastCompressor(); + checksum = XXHashFactory.fastestInstance().hash32(); + frameInfo = new FrameInfo(new FLG(FLG.DEFAULT_VERSION, bits), new BD(blockSize)); + maxBlockSize = frameInfo.getBD().getBlockMaximumSize(); + buffer = ByteBuffer.allocate(maxBlockSize).order(ByteOrder.LITTLE_ENDIAN); + compressedBuffer = new byte[compressor.maxCompressedLength(maxBlockSize)]; + if (frameInfo.getFLG().isEnabled(FLG.Bits.CONTENT_SIZE) && knownSize < 0) { + throw new IllegalArgumentException("Known size must be greater than zero in order to use the known size feature"); + } + this.knownSize = knownSize; + writeHeader(); + } + + /** + * Creates a new {@link OutputStream} that will compress data using the LZ4 algorithm. The block independence flag is set, and none of the other flags are set. + * + * @param out The stream to compress + * @param blockSize the BLOCKSIZE to use + * @throws IOException if an I/O error occurs + * + * @see #LZ4FrameOutputStream(OutputStream, BLOCKSIZE, FLG.Bits...) + */ + public LZ4FrameOutputStream(OutputStream out, BLOCKSIZE blockSize) throws IOException { + this(out, blockSize, DEFAULT_FEATURES); + } + + /** + * Creates a new {@link OutputStream} that will compress data using the LZ4 algorithm with 4-MB blocks. + * + * @param out the output stream to compress + * @throws IOException if an I/O error occurs + * + * @see #LZ4FrameOutputStream(OutputStream, BLOCKSIZE) + */ + public LZ4FrameOutputStream(OutputStream out) throws IOException { + this(out, BLOCKSIZE.SIZE_4MB); + } + + /** + * Writes the magic number and frame descriptor to the underlying {@link OutputStream}. + * + * @throws IOException + */ + private void writeHeader() throws IOException { + final ByteBuffer headerBuffer = ByteBuffer.allocate(LZ4_MAX_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + headerBuffer.putInt(MAGIC); + headerBuffer.put(frameInfo.getFLG().toByte()); + headerBuffer.put(frameInfo.getBD().toByte()); + if (frameInfo.isEnabled(FLG.Bits.CONTENT_SIZE)) { + headerBuffer.putLong(knownSize); + } + // compute checksum on all descriptor fields + final int hash = (checksum.hash(headerBuffer.array(), INTEGER_BYTES, headerBuffer.position() - INTEGER_BYTES, 0) >> 8) & 0xFF; + headerBuffer.put((byte) hash); + // write out frame descriptor + out.write(headerBuffer.array(), 0, headerBuffer.position()); + } + + /** + * Compresses buffered data, optionally computes an XXHash32 checksum, and writes the result to the underlying + * {@link OutputStream}. + * + * @throws IOException + */ + private void writeBlock() throws IOException { + if (buffer.position() == 0) { + return; + } + // Make sure there's no stale data + Arrays.fill(compressedBuffer, (byte) 0); + + int compressedLength = compressor.compress(buffer.array(), 0, buffer.position(), compressedBuffer, 0); + final byte[] bufferToWrite; + final int compressMethod; + + // Store block uncompressed if compressed length is greater (incompressible) + if (compressedLength >= buffer.position()) { + compressedLength = buffer.position(); + bufferToWrite = Arrays.copyOf(buffer.array(), compressedLength); + compressMethod = LZ4_FRAME_INCOMPRESSIBLE_MASK; + } else { + bufferToWrite = compressedBuffer; + compressMethod = 0; + } + + // Write content + intLEBuffer.putInt(0, compressedLength | compressMethod); + out.write(intLEBuffer.array()); + out.write(bufferToWrite, 0, compressedLength); + + // Calculate and write block checksum + if (frameInfo.isEnabled(FLG.Bits.BLOCK_CHECKSUM)) { + intLEBuffer.putInt(0, checksum.hash(bufferToWrite, 0, compressedLength, 0)); + out.write(intLEBuffer.array()); + } + buffer.rewind(); + } + + /** + * Similar to the {@link #writeBlock()} method. Writes a 0-length block (without block checksum) to signal the end + * of the block stream. + * + * @throws IOException + */ + private void writeEndMark() throws IOException { + intLEBuffer.putInt(0, 0); + out.write(intLEBuffer.array()); + if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { + intLEBuffer.putInt(0, frameInfo.currentStreamHash()); + out.write(intLEBuffer.array()); + } + frameInfo.finish(); + } + + @Override + public void write(int b) throws IOException { + ensureNotFinished(); + if (buffer.position() == maxBlockSize) { + writeBlock(); + } + buffer.put((byte) b); + + if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { + frameInfo.updateStreamHash(new byte[]{(byte) b}, 0, 1); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if ((off < 0) || (len < 0) || (off + len > b.length)) { + throw new IndexOutOfBoundsException(); + } + ensureNotFinished(); + + // while b will fill the buffer + while (len > buffer.remaining()) { + int sizeWritten = buffer.remaining(); + // fill remaining space in buffer + buffer.put(b, off, sizeWritten); + if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { + frameInfo.updateStreamHash(b, off, sizeWritten); + } + writeBlock(); + // compute new offset and length + off += sizeWritten; + len -= sizeWritten; + } + buffer.put(b, off, len); + + if (frameInfo.isEnabled(FLG.Bits.CONTENT_CHECKSUM)) { + frameInfo.updateStreamHash(b, off, len); + } + } + + @Override + public void flush() throws IOException { + if (!frameInfo.isFinished()) { + writeBlock(); + } + super.flush(); + } + + /** + * A simple state check to ensure the stream is still open. + */ + private void ensureNotFinished() { + if (frameInfo.isFinished()) { + throw new IllegalStateException(CLOSED_STREAM); + } + } + + @Override + public void close() throws IOException { + if (!frameInfo.isFinished()) { + flush(); + writeEndMark(); + } + super.close(); + } + + public static class FLG { + private static final int DEFAULT_VERSION = 1; + + private final BitSet bitSet; + private final int version; + + public enum Bits { + RESERVED_0(0), + RESERVED_1(1), + CONTENT_CHECKSUM(2), + CONTENT_SIZE(3), + BLOCK_CHECKSUM(4), + BLOCK_INDEPENDENCE(5); + + private final int position; + Bits(int position) { + this.position = position; + } + } + + public FLG(int version, Bits... bits) { + this.bitSet = new BitSet(8); + this.version = version; + if (bits != null) { + for (Bits bit : bits) { + bitSet.set(bit.position); + } + } + validate(); + } + + private FLG(int version, byte b) { + this.bitSet = BitSet.valueOf(new byte[]{b}); + this.version = version; + validate(); + } + + public static FLG fromByte(byte flg) { + final byte versionMask = (byte)(flg & (3 << 6)); + return new FLG(versionMask >>> 6, (byte) (flg ^ versionMask)); + } + + public byte toByte() { + return (byte)(bitSet.toByteArray()[0] | ((version & 3) << 6)); + } + + private void validate() { + if (bitSet.get(Bits.RESERVED_0.position)) { + throw new RuntimeException("Reserved0 field must be 0"); + } + if (bitSet.get(Bits.RESERVED_1.position)) { + throw new RuntimeException("Reserved1 field must be 0"); + } + if (!bitSet.get(Bits.BLOCK_INDEPENDENCE.position)) { + throw new RuntimeException("Dependent block stream is unsupported (BLOCK_INDEPENDENCE must be set)"); + } + if (version != DEFAULT_VERSION) { + throw new RuntimeException(String.format(Locale.ROOT, "Version %d is unsupported", version)); + } + } + + public boolean isEnabled(Bits bit) { + return bitSet.get(bit.position); + } + + public int getVersion() { + return version; + } + } + + public static class BD { + private static final int RESERVED_MASK = 0x8F; + + private final BLOCKSIZE blockSizeValue; + + private BD(BLOCKSIZE blockSizeValue) { + this.blockSizeValue = blockSizeValue; + } + + public static BD fromByte(byte bd) { + int blockMaximumSize = (bd >>> 4) & 7; + if ((bd & RESERVED_MASK) > 0) { + throw new RuntimeException("Reserved fields must be 0"); + } + + return new BD(BLOCKSIZE.valueOf(blockMaximumSize)); + } + + // 2^(2n+8) + public int getBlockMaximumSize() { + return 1 << ((2 * blockSizeValue.getIndicator()) + 8); + } + + public byte toByte() { + return (byte) ((blockSizeValue.getIndicator() & 7) << 4); + } + } + + static class FrameInfo { + private final FLG flg; + private final BD bd; + private final StreamingXXHash32 streamHash; + private boolean finished = false; + + public FrameInfo(FLG flg, BD bd) { + this.flg = flg; + this.bd = bd; + this.streamHash = flg.isEnabled(FLG.Bits.CONTENT_CHECKSUM) ? XXHashFactory.fastestInstance().newStreamingHash32(0) : null; + } + + public boolean isEnabled(FLG.Bits bit) { + return flg.isEnabled(bit); + } + + public FLG getFLG() { + return this.flg; + } + + public BD getBD() { + return this.bd; + } + + public void updateStreamHash(byte[] buff, int off, int len) { + this.streamHash.update(buff, off, len); + } + + public int currentStreamHash() { + return this.streamHash.getValue(); + } + + public void finish() { + this.finished = true; + } + + public boolean isFinished() { + return this.finished; + } + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJNICompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJNICompressor.java new file mode 100644 index 000000000..30f098c94 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJNICompressor.java @@ -0,0 +1,86 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * High compression {@link LZ4Compressor}s implemented with JNI bindings to the + * original C implementation of LZ4. + */ +final class LZ4HCJNICompressor extends LZ4Compressor { + + public static final LZ4HCJNICompressor INSTANCE = new LZ4HCJNICompressor(); + private static LZ4Compressor SAFE_INSTANCE; + + private final int compressionLevel; + + LZ4HCJNICompressor() { this(LZ4Constants.DEFAULT_COMPRESSION_LEVEL); } + LZ4HCJNICompressor(int compressionLevel) { + this.compressionLevel = compressionLevel; + } + + @Override + public int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) { + SafeUtils.checkRange(src, srcOff, srcLen); + SafeUtils.checkRange(dest, destOff, maxDestLen); + final int result = LZ4JNI.LZ4_compressHC(src, null, srcOff, srcLen, dest, null, destOff, maxDestLen, compressionLevel); + if (result <= 0) { + throw new LZ4Exception(); + } + return result; + } + + @Override + public int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) { + ByteBufferUtils.checkNotReadOnly(dest); + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + + if ((src.hasArray() || src.isDirect()) && (dest.hasArray() || dest.isDirect())) { + byte[] srcArr = null, destArr = null; + ByteBuffer srcBuf = null, destBuf = null; + if (src.hasArray()) { + srcArr = src.array(); + srcOff += src.arrayOffset(); + } else { + assert src.isDirect(); + srcBuf = src; + } + if (dest.hasArray()) { + destArr = dest.array(); + destOff += dest.arrayOffset(); + } else { + assert dest.isDirect(); + destBuf = dest; + } + + final int result = LZ4JNI.LZ4_compressHC(srcArr, srcBuf, srcOff, srcLen, destArr, destBuf, destOff, maxDestLen, compressionLevel); + if (result <= 0) { + throw new LZ4Exception(); + } + return result; + } else { + LZ4Compressor safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = LZ4Factory.safeInstance().highCompressor(compressionLevel); + } + return safeInstance.compress(src, srcOff, srcLen, dest, destOff, maxDestLen); + } + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java new file mode 100644 index 000000000..7147358f0 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaSafeCompressor.java @@ -0,0 +1,550 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; +import static com.fr.third.net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.fr.third.net.jpountz.lz4.LZ4Utils.Match; +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * High compression compressor. + */ +final class LZ4HCJavaSafeCompressor extends LZ4Compressor { + + public static final LZ4Compressor INSTANCE = new LZ4HCJavaSafeCompressor(); + + private final int maxAttempts; + final int compressionLevel; + + LZ4HCJavaSafeCompressor() { this(DEFAULT_COMPRESSION_LEVEL); } + LZ4HCJavaSafeCompressor(int compressionLevel) { + this.maxAttempts = 1<<(compressionLevel-1); + this.compressionLevel = compressionLevel; + } + + private class HashTable { + static final int MASK = MAX_DISTANCE - 1; + int nextToUpdate; + private final int base; + private final int[] hashTable; + private final short[] chainTable; + + HashTable(int base) { + this.base = base; + nextToUpdate = base; + hashTable = new int[HASH_TABLE_SIZE_HC]; + Arrays.fill(hashTable, -1); + chainTable = new short[MAX_DISTANCE]; + } + + private int hashPointer(byte[] bytes, int off) { + final int v = SafeUtils.readInt(bytes, off); + return hashPointer(v); + } + + private int hashPointer(ByteBuffer bytes, int off) { + final int v = ByteBufferUtils.readInt(bytes, off); + return hashPointer(v); + } + + private int hashPointer(int v) { + final int h = hashHC(v); + return hashTable[h]; + } + + private int next(int off) { + return off - (chainTable[off & MASK] & 0xFFFF); + } + + private void addHash(byte[] bytes, int off) { + final int v = SafeUtils.readInt(bytes, off); + addHash(v, off); + } + + private void addHash(ByteBuffer bytes, int off) { + final int v = ByteBufferUtils.readInt(bytes, off); + addHash(v, off); + } + + private void addHash(int v, int off) { + final int h = hashHC(v); + int delta = off - hashTable[h]; + assert delta > 0 : delta; + if (delta >= MAX_DISTANCE) { + delta = MAX_DISTANCE - 1; + } + chainTable[off & MASK] = (short) delta; + hashTable[h] = off; + } + + void insert(int off, byte[] bytes) { + for (; nextToUpdate < off; ++nextToUpdate) { + addHash(bytes, nextToUpdate); + } + } + + void insert(int off, ByteBuffer bytes) { + for (; nextToUpdate < off; ++nextToUpdate) { + addHash(bytes, nextToUpdate); + } + } + + + + boolean insertAndFindBestMatch(byte[] buf, int off, int matchLimit, Match match) { + match.start = off; + match.len = 0; + int delta = 0; + int repl = 0; + + insert(off, buf); + + int ref = hashPointer(buf, off); + + if (ref >= off - 4 && ref <= off && ref >= base) { // potential repetition + if (LZ4SafeUtils.readIntEquals(buf, ref, off)) { // confirmed + delta = off - ref; + repl = match.len = MIN_MATCH + LZ4SafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + match.ref = ref; + } + ref = next(ref); + } + + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4SafeUtils.readIntEquals(buf, ref, off)) { + final int matchLen = MIN_MATCH + LZ4SafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + if (matchLen > match.len) { + match.ref = ref; + match.len = matchLen; + } + } + ref = next(ref); + } + + if (repl != 0) { + int ptr = off; + final int end = off + repl - (MIN_MATCH - 1); + while (ptr < end - delta) { + chainTable[ptr & MASK] = (short) delta; // pre load + ++ptr; + } + do { + chainTable[ptr & MASK] = (short) delta; + hashTable[hashHC(SafeUtils.readInt(buf, ptr))] = ptr; + ++ptr; + } while (ptr < end); + nextToUpdate = end; + } + + return match.len != 0; + } + + boolean insertAndFindWiderMatch(byte[] buf, int off, int startLimit, int matchLimit, int minLen, Match match) { + match.len = minLen; + + insert(off, buf); + + final int delta = off - startLimit; + int ref = hashPointer(buf, off); + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4SafeUtils.readIntEquals(buf, ref, off)) { + final int matchLenForward = MIN_MATCH +LZ4SafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + final int matchLenBackward = LZ4SafeUtils.commonBytesBackward(buf, ref, off, base, startLimit); + final int matchLen = matchLenBackward + matchLenForward; + if (matchLen > match.len) { + match.len = matchLen; + match.ref = ref - matchLenBackward; + match.start = off - matchLenBackward; + } + } + ref = next(ref); + } + + return match.len > minLen; + } + + + boolean insertAndFindBestMatch(ByteBuffer buf, int off, int matchLimit, Match match) { + match.start = off; + match.len = 0; + int delta = 0; + int repl = 0; + + insert(off, buf); + + int ref = hashPointer(buf, off); + + if (ref >= off - 4 && ref <= off && ref >= base) { // potential repetition + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { // confirmed + delta = off - ref; + repl = match.len = MIN_MATCH + LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + match.ref = ref; + } + ref = next(ref); + } + + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { + final int matchLen = MIN_MATCH + LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + if (matchLen > match.len) { + match.ref = ref; + match.len = matchLen; + } + } + ref = next(ref); + } + + if (repl != 0) { + int ptr = off; + final int end = off + repl - (MIN_MATCH - 1); + while (ptr < end - delta) { + chainTable[ptr & MASK] = (short) delta; // pre load + ++ptr; + } + do { + chainTable[ptr & MASK] = (short) delta; + hashTable[hashHC(ByteBufferUtils.readInt(buf, ptr))] = ptr; + ++ptr; + } while (ptr < end); + nextToUpdate = end; + } + + return match.len != 0; + } + + boolean insertAndFindWiderMatch(ByteBuffer buf, int off, int startLimit, int matchLimit, int minLen, Match match) { + match.len = minLen; + + insert(off, buf); + + final int delta = off - startLimit; + int ref = hashPointer(buf, off); + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { + final int matchLenForward = MIN_MATCH +LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + final int matchLenBackward = LZ4ByteBufferUtils.commonBytesBackward(buf, ref, off, base, startLimit); + final int matchLen = matchLenBackward + matchLenForward; + if (matchLen > match.len) { + match.len = matchLen; + match.ref = ref - matchLenBackward; + match.start = off - matchLenBackward; + } + } + ref = next(ref); + } + + return match.len > minLen; + } + + + } + + @Override + public int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) { + + SafeUtils.checkRange(src, srcOff, srcLen); + SafeUtils.checkRange(dest, destOff, maxDestLen); + + final int srcEnd = srcOff + srcLen; + final int destEnd = destOff + maxDestLen; + final int mfLimit = srcEnd - MF_LIMIT; + final int matchLimit = srcEnd - LAST_LITERALS; + + int sOff = srcOff; + int dOff = destOff; + int anchor = sOff++; + + final HashTable ht = new HashTable(srcOff); + final Match match0 = new Match(); + final Match match1 = new Match(); + final Match match2 = new Match(); + final Match match3 = new Match(); + + main: + while (sOff < mfLimit) { + if (!ht.insertAndFindBestMatch(src, sOff, matchLimit, match1)) { + ++sOff; + continue; + } + + // saved, in case we would skip too much + copyTo(match1, match0); + + search2: + while (true) { + assert match1.start >= anchor; + if (match1.end() >= mfLimit + || !ht.insertAndFindWiderMatch(src, match1.end() - 2, match1.start + 1, matchLimit, match1.len, match2)) { + // no better match + dOff = LZ4SafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + continue main; + } + + if (match0.start < match1.start) { + if (match2.start < match1.start + match0.len) { // empirical + copyTo(match0, match1); + } + } + assert match2.start > match1.start; + + if (match2.start - match1.start < 3) { // First Match too small : removed + copyTo(match2, match1); + continue search2; + } + + search3: + while (true) { + if (match2.start - match1.start < OPTIMAL_ML) { + int newMatchLen = match1.len; + if (newMatchLen > OPTIMAL_ML) { + newMatchLen = OPTIMAL_ML; + } + if (match1.start + newMatchLen > match2.end() - MIN_MATCH) { + newMatchLen = match2.start - match1.start + match2.len - MIN_MATCH; + } + final int correction = newMatchLen - (match2.start - match1.start); + if (correction > 0) { + match2.fix(correction); + } + } + + if (match2.start + match2.len >= mfLimit + || !ht.insertAndFindWiderMatch(src, match2.end() - 3, match2.start, matchLimit, match2.len, match3)) { + // no better match -> 2 sequences to encode + if (match2.start < match1.end()) { + match1.len = match2.start - match1.start; + } + // encode seq 1 + dOff = LZ4SafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + // encode seq 2 + dOff = LZ4SafeUtils.encodeSequence(src, anchor, match2.start, match2.ref, match2.len, dest, dOff, destEnd); + anchor = sOff = match2.end(); + continue main; + } + + if (match3.start < match1.end() + 3) { // Not enough space for match 2 : remove it + if (match3.start >= match1.end()) { // // can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 + if (match2.start < match1.end()) { + final int correction = match1.end() - match2.start; + match2.fix(correction); + if (match2.len < MIN_MATCH) { + copyTo(match3, match2); + } + } + + dOff = LZ4SafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match3, match1); + copyTo(match2, match0); + + continue search2; + } + + copyTo(match3, match2); + continue search3; + } + + // OK, now we have 3 ascending matches; let's write at least the first one + if (match2.start < match1.end()) { + if (match2.start - match1.start < ML_MASK) { + if (match1.len > OPTIMAL_ML) { + match1.len = OPTIMAL_ML; + } + if (match1.end() > match2.end() - MIN_MATCH) { + match1.len = match2.end() - match1.start - MIN_MATCH; + } + final int correction = match1.end() - match2.start; + match2.fix(correction); + } else { + match1.len = match2.start - match1.start; + } + } + + dOff = LZ4SafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match2, match1); + copyTo(match3, match2); + + continue search3; + } + + } + + } + + dOff = LZ4SafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + + @Override + public int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) { + + if (src.hasArray() && dest.hasArray()) { + return compress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), maxDestLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + + final int srcEnd = srcOff + srcLen; + final int destEnd = destOff + maxDestLen; + final int mfLimit = srcEnd - MF_LIMIT; + final int matchLimit = srcEnd - LAST_LITERALS; + + int sOff = srcOff; + int dOff = destOff; + int anchor = sOff++; + + final HashTable ht = new HashTable(srcOff); + final Match match0 = new Match(); + final Match match1 = new Match(); + final Match match2 = new Match(); + final Match match3 = new Match(); + + main: + while (sOff < mfLimit) { + if (!ht.insertAndFindBestMatch(src, sOff, matchLimit, match1)) { + ++sOff; + continue; + } + + // saved, in case we would skip too much + copyTo(match1, match0); + + search2: + while (true) { + assert match1.start >= anchor; + if (match1.end() >= mfLimit + || !ht.insertAndFindWiderMatch(src, match1.end() - 2, match1.start + 1, matchLimit, match1.len, match2)) { + // no better match + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + continue main; + } + + if (match0.start < match1.start) { + if (match2.start < match1.start + match0.len) { // empirical + copyTo(match0, match1); + } + } + assert match2.start > match1.start; + + if (match2.start - match1.start < 3) { // First Match too small : removed + copyTo(match2, match1); + continue search2; + } + + search3: + while (true) { + if (match2.start - match1.start < OPTIMAL_ML) { + int newMatchLen = match1.len; + if (newMatchLen > OPTIMAL_ML) { + newMatchLen = OPTIMAL_ML; + } + if (match1.start + newMatchLen > match2.end() - MIN_MATCH) { + newMatchLen = match2.start - match1.start + match2.len - MIN_MATCH; + } + final int correction = newMatchLen - (match2.start - match1.start); + if (correction > 0) { + match2.fix(correction); + } + } + + if (match2.start + match2.len >= mfLimit + || !ht.insertAndFindWiderMatch(src, match2.end() - 3, match2.start, matchLimit, match2.len, match3)) { + // no better match -> 2 sequences to encode + if (match2.start < match1.end()) { + match1.len = match2.start - match1.start; + } + // encode seq 1 + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + // encode seq 2 + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match2.start, match2.ref, match2.len, dest, dOff, destEnd); + anchor = sOff = match2.end(); + continue main; + } + + if (match3.start < match1.end() + 3) { // Not enough space for match 2 : remove it + if (match3.start >= match1.end()) { // // can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 + if (match2.start < match1.end()) { + final int correction = match1.end() - match2.start; + match2.fix(correction); + if (match2.len < MIN_MATCH) { + copyTo(match3, match2); + } + } + + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match3, match1); + copyTo(match2, match0); + + continue search2; + } + + copyTo(match3, match2); + continue search3; + } + + // OK, now we have 3 ascending matches; let's write at least the first one + if (match2.start < match1.end()) { + if (match2.start - match1.start < ML_MASK) { + if (match1.len > OPTIMAL_ML) { + match1.len = OPTIMAL_ML; + } + if (match1.end() > match2.end() - MIN_MATCH) { + match1.len = match2.end() - match1.start - MIN_MATCH; + } + final int correction = match1.end() - match2.start; + match2.fix(correction); + } else { + match1.len = match2.start - match1.start; + } + } + + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match2, match1); + copyTo(match3, match2); + + continue search3; + } + + } + + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java new file mode 100644 index 000000000..e4b445e09 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4HCJavaUnsafeCompressor.java @@ -0,0 +1,550 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; +import static com.fr.third.net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.fr.third.net.jpountz.lz4.LZ4Utils.Match; +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.UnsafeUtils; + +/** + * High compression compressor. + */ +final class LZ4HCJavaUnsafeCompressor extends LZ4Compressor { + + public static final LZ4Compressor INSTANCE = new LZ4HCJavaUnsafeCompressor(); + + private final int maxAttempts; + final int compressionLevel; + + LZ4HCJavaUnsafeCompressor() { this(DEFAULT_COMPRESSION_LEVEL); } + LZ4HCJavaUnsafeCompressor(int compressionLevel) { + this.maxAttempts = 1<<(compressionLevel-1); + this.compressionLevel = compressionLevel; + } + + private class HashTable { + static final int MASK = MAX_DISTANCE - 1; + int nextToUpdate; + private final int base; + private final int[] hashTable; + private final short[] chainTable; + + HashTable(int base) { + this.base = base; + nextToUpdate = base; + hashTable = new int[HASH_TABLE_SIZE_HC]; + Arrays.fill(hashTable, -1); + chainTable = new short[MAX_DISTANCE]; + } + + private int hashPointer(byte[] bytes, int off) { + final int v = UnsafeUtils.readInt(bytes, off); + return hashPointer(v); + } + + private int hashPointer(ByteBuffer bytes, int off) { + final int v = ByteBufferUtils.readInt(bytes, off); + return hashPointer(v); + } + + private int hashPointer(int v) { + final int h = hashHC(v); + return hashTable[h]; + } + + private int next(int off) { + return off - (chainTable[off & MASK] & 0xFFFF); + } + + private void addHash(byte[] bytes, int off) { + final int v = UnsafeUtils.readInt(bytes, off); + addHash(v, off); + } + + private void addHash(ByteBuffer bytes, int off) { + final int v = ByteBufferUtils.readInt(bytes, off); + addHash(v, off); + } + + private void addHash(int v, int off) { + final int h = hashHC(v); + int delta = off - hashTable[h]; + assert delta > 0 : delta; + if (delta >= MAX_DISTANCE) { + delta = MAX_DISTANCE - 1; + } + chainTable[off & MASK] = (short) delta; + hashTable[h] = off; + } + + void insert(int off, byte[] bytes) { + for (; nextToUpdate < off; ++nextToUpdate) { + addHash(bytes, nextToUpdate); + } + } + + void insert(int off, ByteBuffer bytes) { + for (; nextToUpdate < off; ++nextToUpdate) { + addHash(bytes, nextToUpdate); + } + } + + + + boolean insertAndFindBestMatch(byte[] buf, int off, int matchLimit, Match match) { + match.start = off; + match.len = 0; + int delta = 0; + int repl = 0; + + insert(off, buf); + + int ref = hashPointer(buf, off); + + if (ref >= off - 4 && ref <= off && ref >= base) { // potential repetition + if (LZ4UnsafeUtils.readIntEquals(buf, ref, off)) { // confirmed + delta = off - ref; + repl = match.len = MIN_MATCH + LZ4UnsafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + match.ref = ref; + } + ref = next(ref); + } + + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4UnsafeUtils.readIntEquals(buf, ref, off)) { + final int matchLen = MIN_MATCH + LZ4UnsafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + if (matchLen > match.len) { + match.ref = ref; + match.len = matchLen; + } + } + ref = next(ref); + } + + if (repl != 0) { + int ptr = off; + final int end = off + repl - (MIN_MATCH - 1); + while (ptr < end - delta) { + chainTable[ptr & MASK] = (short) delta; // pre load + ++ptr; + } + do { + chainTable[ptr & MASK] = (short) delta; + hashTable[hashHC(UnsafeUtils.readInt(buf, ptr))] = ptr; + ++ptr; + } while (ptr < end); + nextToUpdate = end; + } + + return match.len != 0; + } + + boolean insertAndFindWiderMatch(byte[] buf, int off, int startLimit, int matchLimit, int minLen, Match match) { + match.len = minLen; + + insert(off, buf); + + final int delta = off - startLimit; + int ref = hashPointer(buf, off); + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4UnsafeUtils.readIntEquals(buf, ref, off)) { + final int matchLenForward = MIN_MATCH +LZ4UnsafeUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + final int matchLenBackward = LZ4UnsafeUtils.commonBytesBackward(buf, ref, off, base, startLimit); + final int matchLen = matchLenBackward + matchLenForward; + if (matchLen > match.len) { + match.len = matchLen; + match.ref = ref - matchLenBackward; + match.start = off - matchLenBackward; + } + } + ref = next(ref); + } + + return match.len > minLen; + } + + + boolean insertAndFindBestMatch(ByteBuffer buf, int off, int matchLimit, Match match) { + match.start = off; + match.len = 0; + int delta = 0; + int repl = 0; + + insert(off, buf); + + int ref = hashPointer(buf, off); + + if (ref >= off - 4 && ref <= off && ref >= base) { // potential repetition + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { // confirmed + delta = off - ref; + repl = match.len = MIN_MATCH + LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + match.ref = ref; + } + ref = next(ref); + } + + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { + final int matchLen = MIN_MATCH + LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + if (matchLen > match.len) { + match.ref = ref; + match.len = matchLen; + } + } + ref = next(ref); + } + + if (repl != 0) { + int ptr = off; + final int end = off + repl - (MIN_MATCH - 1); + while (ptr < end - delta) { + chainTable[ptr & MASK] = (short) delta; // pre load + ++ptr; + } + do { + chainTable[ptr & MASK] = (short) delta; + hashTable[hashHC(ByteBufferUtils.readInt(buf, ptr))] = ptr; + ++ptr; + } while (ptr < end); + nextToUpdate = end; + } + + return match.len != 0; + } + + boolean insertAndFindWiderMatch(ByteBuffer buf, int off, int startLimit, int matchLimit, int minLen, Match match) { + match.len = minLen; + + insert(off, buf); + + final int delta = off - startLimit; + int ref = hashPointer(buf, off); + for (int i = 0; i < maxAttempts; ++i) { + if (ref < Math.max(base, off - MAX_DISTANCE + 1) || ref > off) { + break; + } + if (LZ4ByteBufferUtils.readIntEquals(buf, ref, off)) { + final int matchLenForward = MIN_MATCH +LZ4ByteBufferUtils.commonBytes(buf, ref + MIN_MATCH, off + MIN_MATCH, matchLimit); + final int matchLenBackward = LZ4ByteBufferUtils.commonBytesBackward(buf, ref, off, base, startLimit); + final int matchLen = matchLenBackward + matchLenForward; + if (matchLen > match.len) { + match.len = matchLen; + match.ref = ref - matchLenBackward; + match.start = off - matchLenBackward; + } + } + ref = next(ref); + } + + return match.len > minLen; + } + + + } + + @Override + public int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) { + + UnsafeUtils.checkRange(src, srcOff, srcLen); + UnsafeUtils.checkRange(dest, destOff, maxDestLen); + + final int srcEnd = srcOff + srcLen; + final int destEnd = destOff + maxDestLen; + final int mfLimit = srcEnd - MF_LIMIT; + final int matchLimit = srcEnd - LAST_LITERALS; + + int sOff = srcOff; + int dOff = destOff; + int anchor = sOff++; + + final HashTable ht = new HashTable(srcOff); + final Match match0 = new Match(); + final Match match1 = new Match(); + final Match match2 = new Match(); + final Match match3 = new Match(); + + main: + while (sOff < mfLimit) { + if (!ht.insertAndFindBestMatch(src, sOff, matchLimit, match1)) { + ++sOff; + continue; + } + + // saved, in case we would skip too much + copyTo(match1, match0); + + search2: + while (true) { + assert match1.start >= anchor; + if (match1.end() >= mfLimit + || !ht.insertAndFindWiderMatch(src, match1.end() - 2, match1.start + 1, matchLimit, match1.len, match2)) { + // no better match + dOff = LZ4UnsafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + continue main; + } + + if (match0.start < match1.start) { + if (match2.start < match1.start + match0.len) { // empirical + copyTo(match0, match1); + } + } + assert match2.start > match1.start; + + if (match2.start - match1.start < 3) { // First Match too small : removed + copyTo(match2, match1); + continue search2; + } + + search3: + while (true) { + if (match2.start - match1.start < OPTIMAL_ML) { + int newMatchLen = match1.len; + if (newMatchLen > OPTIMAL_ML) { + newMatchLen = OPTIMAL_ML; + } + if (match1.start + newMatchLen > match2.end() - MIN_MATCH) { + newMatchLen = match2.start - match1.start + match2.len - MIN_MATCH; + } + final int correction = newMatchLen - (match2.start - match1.start); + if (correction > 0) { + match2.fix(correction); + } + } + + if (match2.start + match2.len >= mfLimit + || !ht.insertAndFindWiderMatch(src, match2.end() - 3, match2.start, matchLimit, match2.len, match3)) { + // no better match -> 2 sequences to encode + if (match2.start < match1.end()) { + match1.len = match2.start - match1.start; + } + // encode seq 1 + dOff = LZ4UnsafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + // encode seq 2 + dOff = LZ4UnsafeUtils.encodeSequence(src, anchor, match2.start, match2.ref, match2.len, dest, dOff, destEnd); + anchor = sOff = match2.end(); + continue main; + } + + if (match3.start < match1.end() + 3) { // Not enough space for match 2 : remove it + if (match3.start >= match1.end()) { // // can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 + if (match2.start < match1.end()) { + final int correction = match1.end() - match2.start; + match2.fix(correction); + if (match2.len < MIN_MATCH) { + copyTo(match3, match2); + } + } + + dOff = LZ4UnsafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match3, match1); + copyTo(match2, match0); + + continue search2; + } + + copyTo(match3, match2); + continue search3; + } + + // OK, now we have 3 ascending matches; let's write at least the first one + if (match2.start < match1.end()) { + if (match2.start - match1.start < ML_MASK) { + if (match1.len > OPTIMAL_ML) { + match1.len = OPTIMAL_ML; + } + if (match1.end() > match2.end() - MIN_MATCH) { + match1.len = match2.end() - match1.start - MIN_MATCH; + } + final int correction = match1.end() - match2.start; + match2.fix(correction); + } else { + match1.len = match2.start - match1.start; + } + } + + dOff = LZ4UnsafeUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match2, match1); + copyTo(match3, match2); + + continue search3; + } + + } + + } + + dOff = LZ4UnsafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + + @Override + public int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) { + + if (src.hasArray() && dest.hasArray()) { + return compress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), maxDestLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + + final int srcEnd = srcOff + srcLen; + final int destEnd = destOff + maxDestLen; + final int mfLimit = srcEnd - MF_LIMIT; + final int matchLimit = srcEnd - LAST_LITERALS; + + int sOff = srcOff; + int dOff = destOff; + int anchor = sOff++; + + final HashTable ht = new HashTable(srcOff); + final Match match0 = new Match(); + final Match match1 = new Match(); + final Match match2 = new Match(); + final Match match3 = new Match(); + + main: + while (sOff < mfLimit) { + if (!ht.insertAndFindBestMatch(src, sOff, matchLimit, match1)) { + ++sOff; + continue; + } + + // saved, in case we would skip too much + copyTo(match1, match0); + + search2: + while (true) { + assert match1.start >= anchor; + if (match1.end() >= mfLimit + || !ht.insertAndFindWiderMatch(src, match1.end() - 2, match1.start + 1, matchLimit, match1.len, match2)) { + // no better match + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + continue main; + } + + if (match0.start < match1.start) { + if (match2.start < match1.start + match0.len) { // empirical + copyTo(match0, match1); + } + } + assert match2.start > match1.start; + + if (match2.start - match1.start < 3) { // First Match too small : removed + copyTo(match2, match1); + continue search2; + } + + search3: + while (true) { + if (match2.start - match1.start < OPTIMAL_ML) { + int newMatchLen = match1.len; + if (newMatchLen > OPTIMAL_ML) { + newMatchLen = OPTIMAL_ML; + } + if (match1.start + newMatchLen > match2.end() - MIN_MATCH) { + newMatchLen = match2.start - match1.start + match2.len - MIN_MATCH; + } + final int correction = newMatchLen - (match2.start - match1.start); + if (correction > 0) { + match2.fix(correction); + } + } + + if (match2.start + match2.len >= mfLimit + || !ht.insertAndFindWiderMatch(src, match2.end() - 3, match2.start, matchLimit, match2.len, match3)) { + // no better match -> 2 sequences to encode + if (match2.start < match1.end()) { + match1.len = match2.start - match1.start; + } + // encode seq 1 + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + // encode seq 2 + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match2.start, match2.ref, match2.len, dest, dOff, destEnd); + anchor = sOff = match2.end(); + continue main; + } + + if (match3.start < match1.end() + 3) { // Not enough space for match 2 : remove it + if (match3.start >= match1.end()) { // // can write Seq1 immediately ==> Seq2 is removed, so Seq3 becomes Seq1 + if (match2.start < match1.end()) { + final int correction = match1.end() - match2.start; + match2.fix(correction); + if (match2.len < MIN_MATCH) { + copyTo(match3, match2); + } + } + + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match3, match1); + copyTo(match2, match0); + + continue search2; + } + + copyTo(match3, match2); + continue search3; + } + + // OK, now we have 3 ascending matches; let's write at least the first one + if (match2.start < match1.end()) { + if (match2.start - match1.start < ML_MASK) { + if (match1.len > OPTIMAL_ML) { + match1.len = OPTIMAL_ML; + } + if (match1.end() > match2.end() - MIN_MATCH) { + match1.len = match2.end() - match1.start - MIN_MATCH; + } + final int correction = match1.end() - match2.start; + match2.fix(correction); + } else { + match1.len = match2.start - match1.start; + } + } + + dOff = LZ4ByteBufferUtils.encodeSequence(src, anchor, match1.start, match1.ref, match1.len, dest, dOff, destEnd); + anchor = sOff = match1.end(); + + copyTo(match2, match1); + copyTo(match3, match2); + + continue search3; + } + + } + + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNI.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNI.java new file mode 100644 index 000000000..2c036d0ba --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNI.java @@ -0,0 +1,41 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.Native; + + +/** + * JNI bindings to the original C implementation of LZ4. + */ +enum LZ4JNI { + ; + + static { + Native.load(); + init(); + } + + static native void init(); + static native int LZ4_compress_limitedOutput(byte[] srcArray, ByteBuffer srcBuffer, int srcOff, int srcLen, byte[] destArray, ByteBuffer destBuffer, int destOff, int maxDestLen); + static native int LZ4_compressHC(byte[] srcArray, ByteBuffer srcBuffer, int srcOff, int srcLen, byte[] destArray, ByteBuffer destBuffer, int destOff, int maxDestLen, int compressionLevel); + static native int LZ4_decompress_fast(byte[] srcArray, ByteBuffer srcBuffer, int srcOff, byte[] destArray, ByteBuffer destBuffer, int destOff, int destLen); + 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); + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNICompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNICompressor.java new file mode 100644 index 000000000..4afda18b6 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNICompressor.java @@ -0,0 +1,80 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils.checkNotReadOnly; +import static com.fr.third.net.jpountz.util.ByteBufferUtils.checkRange; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; + +import java.nio.ByteBuffer; + +/** + * Fast {@link LZ4FastCompressor}s implemented with JNI bindings to the original C + * implementation of LZ4. + */ +final class LZ4JNICompressor extends LZ4Compressor { + + public static final LZ4Compressor INSTANCE = new LZ4JNICompressor(); + private static LZ4Compressor SAFE_INSTANCE; + + @Override + public int compress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) { + checkRange(src, srcOff, srcLen); + checkRange(dest, destOff, maxDestLen); + final int result = LZ4JNI.LZ4_compress_limitedOutput(src, null, srcOff, srcLen, dest, null, destOff, maxDestLen); + if (result <= 0) { + throw new LZ4Exception("maxDestLen is too small"); + } + return result; + } + + @Override + public int compress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) { + checkNotReadOnly(dest); + checkRange(src, srcOff, srcLen); + checkRange(dest, destOff, maxDestLen); + + if ((src.hasArray() || src.isDirect()) && (dest.hasArray() || dest.isDirect())) { + byte[] srcArr = null, destArr = null; + ByteBuffer srcBuf = null, destBuf = null; + if (src.hasArray()) { + srcArr = src.array(); + srcOff += src.arrayOffset(); + } else { + assert src.isDirect(); + srcBuf = src; + } + if (dest.hasArray()) { + destArr = dest.array(); + destOff += dest.arrayOffset(); + } else { + assert dest.isDirect(); + destBuf = dest; + } + + final int result = LZ4JNI.LZ4_compress_limitedOutput(srcArr, srcBuf, srcOff, srcLen, destArr, destBuf, destOff, maxDestLen); + if (result <= 0) { + throw new LZ4Exception("maxDestLen is too small"); + } + return result; + } else { + LZ4Compressor safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = LZ4Factory.safeInstance().fastCompressor(); + } + return safeInstance.compress(src, srcOff, srcLen, dest, destOff, maxDestLen); + } + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNIFastDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNIFastDecompressor.java new file mode 100644 index 000000000..132637b90 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNIFastDecompressor.java @@ -0,0 +1,82 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + + +/** + * {@link LZ4FastDecompressor} implemented with JNI bindings to the original C + * implementation of LZ4. + */ +final class LZ4JNIFastDecompressor extends LZ4FastDecompressor { + + public static final LZ4JNIFastDecompressor INSTANCE = new LZ4JNIFastDecompressor(); + private static LZ4FastDecompressor SAFE_INSTANCE; + + @Override + public final int decompress(byte[] src, int srcOff, byte[] dest, int destOff, int destLen) { + SafeUtils.checkRange(src, srcOff); + SafeUtils.checkRange(dest, destOff, destLen); + final int result = LZ4JNI.LZ4_decompress_fast(src, null, srcOff, dest, null, destOff, destLen); + if (result < 0) { + throw new LZ4Exception("Error decoding offset " + (srcOff - result) + " of input buffer"); + } + return result; + } + + @Override + public int decompress(ByteBuffer src, int srcOff, ByteBuffer dest, int destOff, int destLen) { + ByteBufferUtils.checkNotReadOnly(dest); + ByteBufferUtils.checkRange(src, srcOff); + ByteBufferUtils.checkRange(dest, destOff, destLen); + + if ((src.hasArray() || src.isDirect()) && (dest.hasArray() || dest.isDirect())) { + byte[] srcArr = null, destArr = null; + ByteBuffer srcBuf = null, destBuf = null; + if (src.hasArray()) { + srcArr = src.array(); + srcOff += src.arrayOffset(); + } else { + assert src.isDirect(); + srcBuf = src; + } + if (dest.hasArray()) { + destArr = dest.array(); + destOff += dest.arrayOffset(); + } else { + assert dest.isDirect(); + destBuf = dest; + } + + final int result = LZ4JNI.LZ4_decompress_fast(srcArr, srcBuf, srcOff, destArr, destBuf, destOff, destLen); + if (result < 0) { + throw new LZ4Exception("Error decoding offset " + (srcOff - result) + " of input buffer"); + } + return result; + } else { + LZ4FastDecompressor safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = LZ4Factory.safeInstance().fastDecompressor(); + } + return safeInstance.decompress(src, srcOff, dest, destOff, destLen); + } + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNISafeDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNISafeDecompressor.java new file mode 100644 index 000000000..1dfac61a9 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JNISafeDecompressor.java @@ -0,0 +1,81 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * {@link LZ4SafeDecompressor} implemented with JNI bindings to the original C + * implementation of LZ4. + */ +final class LZ4JNISafeDecompressor extends LZ4SafeDecompressor { + + public static final LZ4JNISafeDecompressor INSTANCE = new LZ4JNISafeDecompressor(); + private static LZ4SafeDecompressor SAFE_INSTANCE; + + @Override + public final int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen) { + SafeUtils.checkRange(src, srcOff, srcLen); + SafeUtils.checkRange(dest, destOff, maxDestLen); + final int result = LZ4JNI.LZ4_decompress_safe(src, null, srcOff, srcLen, dest, null, destOff, maxDestLen); + if (result < 0) { + throw new LZ4Exception("Error decoding offset " + (srcOff - result) + " of input buffer"); + } + return result; + } + + @Override + public int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen) { + ByteBufferUtils.checkNotReadOnly(dest); + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + + if ((src.hasArray() || src.isDirect()) && (dest.hasArray() || dest.isDirect())) { + byte[] srcArr = null, destArr = null; + ByteBuffer srcBuf = null, destBuf = null; + if (src.hasArray()) { + srcArr = src.array(); + srcOff += src.arrayOffset(); + } else { + assert src.isDirect(); + srcBuf = src; + } + if (dest.hasArray()) { + destArr = dest.array(); + destOff += dest.arrayOffset(); + } else { + assert dest.isDirect(); + destBuf = dest; + } + + final int result = LZ4JNI.LZ4_decompress_safe(srcArr, srcBuf, srcOff, srcLen, destArr, destBuf, destOff, maxDestLen); + if (result < 0) { + throw new LZ4Exception("Error decoding offset " + (srcOff - result) + " of input buffer"); + } + return result; + } else { + LZ4SafeDecompressor safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = LZ4Factory.safeInstance().safeDecompressor(); + } + return safeInstance.decompress(src, srcOff, srcLen, dest, destOff, maxDestLen); + } + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeCompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeCompressor.java new file mode 100644 index 000000000..6970034be --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeCompressor.java @@ -0,0 +1,511 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; +import static com.fr.third.net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * Compressor. + */ +final class LZ4JavaSafeCompressor extends LZ4Compressor { + + public static final LZ4Compressor INSTANCE = new LZ4JavaSafeCompressor(); + + static int compress64k(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int destEnd) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash64k(SafeUtils.readInt(src, sOff)); + ref = srcOff + SafeUtils.readShort(hashTable, h); + SafeUtils.writeShort(hashTable, h, sOff - srcOff); + } while (!LZ4SafeUtils.readIntEquals(src, ref, sOff)); + + // catch up + final int excess = LZ4SafeUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + SafeUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4SafeUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + SafeUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4SafeUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + SafeUtils.writeShortLE(dest, dOff, (short) (sOff - ref)); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = LZ4SafeUtils.commonBytes(src, ref, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + SafeUtils.writeByte(dest, tokenOff, SafeUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4SafeUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + SafeUtils.writeByte(dest, tokenOff, SafeUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + SafeUtils.writeShort(hashTable, hash64k(SafeUtils.readInt(src, sOff - 2)), sOff - 2 - srcOff); + + // test next position + final int h = hash64k(SafeUtils.readInt(src, sOff)); + ref = srcOff + SafeUtils.readShort(hashTable, h); + SafeUtils.writeShort(hashTable, h, sOff - srcOff); + + if (!LZ4SafeUtils.readIntEquals(src, sOff, ref)) { + break; + } + + tokenOff = dOff++; + SafeUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + } + + dOff = LZ4SafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + @Override + public int compress(byte[] src, final int srcOff, int srcLen, byte[] dest, final int destOff, int maxDestLen) { + + SafeUtils.checkRange(src, srcOff, srcLen); + SafeUtils.checkRange(dest, destOff, maxDestLen); + final int destEnd = destOff + maxDestLen; + + if (srcLen < LZ4_64K_LIMIT) { + return compress64k(src, srcOff, srcLen, dest, destOff, destEnd); + } + + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + int anchor = sOff++; + + final int[] hashTable = new int[HASH_TABLE_SIZE]; + Arrays.fill(hashTable, anchor); + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + int back; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash(SafeUtils.readInt(src, sOff)); + ref = SafeUtils.readInt(hashTable, h); + back = sOff - ref; + SafeUtils.writeInt(hashTable, h, sOff); + } while (back >= MAX_DISTANCE || !LZ4SafeUtils.readIntEquals(src, ref, sOff)); + + + final int excess = LZ4SafeUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + SafeUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4SafeUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + SafeUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4SafeUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + SafeUtils.writeShortLE(dest, dOff, back); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + final int matchLen = LZ4SafeUtils.commonBytes(src, ref + MIN_MATCH, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + SafeUtils.writeByte(dest, tokenOff, SafeUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4SafeUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + SafeUtils.writeByte(dest, tokenOff, SafeUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + SafeUtils.writeInt(hashTable, hash(SafeUtils.readInt(src, sOff - 2)), sOff - 2); + + // test next position + final int h = hash(SafeUtils.readInt(src, sOff)); + ref = SafeUtils.readInt(hashTable, h); + SafeUtils.writeInt(hashTable, h, sOff); + back = sOff - ref; + + if (back >= MAX_DISTANCE || !LZ4SafeUtils.readIntEquals(src, ref, sOff)) { + break; + } + + tokenOff = dOff++; + SafeUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + + dOff = LZ4SafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + + static int compress64k(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int destEnd) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash64k(ByteBufferUtils.readInt(src, sOff)); + ref = srcOff + SafeUtils.readShort(hashTable, h); + SafeUtils.writeShort(hashTable, h, sOff - srcOff); + } while (!LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)); + + // catch up + final int excess = LZ4ByteBufferUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4ByteBufferUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4ByteBufferUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + ByteBufferUtils.writeShortLE(dest, dOff, (short) (sOff - ref)); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = LZ4ByteBufferUtils.commonBytes(src, ref, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4ByteBufferUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + SafeUtils.writeShort(hashTable, hash64k(ByteBufferUtils.readInt(src, sOff - 2)), sOff - 2 - srcOff); + + // test next position + final int h = hash64k(ByteBufferUtils.readInt(src, sOff)); + ref = srcOff + SafeUtils.readShort(hashTable, h); + SafeUtils.writeShort(hashTable, h, sOff - srcOff); + + if (!LZ4ByteBufferUtils.readIntEquals(src, sOff, ref)) { + break; + } + + tokenOff = dOff++; + ByteBufferUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + @Override + public int compress(ByteBuffer src, final int srcOff, int srcLen, ByteBuffer dest, final int destOff, int maxDestLen) { + + if (src.hasArray() && dest.hasArray()) { + return compress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), maxDestLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + final int destEnd = destOff + maxDestLen; + + if (srcLen < LZ4_64K_LIMIT) { + return compress64k(src, srcOff, srcLen, dest, destOff, destEnd); + } + + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + int anchor = sOff++; + + final int[] hashTable = new int[HASH_TABLE_SIZE]; + Arrays.fill(hashTable, anchor); + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + int back; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash(ByteBufferUtils.readInt(src, sOff)); + ref = SafeUtils.readInt(hashTable, h); + back = sOff - ref; + SafeUtils.writeInt(hashTable, h, sOff); + } while (back >= MAX_DISTANCE || !LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)); + + + final int excess = LZ4ByteBufferUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4ByteBufferUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4ByteBufferUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + ByteBufferUtils.writeShortLE(dest, dOff, back); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + final int matchLen = LZ4ByteBufferUtils.commonBytes(src, ref + MIN_MATCH, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4ByteBufferUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + SafeUtils.writeInt(hashTable, hash(ByteBufferUtils.readInt(src, sOff - 2)), sOff - 2); + + // test next position + final int h = hash(ByteBufferUtils.readInt(src, sOff)); + ref = SafeUtils.readInt(hashTable, h); + SafeUtils.writeInt(hashTable, h, sOff); + back = sOff - ref; + + if (back >= MAX_DISTANCE || !LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)) { + break; + } + + tokenOff = dOff++; + ByteBufferUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java new file mode 100644 index 000000000..5a28e39d7 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeFastDecompressor.java @@ -0,0 +1,205 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * Decompressor. + */ +final class LZ4JavaSafeFastDecompressor extends LZ4FastDecompressor { + + public static final LZ4FastDecompressor INSTANCE = new LZ4JavaSafeFastDecompressor(); + + @Override + public int decompress(byte[] src, final int srcOff, byte[] dest, final int destOff, int destLen) { + + + SafeUtils.checkRange(src, srcOff); + SafeUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (SafeUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Malformed input at " + srcOff); + } + return 1; + } + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = SafeUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while ((len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH) { + if (literalCopyEnd != destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4SafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4SafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = SafeUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while ((len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4SafeUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4SafeUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return sOff - srcOff; + + } + + @Override + public int decompress(ByteBuffer src, final int srcOff, ByteBuffer dest, final int destOff, int destLen) { + + if (src.hasArray() && dest.hasArray()) { + return decompress(src.array(), srcOff + src.arrayOffset(), dest.array(), destOff + dest.arrayOffset(), destLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + + ByteBufferUtils.checkRange(src, srcOff); + ByteBufferUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (ByteBufferUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Malformed input at " + srcOff); + } + return 1; + } + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = ByteBufferUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while ((len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH) { + if (literalCopyEnd != destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4ByteBufferUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4ByteBufferUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = ByteBufferUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while ((len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4ByteBufferUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4ByteBufferUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return sOff - srcOff; + + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java new file mode 100644 index 000000000..21b427c40 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaSafeSafeDecompressor.java @@ -0,0 +1,213 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +/** + * Decompressor. + */ +final class LZ4JavaSafeSafeDecompressor extends LZ4SafeDecompressor { + + public static final LZ4SafeDecompressor INSTANCE = new LZ4JavaSafeSafeDecompressor(); + + @Override + public int decompress(byte[] src, final int srcOff, final int srcLen , byte[] dest, final int destOff, int destLen) { + + + SafeUtils.checkRange(src, srcOff, srcLen); + SafeUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (srcLen != 1 || SafeUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Output buffer too small"); + } + return 0; + } + + final int srcEnd = srcOff + srcLen; + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = SafeUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH || sOff + literalLen > srcEnd - COPY_LENGTH) { + if (literalCopyEnd > destEnd) { + throw new LZ4Exception(); + } else if (sOff + literalLen != srcEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4SafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4SafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = SafeUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = SafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4SafeUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4SafeUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return dOff - destOff; + + } + + @Override + public int decompress(ByteBuffer src, final int srcOff, final int srcLen , ByteBuffer dest, final int destOff, int destLen) { + + if (src.hasArray() && dest.hasArray()) { + return decompress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), destLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (srcLen != 1 || ByteBufferUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Output buffer too small"); + } + return 0; + } + + final int srcEnd = srcOff + srcLen; + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = ByteBufferUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH || sOff + literalLen > srcEnd - COPY_LENGTH) { + if (literalCopyEnd > destEnd) { + throw new LZ4Exception(); + } else if (sOff + literalLen != srcEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4ByteBufferUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4ByteBufferUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = ByteBufferUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4ByteBufferUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4ByteBufferUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return dOff - destOff; + + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java new file mode 100644 index 000000000..d81f2f2f9 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeCompressor.java @@ -0,0 +1,511 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; +import static com.fr.third.net.jpountz.lz4.LZ4Utils.*; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.UnsafeUtils; + +/** + * Compressor. + */ +final class LZ4JavaUnsafeCompressor extends LZ4Compressor { + + public static final LZ4Compressor INSTANCE = new LZ4JavaUnsafeCompressor(); + + static int compress64k(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int destEnd) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash64k(UnsafeUtils.readInt(src, sOff)); + ref = srcOff + UnsafeUtils.readShort(hashTable, h); + UnsafeUtils.writeShort(hashTable, h, sOff - srcOff); + } while (!LZ4UnsafeUtils.readIntEquals(src, ref, sOff)); + + // catch up + final int excess = LZ4UnsafeUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + UnsafeUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4UnsafeUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + UnsafeUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4UnsafeUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + UnsafeUtils.writeShortLE(dest, dOff, (short) (sOff - ref)); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = LZ4UnsafeUtils.commonBytes(src, ref, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + UnsafeUtils.writeByte(dest, tokenOff, UnsafeUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4UnsafeUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + UnsafeUtils.writeByte(dest, tokenOff, UnsafeUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + UnsafeUtils.writeShort(hashTable, hash64k(UnsafeUtils.readInt(src, sOff - 2)), sOff - 2 - srcOff); + + // test next position + final int h = hash64k(UnsafeUtils.readInt(src, sOff)); + ref = srcOff + UnsafeUtils.readShort(hashTable, h); + UnsafeUtils.writeShort(hashTable, h, sOff - srcOff); + + if (!LZ4UnsafeUtils.readIntEquals(src, sOff, ref)) { + break; + } + + tokenOff = dOff++; + UnsafeUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + } + + dOff = LZ4UnsafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + @Override + public int compress(byte[] src, final int srcOff, int srcLen, byte[] dest, final int destOff, int maxDestLen) { + + UnsafeUtils.checkRange(src, srcOff, srcLen); + UnsafeUtils.checkRange(dest, destOff, maxDestLen); + final int destEnd = destOff + maxDestLen; + + if (srcLen < LZ4_64K_LIMIT) { + return compress64k(src, srcOff, srcLen, dest, destOff, destEnd); + } + + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + int anchor = sOff++; + + final int[] hashTable = new int[HASH_TABLE_SIZE]; + Arrays.fill(hashTable, anchor); + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + int back; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash(UnsafeUtils.readInt(src, sOff)); + ref = UnsafeUtils.readInt(hashTable, h); + back = sOff - ref; + UnsafeUtils.writeInt(hashTable, h, sOff); + } while (back >= MAX_DISTANCE || !LZ4UnsafeUtils.readIntEquals(src, ref, sOff)); + + + final int excess = LZ4UnsafeUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + UnsafeUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4UnsafeUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + UnsafeUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4UnsafeUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + UnsafeUtils.writeShortLE(dest, dOff, back); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + final int matchLen = LZ4UnsafeUtils.commonBytes(src, ref + MIN_MATCH, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + UnsafeUtils.writeByte(dest, tokenOff, UnsafeUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4UnsafeUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + UnsafeUtils.writeByte(dest, tokenOff, UnsafeUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + UnsafeUtils.writeInt(hashTable, hash(UnsafeUtils.readInt(src, sOff - 2)), sOff - 2); + + // test next position + final int h = hash(UnsafeUtils.readInt(src, sOff)); + ref = UnsafeUtils.readInt(hashTable, h); + UnsafeUtils.writeInt(hashTable, h, sOff); + back = sOff - ref; + + if (back >= MAX_DISTANCE || !LZ4UnsafeUtils.readIntEquals(src, ref, sOff)) { + break; + } + + tokenOff = dOff++; + UnsafeUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + + dOff = LZ4UnsafeUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + + static int compress64k(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int destEnd) { + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + + int anchor = sOff; + + if (srcLen >= MIN_LENGTH) { + + final short[] hashTable = new short[HASH_TABLE_SIZE_64K]; + + ++sOff; + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash64k(ByteBufferUtils.readInt(src, sOff)); + ref = srcOff + UnsafeUtils.readShort(hashTable, h); + UnsafeUtils.writeShort(hashTable, h, sOff - srcOff); + } while (!LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)); + + // catch up + final int excess = LZ4ByteBufferUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4ByteBufferUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4ByteBufferUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + ByteBufferUtils.writeShortLE(dest, dOff, (short) (sOff - ref)); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + ref += MIN_MATCH; + final int matchLen = LZ4ByteBufferUtils.commonBytes(src, ref, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4ByteBufferUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + UnsafeUtils.writeShort(hashTable, hash64k(ByteBufferUtils.readInt(src, sOff - 2)), sOff - 2 - srcOff); + + // test next position + final int h = hash64k(ByteBufferUtils.readInt(src, sOff)); + ref = srcOff + UnsafeUtils.readShort(hashTable, h); + UnsafeUtils.writeShort(hashTable, h, sOff - srcOff); + + if (!LZ4ByteBufferUtils.readIntEquals(src, sOff, ref)) { + break; + } + + tokenOff = dOff++; + ByteBufferUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + @Override + public int compress(ByteBuffer src, final int srcOff, int srcLen, ByteBuffer dest, final int destOff, int maxDestLen) { + + if (src.hasArray() && dest.hasArray()) { + return compress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), maxDestLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, maxDestLen); + final int destEnd = destOff + maxDestLen; + + if (srcLen < LZ4_64K_LIMIT) { + return compress64k(src, srcOff, srcLen, dest, destOff, destEnd); + } + + final int srcEnd = srcOff + srcLen; + final int srcLimit = srcEnd - LAST_LITERALS; + final int mflimit = srcEnd - MF_LIMIT; + + int sOff = srcOff, dOff = destOff; + int anchor = sOff++; + + final int[] hashTable = new int[HASH_TABLE_SIZE]; + Arrays.fill(hashTable, anchor); + + main: + while (true) { + + // find a match + int forwardOff = sOff; + + int ref; + int step = 1; + int searchMatchNb = 1 << SKIP_STRENGTH; + int back; + do { + sOff = forwardOff; + forwardOff += step; + step = searchMatchNb++ >>> SKIP_STRENGTH; + + if (forwardOff > mflimit) { + break main; + } + + final int h = hash(ByteBufferUtils.readInt(src, sOff)); + ref = UnsafeUtils.readInt(hashTable, h); + back = sOff - ref; + UnsafeUtils.writeInt(hashTable, h, sOff); + } while (back >= MAX_DISTANCE || !LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)); + + + final int excess = LZ4ByteBufferUtils.commonBytesBackward(src, ref, sOff, srcOff, anchor); + sOff -= excess; + ref -= excess; + + // sequence == refsequence + final int runLen = sOff - anchor; + + // encode literal length + int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + if (runLen >= RUN_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, RUN_MASK << ML_BITS); + dOff = LZ4ByteBufferUtils.writeLen(runLen - RUN_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, runLen << ML_BITS); + } + + // copy literals + LZ4ByteBufferUtils.wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + while (true) { + // encode offset + ByteBufferUtils.writeShortLE(dest, dOff, back); + dOff += 2; + + // count nb matches + sOff += MIN_MATCH; + final int matchLen = LZ4ByteBufferUtils.commonBytes(src, ref + MIN_MATCH, sOff, srcLimit); + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + sOff += matchLen; + + // encode match len + if (matchLen >= ML_MASK) { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | ML_MASK); + dOff = LZ4ByteBufferUtils.writeLen(matchLen - ML_MASK, dest, dOff); + } else { + ByteBufferUtils.writeByte(dest, tokenOff, ByteBufferUtils.readByte(dest, tokenOff) | matchLen); + } + + // test end of chunk + if (sOff > mflimit) { + anchor = sOff; + break main; + } + + // fill table + UnsafeUtils.writeInt(hashTable, hash(ByteBufferUtils.readInt(src, sOff - 2)), sOff - 2); + + // test next position + final int h = hash(ByteBufferUtils.readInt(src, sOff)); + ref = UnsafeUtils.readInt(hashTable, h); + UnsafeUtils.writeInt(hashTable, h, sOff); + back = sOff - ref; + + if (back >= MAX_DISTANCE || !LZ4ByteBufferUtils.readIntEquals(src, ref, sOff)) { + break; + } + + tokenOff = dOff++; + ByteBufferUtils.writeByte(dest, tokenOff, 0); + } + + // prepare next loop + anchor = sOff++; + } + + dOff = LZ4ByteBufferUtils.lastLiterals(src, anchor, srcEnd - anchor, dest, dOff, destEnd); + return dOff - destOff; + } + + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java new file mode 100644 index 000000000..ddac21fc2 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeFastDecompressor.java @@ -0,0 +1,205 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.UnsafeUtils; + +/** + * Decompressor. + */ +final class LZ4JavaUnsafeFastDecompressor extends LZ4FastDecompressor { + + public static final LZ4FastDecompressor INSTANCE = new LZ4JavaUnsafeFastDecompressor(); + + @Override + public int decompress(byte[] src, final int srcOff, byte[] dest, final int destOff, int destLen) { + + + UnsafeUtils.checkRange(src, srcOff); + UnsafeUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (UnsafeUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Malformed input at " + srcOff); + } + return 1; + } + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = UnsafeUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while ((len = UnsafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH) { + if (literalCopyEnd != destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4UnsafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4UnsafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = UnsafeUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while ((len = UnsafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4UnsafeUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4UnsafeUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return sOff - srcOff; + + } + + @Override + public int decompress(ByteBuffer src, final int srcOff, ByteBuffer dest, final int destOff, int destLen) { + + if (src.hasArray() && dest.hasArray()) { + return decompress(src.array(), srcOff + src.arrayOffset(), dest.array(), destOff + dest.arrayOffset(), destLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + + ByteBufferUtils.checkRange(src, srcOff); + ByteBufferUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (ByteBufferUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Malformed input at " + srcOff); + } + return 1; + } + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = ByteBufferUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while ((len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH) { + if (literalCopyEnd != destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4ByteBufferUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4ByteBufferUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = ByteBufferUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while ((len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4ByteBufferUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4ByteBufferUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return sOff - srcOff; + + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java new file mode 100644 index 000000000..9ee2aae82 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4JavaUnsafeSafeDecompressor.java @@ -0,0 +1,213 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.lz4; + +import static com.fr.third.net.jpountz.lz4.LZ4Constants.*; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.UnsafeUtils; + +/** + * Decompressor. + */ +final class LZ4JavaUnsafeSafeDecompressor extends LZ4SafeDecompressor { + + public static final LZ4SafeDecompressor INSTANCE = new LZ4JavaUnsafeSafeDecompressor(); + + @Override + public int decompress(byte[] src, final int srcOff, final int srcLen , byte[] dest, final int destOff, int destLen) { + + + UnsafeUtils.checkRange(src, srcOff, srcLen); + UnsafeUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (srcLen != 1 || UnsafeUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Output buffer too small"); + } + return 0; + } + + final int srcEnd = srcOff + srcLen; + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = UnsafeUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = UnsafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH || sOff + literalLen > srcEnd - COPY_LENGTH) { + if (literalCopyEnd > destEnd) { + throw new LZ4Exception(); + } else if (sOff + literalLen != srcEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4UnsafeUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4UnsafeUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = UnsafeUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = UnsafeUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4UnsafeUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4UnsafeUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return dOff - destOff; + + } + + @Override + public int decompress(ByteBuffer src, final int srcOff, final int srcLen , ByteBuffer dest, final int destOff, int destLen) { + + if (src.hasArray() && dest.hasArray()) { + return decompress(src.array(), srcOff + src.arrayOffset(), srcLen, dest.array(), destOff + dest.arrayOffset(), destLen); + } + src = ByteBufferUtils.inNativeByteOrder(src); + dest = ByteBufferUtils.inNativeByteOrder(dest); + + + ByteBufferUtils.checkRange(src, srcOff, srcLen); + ByteBufferUtils.checkRange(dest, destOff, destLen); + + if (destLen == 0) { + if (srcLen != 1 || ByteBufferUtils.readByte(src, srcOff) != 0) { + throw new LZ4Exception("Output buffer too small"); + } + return 0; + } + + final int srcEnd = srcOff + srcLen; + + + final int destEnd = destOff + destLen; + + int sOff = srcOff; + int dOff = destOff; + + while (true) { + final int token = ByteBufferUtils.readByte(src, sOff) & 0xFF; + ++sOff; + + // literals + int literalLen = token >>> ML_BITS; + if (literalLen == RUN_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + literalLen += 0xFF; + } + literalLen += len & 0xFF; + } + + final int literalCopyEnd = dOff + literalLen; + + if (literalCopyEnd > destEnd - COPY_LENGTH || sOff + literalLen > srcEnd - COPY_LENGTH) { + if (literalCopyEnd > destEnd) { + throw new LZ4Exception(); + } else if (sOff + literalLen != srcEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + + } else { + LZ4ByteBufferUtils.safeArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + break; // EOF + } + } + + LZ4ByteBufferUtils.wildArraycopy(src, sOff, dest, dOff, literalLen); + sOff += literalLen; + dOff = literalCopyEnd; + + // matchs + final int matchDec = ByteBufferUtils.readShortLE(src, sOff); + sOff += 2; + int matchOff = dOff - matchDec; + + if (matchOff < destOff) { + throw new LZ4Exception("Malformed input at " + sOff); + } + + int matchLen = token & ML_MASK; + if (matchLen == ML_MASK) { + byte len = (byte) 0xFF; + while (sOff < srcEnd &&(len = ByteBufferUtils.readByte(src, sOff++)) == (byte) 0xFF) { + matchLen += 0xFF; + } + matchLen += len & 0xFF; + } + matchLen += MIN_MATCH; + + final int matchCopyEnd = dOff + matchLen; + + if (matchCopyEnd > destEnd - COPY_LENGTH) { + if (matchCopyEnd > destEnd) { + throw new LZ4Exception("Malformed input at " + sOff); + } + LZ4ByteBufferUtils.safeIncrementalCopy(dest, matchOff, dOff, matchLen); + } else { + LZ4ByteBufferUtils.wildIncrementalCopy(dest, matchOff, dOff, matchCopyEnd); + } + dOff = matchCopyEnd; + } + + + return dOff - destOff; + + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeDecompressor.java new file mode 100644 index 000000000..edd189519 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeDecompressor.java @@ -0,0 +1,155 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 java.util.Arrays; + +/** + * LZ4 decompressor that requires the size of the compressed data to be known. + *

+ * Implementations of this class are usually a little slower than those of + * {@link LZ4FastDecompressor} but do not require the size of the original data to + * be known. + */ +public abstract class LZ4SafeDecompressor implements LZ4UnknownSizeDecompressor { + + /** + * Decompresses src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the number of + * decompressed bytes written into dest. + * + * @param src the compressed data + * @param srcOff the start offset in src + * @param srcLen the exact size of the compressed data + * @param dest the destination buffer to store the decompressed data + * @param destOff the start offset in dest + * @param maxDestLen the maximum number of bytes to write in dest + * @return the original input size + * @throws LZ4Exception if maxDestLen is too small + */ + public abstract int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen); + + /** + * Decompresses src[srcOff:srcOff+srcLen] into + * dest[destOff:destOff+maxDestLen] and returns the number of + * decompressed bytes written into dest. + * + * @param src the compressed data + * @param srcOff the start offset in src + * @param srcLen the exact size of the compressed data + * @param dest the destination buffer to store the decompressed data + * @param destOff the start offset in dest + * @param maxDestLen the maximum number of bytes to write in dest + * @return the original input size + * @throws LZ4Exception if maxDestLen is too small + */ + public abstract int decompress(ByteBuffer src, int srcOff, int srcLen, ByteBuffer dest, int destOff, int maxDestLen); + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], int, int, byte[], int, int) decompress(src, srcOff, srcLen, dest, destOff, dest.length - destOff)}. + * + * @param src the compressed data + * @param srcOff the start offset in src + * @param srcLen the exact size of the compressed data + * @param dest the destination buffer to store the decompressed data + * @param destOff the start offset in dest + * @return the original input size + * @throws LZ4Exception if dest is too small + */ + public final int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff) { + return decompress(src, srcOff, srcLen, dest, destOff, dest.length - destOff); + } + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], int, int, byte[], int) decompress(src, 0, src.length, dest, 0)} + * + * @param src the compressed data + * @param dest the destination buffer to store the decompressed data + * @return the original input size + * @throws LZ4Exception if dest is too small + */ + public final int decompress(byte[] src, byte[] dest) { + return decompress(src, 0, src.length, dest, 0); + } + + /** + * Convenience method which returns src[srcOff:srcOff+srcLen] + * decompressed. + *

Warning: this method has an + * important overhead due to the fact that it needs to allocate a buffer to + * decompress into, and then needs to resize this buffer to the actual + * decompressed length.

+ *

Here is how this method is implemented:

+ *
+   * byte[] decompressed = new byte[maxDestLen];
+   * final int decompressedLength = decompress(src, srcOff, srcLen, decompressed, 0, maxDestLen);
+   * if (decompressedLength != decompressed.length) {
+   *   decompressed = Arrays.copyOf(decompressed, decompressedLength);
+   * }
+   * return decompressed;
+   * 
+ * + * @param src the compressed data + * @param srcOff the start offset in src + * @param srcLen the exact size of the compressed data + * @param maxDestLen the maximum number of bytes to write in dest + * @return the decompressed data + * @throws LZ4Exception if maxDestLen is too small + */ + public final byte[] decompress(byte[] src, int srcOff, int srcLen, int maxDestLen) { + byte[] decompressed = new byte[maxDestLen]; + final int decompressedLength = decompress(src, srcOff, srcLen, decompressed, 0, maxDestLen); + if (decompressedLength != decompressed.length) { + decompressed = Arrays.copyOf(decompressed, decompressedLength); + } + return decompressed; + } + + /** + * Convenience method, equivalent to calling + * {@link #decompress(byte[], int, int, int) decompress(src, 0, src.length, maxDestLen)}. + * + * @param src the compressed data + * @param maxDestLen the maximum number of bytes to write in dest + * @return the decompressed data + * @throws LZ4Exception if maxDestLen is too small + */ + public final byte[] decompress(byte[] src, int maxDestLen) { + return decompress(src, 0, src.length, maxDestLen); + } + + /** + * Decompresses src into dest. src's + * {@link ByteBuffer#remaining()} must be exactly the size of the compressed + * data. This method moves the positions of the buffers. + * @param src the compressed data + * @param dest the destination buffer to store the decompressed data + * @throws LZ4Exception if dest is too small + */ + public final void decompress(ByteBuffer src, ByteBuffer dest) { + final int decompressed = decompress(src, src.position(), src.remaining(), dest, dest.position(), dest.remaining()); + src.position(src.limit()); + dest.position(dest.position() + decompressed); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeUtils.java new file mode 100644 index 000000000..b00f99af2 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4SafeUtils.java @@ -0,0 +1,179 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.lz4.LZ4Constants.LAST_LITERALS; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.ML_BITS; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.ML_MASK; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.RUN_MASK; +import com.fr.third.net.jpountz.util.SafeUtils; + +enum LZ4SafeUtils { + ; + + static int hash(byte[] buf, int i) { + return LZ4Utils.hash(SafeUtils.readInt(buf, i)); + } + + static int hash64k(byte[] buf, int i) { + return LZ4Utils.hash64k(SafeUtils.readInt(buf, i)); + } + + static boolean readIntEquals(byte[] buf, int i, int j) { + return buf[i] == buf[j] && buf[i+1] == buf[j+1] && buf[i+2] == buf[j+2] && buf[i+3] == buf[j+3]; + } + + static void safeIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchLen) { + for (int i = 0; i < matchLen; ++i) { + dest[dOff + i] = dest[matchOff + i]; + } + } + + static void wildIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchCopyEnd) { + do { + copy8Bytes(dest, matchOff, dest, dOff); + matchOff += 8; + dOff += 8; + } while (dOff < matchCopyEnd); + } + + static void copy8Bytes(byte[] src, int sOff, byte[] dest, int dOff) { + for (int i = 0; i < 8; ++i) { + dest[dOff + i] = src[sOff + i]; + } + } + + static int commonBytes(byte[] b, int o1, int o2, int limit) { + int count = 0; + while (o2 < limit && b[o1++] == b[o2++]) { + ++count; + } + return count; + } + + static int commonBytesBackward(byte[] b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && b[--o1] == b[--o2]) { + ++count; + } + return count; + } + + static void safeArraycopy(byte[] src, int sOff, byte[] dest, int dOff, int len) { + System.arraycopy(src, sOff, dest, dOff, len); + } + + static void wildArraycopy(byte[] src, int sOff, byte[] dest, int dOff, int len) { + try { + for (int i = 0; i < len; i += 8) { + copy8Bytes(src, sOff + i, dest, dOff + i); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new LZ4Exception("Malformed input at offset " + sOff); + } + } + + static int encodeSequence(byte[] src, int anchor, int matchOff, int matchRef, int matchLen, byte[] dest, int dOff, int destEnd) { + final int runLen = matchOff - anchor; + final int tokenOff = dOff++; + + if (dOff + runLen + (2 + 1 + LAST_LITERALS) + (runLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + + int token; + if (runLen >= RUN_MASK) { + token = (byte) (RUN_MASK << ML_BITS); + dOff = writeLen(runLen - RUN_MASK, dest, dOff); + } else { + token = runLen << ML_BITS; + } + + // copy literals + wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + // encode offset + final int matchDec = matchOff - matchRef; + dest[dOff++] = (byte) matchDec; + dest[dOff++] = (byte) (matchDec >>> 8); + + // encode match len + matchLen -= 4; + if (dOff + (1 + LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + if (matchLen >= ML_MASK) { + token |= ML_MASK; + dOff = writeLen(matchLen - RUN_MASK, dest, dOff); + } else { + token |= matchLen; + } + + dest[tokenOff] = (byte) token; + + return dOff; + } + + static int lastLiterals(byte[] src, int sOff, int srcLen, byte[] dest, int dOff, int destEnd) { + final int runLen = srcLen; + + if (dOff + runLen + 1 + (runLen + 255 - RUN_MASK) / 255 > destEnd) { + throw new LZ4Exception(); + } + + if (runLen >= RUN_MASK) { + dest[dOff++] = (byte) (RUN_MASK << ML_BITS); + dOff = writeLen(runLen - RUN_MASK, dest, dOff); + } else { + dest[dOff++] = (byte) (runLen << ML_BITS); + } + // copy literals + System.arraycopy(src, sOff, dest, dOff, runLen); + dOff += runLen; + + return dOff; + } + + static int writeLen(int len, byte[] dest, int dOff) { + while (len >= 0xFF) { + dest[dOff++] = (byte) 0xFF; + len -= 0xFF; + } + dest[dOff++] = (byte) len; + return dOff; + } + + static class Match { + int start, ref, len; + + void fix(int correction) { + start += correction; + ref += correction; + len -= correction; + } + + int end() { + return start + len; + } + } + + static void copyTo(Match m1, Match m2) { + m2.len = m1.len; + m2.start = m1.start; + m2.ref = m1.ref; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnknownSizeDecompressor.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnknownSizeDecompressor.java new file mode 100644 index 000000000..c8bcdde96 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnknownSizeDecompressor.java @@ -0,0 +1,27 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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. + */ + +/** + * @deprecated Use {@link LZ4SafeDecompressor} instead. + */ +@Deprecated +public interface LZ4UnknownSizeDecompressor { + + int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff, int maxDestLen); + + int decompress(byte[] src, int srcOff, int srcLen, byte[] dest, int destOff); + +} \ No newline at end of file diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnsafeUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnsafeUtils.java new file mode 100644 index 000000000..4e6f521cf --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4UnsafeUtils.java @@ -0,0 +1,200 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.util.UnsafeUtils; +import com.fr.third.net.jpountz.util.Utils; + +import static com.fr.third.net.jpountz.util.UnsafeUtils.readInt; +import static com.fr.third.net.jpountz.util.UnsafeUtils.readShort; +import static com.fr.third.net.jpountz.util.UnsafeUtils.writeByte; +import static com.fr.third.net.jpountz.util.UnsafeUtils.writeInt; +import static com.fr.third.net.jpountz.util.UnsafeUtils.writeShort; + +import java.nio.ByteOrder; + +enum LZ4UnsafeUtils { + ; + + static void safeArraycopy(byte[] src, int srcOff, byte[] dest, int destOff, int len) { + final int fastLen = len & 0xFFFFFFF8; + wildArraycopy(src, srcOff, dest, destOff, fastLen); + for (int i = 0, slowLen = len & 0x7; i < slowLen; i += 1) { + UnsafeUtils.writeByte(dest, destOff + fastLen + i, UnsafeUtils.readByte(src, srcOff + fastLen + i)); + } + } + + static void wildArraycopy(byte[] src, int srcOff, byte[] dest, int destOff, int len) { + for (int i = 0; i < len; i += 8) { + UnsafeUtils.writeLong(dest, destOff + i, UnsafeUtils.readLong(src, srcOff + i)); + } + } + + static void wildIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchCopyEnd) { + if (dOff - matchOff < 4) { + for (int i = 0; i < 4; ++i) { + UnsafeUtils.writeByte(dest, dOff+i, UnsafeUtils.readByte(dest, matchOff+i)); + } + dOff += 4; + matchOff += 4; + int dec = 0; + assert dOff >= matchOff && dOff - matchOff < 8; + switch (dOff - matchOff) { + case 1: + matchOff -= 3; + break; + case 2: + matchOff -= 2; + break; + case 3: + matchOff -= 3; + dec = -1; + break; + case 5: + dec = 1; + break; + case 6: + dec = 2; + break; + case 7: + dec = 3; + break; + default: + break; + } + UnsafeUtils.writeInt(dest, dOff, UnsafeUtils.readInt(dest, matchOff)); + dOff += 4; + matchOff -= dec; + } else if (dOff - matchOff < LZ4Constants.COPY_LENGTH) { + UnsafeUtils.writeLong(dest, dOff, UnsafeUtils.readLong(dest, matchOff)); + dOff += dOff - matchOff; + } + while (dOff < matchCopyEnd) { + UnsafeUtils.writeLong(dest, dOff, UnsafeUtils.readLong(dest, matchOff)); + dOff += 8; + matchOff += 8; + } + } + + static void safeIncrementalCopy(byte[] dest, int matchOff, int dOff, int matchLen) { + for (int i = 0; i < matchLen; ++i) { + dest[dOff + i] = dest[matchOff + i]; + UnsafeUtils.writeByte(dest, dOff + i, UnsafeUtils.readByte(dest, matchOff + i)); + } + } + + static int readShortLittleEndian(byte[] src, int srcOff) { + short s = UnsafeUtils.readShort(src, srcOff); + if (Utils.NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + s = Short.reverseBytes(s); + } + return s & 0xFFFF; + } + + static void writeShortLittleEndian(byte[] dest, int destOff, int value) { + short s = (short) value; + if (Utils.NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + s = Short.reverseBytes(s); + } + UnsafeUtils.writeShort(dest, destOff, s); + } + + static boolean readIntEquals(byte[] src, int ref, int sOff) { + return UnsafeUtils.readInt(src, ref) == UnsafeUtils.readInt(src, sOff); + } + + static int commonBytes(byte[] src, int ref, int sOff, int srcLimit) { + int matchLen = 0; + while (sOff <= srcLimit - 8) { + if (UnsafeUtils.readLong(src, sOff) == UnsafeUtils.readLong(src, ref)) { + matchLen += 8; + ref += 8; + sOff += 8; + } else { + final int zeroBits; + if (Utils.NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + zeroBits = Long.numberOfLeadingZeros(UnsafeUtils.readLong(src, sOff) ^ UnsafeUtils.readLong(src, ref)); + } else { + zeroBits = Long.numberOfTrailingZeros(UnsafeUtils.readLong(src, sOff) ^ UnsafeUtils.readLong(src, ref)); + } + return matchLen + (zeroBits >>> 3); + } + } + while (sOff < srcLimit && UnsafeUtils.readByte(src, ref++) == UnsafeUtils.readByte(src, sOff++)) { + ++matchLen; + } + return matchLen; + } + + static int writeLen(int len, byte[] dest, int dOff) { + while (len >= 0xFF) { + UnsafeUtils.writeByte(dest, dOff++, 0xFF); + len -= 0xFF; + } + UnsafeUtils.writeByte(dest, dOff++, len); + return dOff; + } + + static int encodeSequence(byte[] src, int anchor, int matchOff, int matchRef, int matchLen, byte[] dest, int dOff, int destEnd) { + final int runLen = matchOff - anchor; + final int tokenOff = dOff++; + int token; + + if (runLen >= LZ4Constants.RUN_MASK) { + token = (byte) (LZ4Constants.RUN_MASK << LZ4Constants.ML_BITS); + dOff = writeLen(runLen - LZ4Constants.RUN_MASK, dest, dOff); + } else { + token = runLen << LZ4Constants.ML_BITS; + } + + // copy literals + wildArraycopy(src, anchor, dest, dOff, runLen); + dOff += runLen; + + // encode offset + final int matchDec = matchOff - matchRef; + dest[dOff++] = (byte) matchDec; + dest[dOff++] = (byte) (matchDec >>> 8); + + // encode match len + matchLen -= 4; + if (dOff + (1 + LZ4Constants.LAST_LITERALS) + (matchLen >>> 8) > destEnd) { + throw new LZ4Exception("maxDestLen is too small"); + } + if (matchLen >= LZ4Constants.ML_MASK) { + token |= LZ4Constants.ML_MASK; + dOff = writeLen(matchLen - LZ4Constants.RUN_MASK, dest, dOff); + } else { + token |= matchLen; + } + + dest[tokenOff] = (byte) token; + + return dOff; + } + + static int commonBytesBackward(byte[] b, int o1, int o2, int l1, int l2) { + int count = 0; + while (o1 > l1 && o2 > l2 && UnsafeUtils.readByte(b, --o1) == UnsafeUtils.readByte(b, --o2)) { + ++count; + } + return count; + } + + static int lastLiterals(byte[] src, int sOff, int srcLen, byte[] dest, int dOff, int destEnd) { + return LZ4SafeUtils.lastLiterals(src, sOff, srcLen, dest, dOff, destEnd); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Utils.java b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Utils.java new file mode 100644 index 000000000..ee2301242 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/LZ4Utils.java @@ -0,0 +1,68 @@ +package com.fr.third.net.jpountz.lz4; + +/* + * 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 com.fr.third.net.jpountz.lz4.LZ4Constants.HASH_LOG; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.HASH_LOG_64K; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.HASH_LOG_HC; +import static com.fr.third.net.jpountz.lz4.LZ4Constants.MIN_MATCH; + +enum LZ4Utils { + ; + + private static final int MAX_INPUT_SIZE = 0x7E000000; + + static int maxCompressedLength(int length) { + if (length < 0) { + throw new IllegalArgumentException("length must be >= 0, got " + length); + } else if (length >= MAX_INPUT_SIZE) { + throw new IllegalArgumentException("length must be < " + MAX_INPUT_SIZE); + } + return length + length / 255 + 16; + } + + static int hash(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG); + } + + static int hash64k(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG_64K); + } + + static int hashHC(int i) { + return (i * -1640531535) >>> ((MIN_MATCH * 8) - HASH_LOG_HC); + } + + static class Match { + int start, ref, len; + + void fix(int correction) { + start += correction; + ref += correction; + len -= correction; + } + + int end() { + return start + len; + } + } + + static void copyTo(Match m1, Match m2) { + m2.len = m1.len; + m2.start = m1.start; + m2.ref = m1.ref; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/lz4/package.html b/fine-lz4/src/com/fr/third/net/jpountz/lz4/package.html new file mode 100644 index 000000000..332673e08 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/lz4/package.html @@ -0,0 +1,55 @@ + + + + + + + +

LZ4 compression. The entry point of the API is the + {@link net.jpountz.lz4.LZ4Factory} class, which gives access to + {@link com.fr.third.net.jpountz.lz4.LZ4Compressor compressors} and + {@link com.fr.third.net.jpountz.lz4.LZ4SafeDecompressor decompressors}.

+ + +

Sample usage:

+ +
+    LZ4Factory factory = LZ4Factory.fastestInstance();
+
+    byte[] data = "12345345234572".getBytes("UTF-8");
+    final int decompressedLength = data.length;
+
+    // compress data
+    LZ4Compressor compressor = factory.fastCompressor();
+    int maxCompressedLength = compressor.maxCompressedLength(decompressedLength);
+    byte[] compressed = new byte[maxCompressedLength];
+    int compressedLength = compressor.compress(data, 0, decompressedLength, compressed, 0, maxCompressedLength);
+
+    // decompress data
+    // - method 1: when the decompressed length is known
+    LZ4FastDecompressor decompressor = factory.fastDecompressor();
+    byte[] restored = new byte[decompressedLength];
+    int compressedLength2 = decompressor.decompress(compressed, 0, restored, 0, decompressedLength);
+    // compressedLength == compressedLength2
+
+    // - method 2: when the compressed length is known (a little slower)
+    // the destination buffer needs to be over-sized
+    LZ4SafeDecompressor decompressor2 = factory.safeDecompressor();
+    int decompressedLength2 = decompressor2.decompress(compressed, 0, compressedLength, restored, 0);
+    // decompressedLength == decompressedLength2
+
+ + + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/ByteBufferUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/util/ByteBufferUtils.java new file mode 100644 index 000000000..291f38b92 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/ByteBufferUtils.java @@ -0,0 +1,92 @@ +package com.fr.third.net.jpountz.util; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; + +public enum ByteBufferUtils { + ; + + public static void checkRange(ByteBuffer buf, int off, int len) { + SafeUtils.checkLength(len); + if (len > 0) { + checkRange(buf, off); + checkRange(buf, off + len - 1); + } + } + + public static void checkRange(ByteBuffer buf, int off) { + if (off < 0 || off >= buf.capacity()) { + throw new ArrayIndexOutOfBoundsException(off); + } + } + + public static ByteBuffer inLittleEndianOrder(ByteBuffer buf) { + if (buf.order().equals(ByteOrder.LITTLE_ENDIAN)) { + return buf; + } else { + return buf.duplicate().order(ByteOrder.LITTLE_ENDIAN); + } + } + + public static ByteBuffer inNativeByteOrder(ByteBuffer buf) { + if (buf.order().equals(Utils.NATIVE_BYTE_ORDER)) { + return buf; + } else { + return buf.duplicate().order(Utils.NATIVE_BYTE_ORDER); + } + } + + public static byte readByte(ByteBuffer buf, int i) { + return buf.get(i); + } + + public static void writeInt(ByteBuffer buf, int i, int v) { + assert buf.order() == Utils.NATIVE_BYTE_ORDER; + buf.putInt(i, v); + } + + public static int readInt(ByteBuffer buf, int i) { + assert buf.order() == Utils.NATIVE_BYTE_ORDER; + return buf.getInt(i); + } + + public static int readIntLE(ByteBuffer buf, int i) { + assert buf.order() == ByteOrder.LITTLE_ENDIAN; + return buf.getInt(i); + } + + public static void writeLong(ByteBuffer buf, int i, long v) { + assert buf.order() == Utils.NATIVE_BYTE_ORDER; + buf.putLong(i, v); + } + + public static long readLong(ByteBuffer buf, int i) { + assert buf.order() == Utils.NATIVE_BYTE_ORDER; + return buf.getLong(i); + } + + public static long readLongLE(ByteBuffer buf, int i) { + assert buf.order() == ByteOrder.LITTLE_ENDIAN; + return buf.getLong(i); + } + + public static void writeByte(ByteBuffer dest, int off, int i) { + dest.put(off, (byte) i); + } + + public static void writeShortLE(ByteBuffer dest, int off, int i) { + dest.put(off, (byte) i); + dest.put(off + 1, (byte) (i >>> 8)); + } + + public static void checkNotReadOnly(ByteBuffer buffer) { + if (buffer.isReadOnly()) { + throw new ReadOnlyBufferException(); + } + } + + public static int readShortLE(ByteBuffer buf, int i) { + return (buf.get(i) & 0xFF) | ((buf.get(i+1) & 0xFF) << 8); + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/Native.java b/fine-lz4/src/com/fr/third/net/jpountz/util/Native.java new file mode 100644 index 000000000..d2fada471 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/Native.java @@ -0,0 +1,133 @@ +package com.fr.third.net.jpountz.util; + +/* + * 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.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** FOR INTERNAL USE ONLY */ +public enum Native { + ; + + private enum OS { + // Even on Windows, the default compiler from cpptasks (gcc) uses .so as a shared lib extension + WINDOWS("win32", "so"), LINUX("linux", "so"), MAC("darwin", "dylib"), SOLARIS("solaris", "so"); + public final String name, libExtension; + + private OS(String name, String libExtension) { + this.name = name; + this.libExtension = libExtension; + } + } + + private static String arch() { + return System.getProperty("os.arch"); + } + + private static OS os() { + String osName = System.getProperty("os.name"); + if (osName.contains("Linux")) { + return OS.LINUX; + } else if (osName.contains("Mac")) { + return OS.MAC; + } else if (osName.contains("Windows")) { + return OS.WINDOWS; + } else if (osName.contains("Solaris") || osName.contains("SunOS")) { + return OS.SOLARIS; + } else { + throw new UnsupportedOperationException("Unsupported operating system: " + + osName); + } + } + + private static String resourceName() { + OS os = os(); + String packagePrefix = Native.class.getPackage().getName().replace('.', '/'); + + return "/" + packagePrefix + "/" + os.name + "/" + arch() + "/liblz4-java." + os.libExtension; + } + + private static boolean loaded = false; + + public static synchronized boolean isLoaded() { + return loaded; + } + + public static synchronized void load() { + if (loaded) { + return; + } + + // Try to load lz4-java (liblz4-java.so on Linux) from the java.library.path. + try { + System.loadLibrary("lz4-java"); + loaded = true; + return; + } catch (UnsatisfiedLinkError ex) { + // Doesn't exist, so proceed to loading bundled library. + } + + String resourceName = resourceName(); + InputStream is = Native.class.getResourceAsStream(resourceName); + if (is == null) { + throw new UnsupportedOperationException("Unsupported OS/arch, cannot find " + resourceName + ". Please try building from source."); + } + File tempLib; + try { + tempLib = File.createTempFile("liblz4-java", "." + os().libExtension); + // copy to tempLib + FileOutputStream out = new FileOutputStream(tempLib); + try { + byte[] buf = new byte[4096]; + while (true) { + int read = is.read(buf); + if (read == -1) { + break; + } + out.write(buf, 0, read); + } + try { + out.close(); + out = null; + } catch (IOException e) { + // ignore + } + System.load(tempLib.getAbsolutePath()); + loaded = true; + } finally { + try { + if (out != null) { + out.close(); + } + } catch (IOException e) { + // ignore + } + if (tempLib != null && tempLib.exists()) { + if (!loaded) { + tempLib.delete(); + } else { + // try to delete on exit, does it work on Windows? + tempLib.deleteOnExit(); + } + } + } + } catch (IOException e) { + throw new ExceptionInInitializerError("Cannot unpack liblz4-java"); + } + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/SafeUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/util/SafeUtils.java new file mode 100644 index 000000000..3a572d8c5 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/SafeUtils.java @@ -0,0 +1,95 @@ +package com.fr.third.net.jpountz.util; + +/* + * 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.ByteOrder; + +public enum SafeUtils { + ; + + public static void checkRange(byte[] buf, int off) { + if (off < 0 || off >= buf.length) { + throw new ArrayIndexOutOfBoundsException(off); + } + } + + public static void checkRange(byte[] buf, int off, int len) { + checkLength(len); + if (len > 0) { + checkRange(buf, off); + checkRange(buf, off + len - 1); + } + } + + public static void checkLength(int len) { + if (len < 0) { + throw new IllegalArgumentException("lengths must be >= 0"); + } + } + + public static byte readByte(byte[] buf, int i) { + return buf[i]; + } + + public static int readIntBE(byte[] buf, int i) { + return ((buf[i] & 0xFF) << 24) | ((buf[i+1] & 0xFF) << 16) | ((buf[i+2] & 0xFF) << 8) | (buf[i+3] & 0xFF); + } + + public static int readIntLE(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i+1] & 0xFF) << 8) | ((buf[i+2] & 0xFF) << 16) | ((buf[i+3] & 0xFF) << 24); + } + + public static int readInt(byte[] buf, int i) { + if (Utils.NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + return readIntBE(buf, i); + } else { + return readIntLE(buf, i); + } + } + + public static long readLongLE(byte[] buf, int i) { + return (buf[i] & 0xFFL) | ((buf[i+1] & 0xFFL) << 8) | ((buf[i+2] & 0xFFL) << 16) | ((buf[i+3] & 0xFFL) << 24) + | ((buf[i+4] & 0xFFL) << 32) | ((buf[i+5] & 0xFFL) << 40) | ((buf[i+6] & 0xFFL) << 48) | ((buf[i+7] & 0xFFL) << 56); + } + + public static void writeShortLE(byte[] buf, int off, int v) { + buf[off++] = (byte) v; + buf[off++] = (byte) (v >>> 8); + } + + public static void writeInt(int[] buf, int off, int v) { + buf[off] = v; + } + + public static int readInt(int[] buf, int off) { + return buf[off]; + } + + public static void writeByte(byte[] dest, int off, int i) { + dest[off] = (byte) i; + } + + public static void writeShort(short[] buf, int off, int v) { + buf[off] = (short) v; + } + + public static int readShortLE(byte[] buf, int i) { + return (buf[i] & 0xFF) | ((buf[i+1] & 0xFF) << 8); + } + + public static int readShort(short[] buf, int off) { + return buf[off] & 0xFFFF; + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/UnsafeUtils.java b/fine-lz4/src/com/fr/third/net/jpountz/util/UnsafeUtils.java new file mode 100644 index 000000000..a494592ce --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/UnsafeUtils.java @@ -0,0 +1,147 @@ +package com.fr.third.net.jpountz.util; + +/* + * 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 com.fr.third.net.jpountz.util.Utils.NATIVE_BYTE_ORDER; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; + +import sun.misc.Unsafe; + +public enum UnsafeUtils { + ; + + private static final Unsafe UNSAFE; + private static final long BYTE_ARRAY_OFFSET; + private static final int BYTE_ARRAY_SCALE; + private static final long INT_ARRAY_OFFSET; + private static final int INT_ARRAY_SCALE; + private static final long SHORT_ARRAY_OFFSET; + private static final int SHORT_ARRAY_SCALE; + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + UNSAFE = (Unsafe) theUnsafe.get(null); + BYTE_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); + BYTE_ARRAY_SCALE = UNSAFE.arrayIndexScale(byte[].class); + INT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(int[].class); + INT_ARRAY_SCALE = UNSAFE.arrayIndexScale(int[].class); + SHORT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(short[].class); + SHORT_ARRAY_SCALE = UNSAFE.arrayIndexScale(short[].class); + } catch (IllegalAccessException e) { + throw new ExceptionInInitializerError("Cannot access Unsafe"); + } catch (NoSuchFieldException e) { + throw new ExceptionInInitializerError("Cannot access Unsafe"); + } catch (SecurityException e) { + throw new ExceptionInInitializerError("Cannot access Unsafe"); + } + } + + public static void checkRange(byte[] buf, int off) { + SafeUtils.checkRange(buf, off); + } + + public static void checkRange(byte[] buf, int off, int len) { + SafeUtils.checkRange(buf, off, len); + } + + public static void checkLength(int len) { + SafeUtils.checkLength(len); + } + + public static byte readByte(byte[] src, int srcOff) { + return UNSAFE.getByte(src, BYTE_ARRAY_OFFSET + BYTE_ARRAY_SCALE * srcOff); + } + + public static void writeByte(byte[] src, int srcOff, byte value) { + UNSAFE.putByte(src, BYTE_ARRAY_OFFSET + BYTE_ARRAY_SCALE * srcOff, (byte) value); + } + + public static void writeByte(byte[] src, int srcOff, int value) { + writeByte(src, srcOff, (byte) value); + } + + public static long readLong(byte[] src, int srcOff) { + return UNSAFE.getLong(src, BYTE_ARRAY_OFFSET + srcOff); + } + + public static long readLongLE(byte[] src, int srcOff) { + long i = readLong(src, srcOff); + if (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + i = Long.reverseBytes(i); + } + return i; + } + + public static void writeLong(byte[] dest, int destOff, long value) { + UNSAFE.putLong(dest, BYTE_ARRAY_OFFSET + destOff, value); + } + + public static int readInt(byte[] src, int srcOff) { + return UNSAFE.getInt(src, BYTE_ARRAY_OFFSET + srcOff); + } + + public static int readIntLE(byte[] src, int srcOff) { + int i = readInt(src, srcOff); + if (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + i = Integer.reverseBytes(i); + } + return i; + } + + public static void writeInt(byte[] dest, int destOff, int value) { + UNSAFE.putInt(dest, BYTE_ARRAY_OFFSET + destOff, value); + } + + public static short readShort(byte[] src, int srcOff) { + return UNSAFE.getShort(src, BYTE_ARRAY_OFFSET + srcOff); + } + + public static int readShortLE(byte[] src, int srcOff) { + short s = readShort(src, srcOff); + if (NATIVE_BYTE_ORDER == ByteOrder.BIG_ENDIAN) { + s = Short.reverseBytes(s); + } + return s & 0xFFFF; + } + + public static void writeShort(byte[] dest, int destOff, short value) { + UNSAFE.putShort(dest, BYTE_ARRAY_OFFSET + destOff, value); + } + + public static void writeShortLE(byte[] buf, int off, int v) { + writeByte(buf, off, (byte) v); + writeByte(buf, off + 1, (byte) (v >>> 8)); + } + + public static int readInt(int[] src, int srcOff) { + return UNSAFE.getInt(src, INT_ARRAY_OFFSET + INT_ARRAY_SCALE * srcOff); + } + + public static void writeInt(int[] dest, int destOff, int value) { + UNSAFE.putInt(dest, INT_ARRAY_OFFSET + INT_ARRAY_SCALE * destOff, value); + } + + public static int readShort(short[] src, int srcOff) { + return UNSAFE.getShort(src, SHORT_ARRAY_OFFSET + SHORT_ARRAY_SCALE * srcOff) & 0xFFFF; + } + + public static void writeShort(short[] dest, int destOff, int value) { + UNSAFE.putShort(dest, SHORT_ARRAY_OFFSET + SHORT_ARRAY_SCALE * destOff, (short) value); + } +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/Utils.java b/fine-lz4/src/com/fr/third/net/jpountz/util/Utils.java new file mode 100644 index 000000000..9610862cf --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/Utils.java @@ -0,0 +1,36 @@ +package com.fr.third.net.jpountz.util; + +/* + * 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.ByteOrder; + +public enum Utils { + ; + + public static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder(); + + private static final boolean unalignedAccessAllowed; + static { + String arch = System.getProperty("os.arch"); + unalignedAccessAllowed = arch.equals("i386") || arch.equals("x86") + || arch.equals("amd64") || arch.equals("x86_64") + || arch.equals("aarch64") || arch.equals("ppc64le"); + } + + public static boolean isUnalignedAccessAllowed() { + return unalignedAccessAllowed; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/util/package.html b/fine-lz4/src/com/fr/third/net/jpountz/util/package.html new file mode 100644 index 000000000..4b3ceb980 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/util/package.html @@ -0,0 +1,22 @@ + + + + + + + +

Utility classes.

+ + \ No newline at end of file diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java new file mode 100644 index 000000000..9583904e8 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash32Java.java @@ -0,0 +1,39 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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. + */ + +abstract class AbstractStreamingXXHash32Java extends StreamingXXHash32 { + + int v1, v2, v3, v4, memSize; + long totalLen; + final byte[] memory; + + AbstractStreamingXXHash32Java(int seed) { + super(seed); + memory = new byte[16]; + reset(); + } + + @Override + public void reset() { + v1 = seed + XXHashConstants.PRIME1 + XXHashConstants.PRIME2; + v2 = seed + XXHashConstants.PRIME2; + v3 = seed + 0; + v4 = seed - XXHashConstants.PRIME1; + totalLen = 0; + memSize = 0; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java new file mode 100644 index 000000000..cce30934b --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/AbstractStreamingXXHash64Java.java @@ -0,0 +1,40 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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. + */ + +abstract class AbstractStreamingXXHash64Java extends StreamingXXHash64 { + + int memSize; + long v1, v2, v3, v4; + long totalLen; + final byte[] memory; + + AbstractStreamingXXHash64Java(long seed) { + super(seed); + memory = new byte[32]; + reset(); + } + + @Override + public void reset() { + v1 = seed + XXHashConstants.PRIME64_1 + XXHashConstants.PRIME64_2; + v2 = seed + XXHashConstants.PRIME64_2; + v3 = seed + 0; + v4 = seed - XXHashConstants.PRIME64_1; + totalLen = 0; + memSize = 0; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32.java new file mode 100644 index 000000000..8d2497d6f --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32.java @@ -0,0 +1,119 @@ +package com.fr.third.net.jpountz.xxhash; + +import java.util.zip.Checksum; + +/* + * 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. + */ + + + +/** + * Streaming interface for {@link XXHash32}. + *

+ * This API is compatible with the {@link XXHash32 block API} and the following + * code samples are equivalent: + *

+ *   int hash(XXHashFactory xxhashFactory, byte[] buf, int off, int len, int seed) {
+ *     return xxhashFactory.hash32().hash(buf, off, len, seed);
+ *   }
+ * 
+ *
+ *   int hash(XXHashFactory xxhashFactory, byte[] buf, int off, int len, int seed) {
+ *     StreamingXXHash32 sh32 = xxhashFactory.newStreamingHash32(seed);
+ *     sh32.update(buf, off, len);
+ *     return sh32.getValue();
+ *   }
+ * 
+ *

+ * Instances of this class are not thread-safe. + */ +public abstract class StreamingXXHash32 { + + interface Factory { + + StreamingXXHash32 newStreamingHash(int seed); + + } + + final int seed; + + StreamingXXHash32(int seed) { + this.seed = seed; + } + + /** + * Returns the value of the checksum. + * + * @return the checksum + */ + public abstract int getValue(); + + /** + * Updates the value of the hash with buf[off:off+len]. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + */ + public abstract void update(byte[] buf, int off, int len); + + /** + * Resets this instance to the state it had right after instantiation. The + * seed remains unchanged. + */ + public abstract void reset(); + + @Override + public String toString() { + return getClass().getSimpleName() + "(seed=" + seed + ")"; + } + + /** + * Returns a {@link Checksum} view of this instance. Modifications to the view + * will modify this instance too and vice-versa. + * + * @return the {@link Checksum} object representing this instance + */ + public final Checksum asChecksum() { + return new Checksum() { + + @Override + public long getValue() { + return StreamingXXHash32.this.getValue() & 0xFFFFFFFL; + } + + @Override + public void reset() { + StreamingXXHash32.this.reset(); + } + + @Override + public void update(int b) { + StreamingXXHash32.this.update(new byte[] {(byte) b}, 0, 1); + } + + @Override + public void update(byte[] b, int off, int len) { + StreamingXXHash32.this.update(b, off, len); + } + + @Override + public String toString() { + return StreamingXXHash32.this.toString(); + } + + }; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JNI.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JNI.java new file mode 100644 index 000000000..133c86103 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JNI.java @@ -0,0 +1,71 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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. + */ + + +final class StreamingXXHash32JNI extends StreamingXXHash32 { + + static class Factory implements StreamingXXHash32.Factory { + + public static final StreamingXXHash32.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash32 newStreamingHash(int seed) { + return new StreamingXXHash32JNI(seed); + } + + } + + private long state; + + StreamingXXHash32JNI(int seed) { + super(seed); + state = XXHashJNI.XXH32_init(seed); + } + + private void checkState() { + if (state == 0) { + throw new AssertionError("Already finalized"); + } + } + + @Override + public void reset() { + checkState(); + XXHashJNI.XXH32_free(state); + state = XXHashJNI.XXH32_init(seed); + } + + @Override + public int getValue() { + checkState(); + return XXHashJNI.XXH32_digest(state); + } + + @Override + public void update(byte[] bytes, int off, int len) { + checkState(); + XXHashJNI.XXH32_update(state, bytes, off, len); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + // free memory + XXHashJNI.XXH32_free(state); + state = 0; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaSafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaSafe.java new file mode 100644 index 000000000..fde930721 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaSafe.java @@ -0,0 +1,142 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static com.fr.third.net.jpountz.util.SafeUtils.*; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; +import static java.lang.Integer.rotateLeft; + +/** + * Streaming xxhash. + */ +final class StreamingXXHash32JavaSafe extends AbstractStreamingXXHash32Java { + + static class Factory implements StreamingXXHash32.Factory { + + public static final StreamingXXHash32.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash32 newStreamingHash(int seed) { + return new StreamingXXHash32JavaSafe(seed); + } + + } + + StreamingXXHash32JavaSafe(int seed) { + super(seed); + } + + @Override + public int getValue() { + int h32; + if (totalLen >= 16) { + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += totalLen; + + int off = 0; + while (off <= memSize - 4) { + h32 += readIntLE(memory, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < memSize) { + h32 += (readByte(memory, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + @Override + public void update(byte[] buf, int off, int len) { + checkRange(buf, off, len); + + totalLen += len; + + if (memSize + len < 16) { // fill in tmp buffer + System.arraycopy(buf, off, memory, memSize, len); + memSize += len; + return; + } + + final int end = off + len; + + if (memSize > 0) { // data left from previous update + System.arraycopy(buf, off, memory, memSize, 16 - memSize); + + v1 += readIntLE(memory, 0) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + + v2 += readIntLE(memory, 4) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + + v3 += readIntLE(memory, 8) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + + v4 += readIntLE(memory, 12) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + + off += 16 - memSize; + memSize = 0; + } + + { + final int limit = end - 16; + int v1 = this.v1; + int v2 = this.v2; + int v3 = this.v3; + int v4 = this.v4; + + while (off <= limit) { + v1 += readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + if (off < end) { + System.arraycopy(buf, off, memory, 0, end - off); + memSize = end - off; + } + } + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaUnsafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaUnsafe.java new file mode 100644 index 000000000..bd67ebad3 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash32JavaUnsafe.java @@ -0,0 +1,142 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static com.fr.third.net.jpountz.util.UnsafeUtils.*; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; +import static java.lang.Integer.rotateLeft; + +/** + * Streaming xxhash. + */ +final class StreamingXXHash32JavaUnsafe extends AbstractStreamingXXHash32Java { + + static class Factory implements StreamingXXHash32.Factory { + + public static final StreamingXXHash32.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash32 newStreamingHash(int seed) { + return new StreamingXXHash32JavaUnsafe(seed); + } + + } + + StreamingXXHash32JavaUnsafe(int seed) { + super(seed); + } + + @Override + public int getValue() { + int h32; + if (totalLen >= 16) { + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += totalLen; + + int off = 0; + while (off <= memSize - 4) { + h32 += readIntLE(memory, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < memSize) { + h32 += (readByte(memory, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + @Override + public void update(byte[] buf, int off, int len) { + checkRange(buf, off, len); + + totalLen += len; + + if (memSize + len < 16) { // fill in tmp buffer + System.arraycopy(buf, off, memory, memSize, len); + memSize += len; + return; + } + + final int end = off + len; + + if (memSize > 0) { // data left from previous update + System.arraycopy(buf, off, memory, memSize, 16 - memSize); + + v1 += readIntLE(memory, 0) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + + v2 += readIntLE(memory, 4) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + + v3 += readIntLE(memory, 8) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + + v4 += readIntLE(memory, 12) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + + off += 16 - memSize; + memSize = 0; + } + + { + final int limit = end - 16; + int v1 = this.v1; + int v2 = this.v2; + int v3 = this.v3; + int v4 = this.v4; + + while (off <= limit) { + v1 += readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + if (off < end) { + System.arraycopy(buf, off, memory, 0, end - off); + memSize = end - off; + } + } + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64.java new file mode 100644 index 000000000..e988f5218 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64.java @@ -0,0 +1,119 @@ +package com.fr.third.net.jpountz.xxhash; + +import java.util.zip.Checksum; + +/* + * 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. + */ + + + +/** + * Streaming interface for {@link XXHash64}. + *

+ * This API is compatible with the {@link XXHash64 block API} and the following + * code samples are equivalent: + *

+ *   long hash(XXHashFactory xxhashFactory, byte[] buf, int off, int len, long seed) {
+ *     return xxhashFactory.hash64().hash(buf, off, len, seed);
+ *   }
+ * 
+ *
+ *   long hash(XXHashFactory xxhashFactory, byte[] buf, int off, int len, long seed) {
+ *     StreamingXXHash64 sh64 = xxhashFactory.newStreamingHash64(seed);
+ *     sh64.update(buf, off, len);
+ *     return sh64.getValue();
+ *   }
+ * 
+ *

+ * Instances of this class are not thread-safe. + */ +public abstract class StreamingXXHash64 { + + interface Factory { + + StreamingXXHash64 newStreamingHash(long seed); + + } + + final long seed; + + StreamingXXHash64(long seed) { + this.seed = seed; + } + + /** + * Returns the value of the checksum. + * + * @return the checksum + */ + public abstract long getValue(); + + /** + * Updates the value of the hash with buf[off:off+len]. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + */ + public abstract void update(byte[] buf, int off, int len); + + /** + * Resets this instance to the state it had right after instantiation. The + * seed remains unchanged. + */ + public abstract void reset(); + + @Override + public String toString() { + return getClass().getSimpleName() + "(seed=" + seed + ")"; + } + + /** + * Returns a {@link Checksum} view of this instance. Modifications to the view + * will modify this instance too and vice-versa. + * + * @return the {@link Checksum} object representing this instance + */ + public final Checksum asChecksum() { + return new Checksum() { + + @Override + public long getValue() { + return StreamingXXHash64.this.getValue(); + } + + @Override + public void reset() { + StreamingXXHash64.this.reset(); + } + + @Override + public void update(int b) { + StreamingXXHash64.this.update(new byte[] {(byte) b}, 0, 1); + } + + @Override + public void update(byte[] b, int off, int len) { + StreamingXXHash64.this.update(b, off, len); + } + + @Override + public String toString() { + return StreamingXXHash64.this.toString(); + } + + }; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JNI.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JNI.java new file mode 100644 index 000000000..5d8f9b840 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JNI.java @@ -0,0 +1,71 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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. + */ + + +final class StreamingXXHash64JNI extends StreamingXXHash64 { + + static class Factory implements StreamingXXHash64.Factory { + + public static final StreamingXXHash64.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash64 newStreamingHash(long seed) { + return new StreamingXXHash64JNI(seed); + } + + } + + private long state; + + StreamingXXHash64JNI(long seed) { + super(seed); + state = XXHashJNI.XXH64_init(seed); + } + + private void checkState() { + if (state == 0) { + throw new AssertionError("Already finalized"); + } + } + + @Override + public void reset() { + checkState(); + XXHashJNI.XXH64_free(state); + state = XXHashJNI.XXH64_init(seed); + } + + @Override + public long getValue() { + checkState(); + return XXHashJNI.XXH64_digest(state); + } + + @Override + public void update(byte[] bytes, int off, int len) { + checkState(); + XXHashJNI.XXH64_update(state, bytes, off, len); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + // free memory + XXHashJNI.XXH64_free(state); + state = 0; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaSafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaSafe.java new file mode 100644 index 000000000..2445ff415 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaSafe.java @@ -0,0 +1,166 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static com.fr.third.net.jpountz.util.SafeUtils.*; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; +import static java.lang.Long.rotateLeft; + +/** + * Streaming xxhash. + */ +final class StreamingXXHash64JavaSafe extends AbstractStreamingXXHash64Java { + + static class Factory implements StreamingXXHash64.Factory { + + public static final StreamingXXHash64.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash64 newStreamingHash(long seed) { + return new StreamingXXHash64JavaSafe(seed); + } + + } + + StreamingXXHash64JavaSafe(long seed) { + super(seed); + } + + @Override + public long getValue() { + long h64; + if (totalLen >= 32) { + long v1 = this.v1; + long v2 = this.v2; + long v3 = this.v3; + long v4 = this.v4; + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64*PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64*PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64*PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64*PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += totalLen; + + int off = 0; + while (off <= memSize - 8) { + long k1 = readLongLE(memory, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= memSize - 4) { + h64 ^= (readIntLE(memory, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < memSize) { + h64 ^= (memory[off] & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + @Override + public void update(byte[] buf, int off, int len) { + checkRange(buf, off, len); + + totalLen += len; + + if (memSize + len < 32) { // fill in tmp buffer + System.arraycopy(buf, off, memory, memSize, len); + memSize += len; + return; + } + + final int end = off + len; + + if (memSize > 0) { // data left from previous update + System.arraycopy(buf, off, memory, memSize, 32 - memSize); + + v1 += readLongLE(memory, 0) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + + v2 += readLongLE(memory, 8) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + + v3 += readLongLE(memory, 16) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + + v4 += readLongLE(memory, 24) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + + off += 32 - memSize; + memSize = 0; + } + + { + final int limit = end - 32; + long v1 = this.v1; + long v2 = this.v2; + long v3 = this.v3; + long v4 = this.v4; + + while (off <= limit) { + v1 += readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + if (off < end) { + System.arraycopy(buf, off, memory, 0, end - off); + memSize = end - off; + } + } + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaUnsafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaUnsafe.java new file mode 100644 index 000000000..2042bea9d --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/StreamingXXHash64JavaUnsafe.java @@ -0,0 +1,166 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static com.fr.third.net.jpountz.util.UnsafeUtils.*; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; +import static java.lang.Long.rotateLeft; + +/** + * Streaming xxhash. + */ +final class StreamingXXHash64JavaUnsafe extends AbstractStreamingXXHash64Java { + + static class Factory implements StreamingXXHash64.Factory { + + public static final StreamingXXHash64.Factory INSTANCE = new Factory(); + + @Override + public StreamingXXHash64 newStreamingHash(long seed) { + return new StreamingXXHash64JavaUnsafe(seed); + } + + } + + StreamingXXHash64JavaUnsafe(long seed) { + super(seed); + } + + @Override + public long getValue() { + long h64; + if (totalLen >= 32) { + long v1 = this.v1; + long v2 = this.v2; + long v3 = this.v3; + long v4 = this.v4; + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64*PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64*PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64*PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64*PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += totalLen; + + int off = 0; + while (off <= memSize - 8) { + long k1 = readLongLE(memory, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= memSize - 4) { + h64 ^= (readIntLE(memory, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < memSize) { + h64 ^= (memory[off] & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + @Override + public void update(byte[] buf, int off, int len) { + checkRange(buf, off, len); + + totalLen += len; + + if (memSize + len < 32) { // fill in tmp buffer + System.arraycopy(buf, off, memory, memSize, len); + memSize += len; + return; + } + + final int end = off + len; + + if (memSize > 0) { // data left from previous update + System.arraycopy(buf, off, memory, memSize, 32 - memSize); + + v1 += readLongLE(memory, 0) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + + v2 += readLongLE(memory, 8) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + + v3 += readLongLE(memory, 16) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + + v4 += readLongLE(memory, 24) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + + off += 32 - memSize; + memSize = 0; + } + + { + final int limit = end - 32; + long v1 = this.v1; + long v2 = this.v2; + long v3 = this.v3; + long v4 = this.v4; + + while (off <= limit) { + v1 += readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } + + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.v4 = v4; + } + + if (off < end) { + System.arraycopy(buf, off, memory, 0, end - off); + memSize = end - off; + } + } + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32.java new file mode 100644 index 000000000..d40579796 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32.java @@ -0,0 +1,71 @@ +package com.fr.third.net.jpountz.xxhash; + +import java.nio.ByteBuffer; + +/* + * 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. + */ + +/** + * A 32-bits hash. + *

+ * Instances of this class are thread-safe. + */ +public abstract class XXHash32 { + + /** + * Computes the 32-bits hash of buf[off:off+len] using seed + * seed. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + * @param seed the seed to use + * @return the hash value + */ + public abstract int hash(byte[] buf, int off, int len, int seed); + + /** + * Computes the hash of the given slice of the {@link ByteBuffer}. + * {@link ByteBuffer#position() position} and {@link ByteBuffer#limit() limit} + * are not modified. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + * @param seed the seed to use + * @return the hash value + */ + public abstract int hash(ByteBuffer buf, int off, int len, int seed); + + /** + * Computes the hash of the given {@link ByteBuffer}. The + * {@link ByteBuffer#position() position} is moved in order to reflect bytes + * which have been read. + * + * @param buf the input data + * @param seed the seed to use + * @return the hash value + */ + public final int hash(ByteBuffer buf, int seed) { + final int hash = hash(buf, buf.position(), buf.remaining(), seed); + buf.position(buf.limit()); + return hash; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JNI.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JNI.java new file mode 100644 index 000000000..144dd3026 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JNI.java @@ -0,0 +1,52 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +import static com.fr.third.net.jpountz.util.ByteBufferUtils.checkRange; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; + +import java.nio.ByteBuffer; + +final class XXHash32JNI extends XXHash32 { + + public static final XXHash32 INSTANCE = new XXHash32JNI(); + private static XXHash32 SAFE_INSTANCE; + + @Override + public int hash(byte[] buf, int off, int len, int seed) { + SafeUtils.checkRange(buf, off, len); + return XXHashJNI.XXH32(buf, off, len, seed); + } + + @Override + public int hash(ByteBuffer buf, int off, int len, int seed) { + if (buf.isDirect()) { + ByteBufferUtils.checkRange(buf, off, len); + return XXHashJNI.XXH32BB(buf, off, len, seed); + } else if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } else { + XXHash32 safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = XXHashFactory.safeInstance().hash32(); + } + return safeInstance.hash(buf, off, len, seed); + } + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaSafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaSafe.java new file mode 100644 index 000000000..e08754103 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaSafe.java @@ -0,0 +1,154 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static java.lang.Integer.rotateLeft; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.SafeUtils; +import com.fr.third.net.jpountz.util.ByteBufferUtils; + +/** + * {@link XXHash32} implementation. + */ +final class XXHash32JavaSafe extends XXHash32 { + + public static final XXHash32 INSTANCE = new XXHash32JavaSafe(); + + @Override + public int hash(byte[] buf, int off, int len, int seed) { + + SafeUtils.checkRange(buf, off, len); + + final int end = off + len; + int h32; + + if (len >= 16) { + final int limit = end - 16; + int v1 = seed + PRIME1 + PRIME2; + int v2 = seed + PRIME2; + int v3 = seed + 0; + int v4 = seed - PRIME1; + do { + v1 += SafeUtils.readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += SafeUtils.readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += SafeUtils.readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += SafeUtils.readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } while (off <= limit); + + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += len; + + while (off <= end - 4) { + h32 += SafeUtils.readIntLE(buf, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < end) { + h32 += (SafeUtils.readByte(buf, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + @Override + public int hash(ByteBuffer buf, int off, int len, int seed) { + + if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } + ByteBufferUtils.checkRange(buf, off, len); + buf = ByteBufferUtils.inLittleEndianOrder(buf); + + final int end = off + len; + int h32; + + if (len >= 16) { + final int limit = end - 16; + int v1 = seed + PRIME1 + PRIME2; + int v2 = seed + PRIME2; + int v3 = seed + 0; + int v4 = seed - PRIME1; + do { + v1 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } while (off <= limit); + + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += len; + + while (off <= end - 4) { + h32 += ByteBufferUtils.readIntLE(buf, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < end) { + h32 += (ByteBufferUtils.readByte(buf, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaUnsafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaUnsafe.java new file mode 100644 index 000000000..b5473e96d --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash32JavaUnsafe.java @@ -0,0 +1,154 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static java.lang.Integer.rotateLeft; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.UnsafeUtils; +import com.fr.third.net.jpountz.util.ByteBufferUtils; + +/** + * {@link XXHash32} implementation. + */ +final class XXHash32JavaUnsafe extends XXHash32 { + + public static final XXHash32 INSTANCE = new XXHash32JavaUnsafe(); + + @Override + public int hash(byte[] buf, int off, int len, int seed) { + + UnsafeUtils.checkRange(buf, off, len); + + final int end = off + len; + int h32; + + if (len >= 16) { + final int limit = end - 16; + int v1 = seed + PRIME1 + PRIME2; + int v2 = seed + PRIME2; + int v3 = seed + 0; + int v4 = seed - PRIME1; + do { + v1 += UnsafeUtils.readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += UnsafeUtils.readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += UnsafeUtils.readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += UnsafeUtils.readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } while (off <= limit); + + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += len; + + while (off <= end - 4) { + h32 += UnsafeUtils.readIntLE(buf, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < end) { + h32 += (UnsafeUtils.readByte(buf, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + @Override + public int hash(ByteBuffer buf, int off, int len, int seed) { + + if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } + ByteBufferUtils.checkRange(buf, off, len); + buf = ByteBufferUtils.inLittleEndianOrder(buf); + + final int end = off + len; + int h32; + + if (len >= 16) { + final int limit = end - 16; + int v1 = seed + PRIME1 + PRIME2; + int v2 = seed + PRIME2; + int v3 = seed + 0; + int v4 = seed - PRIME1; + do { + v1 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v1 = rotateLeft(v1, 13); + v1 *= PRIME1; + off += 4; + + v2 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v2 = rotateLeft(v2, 13); + v2 *= PRIME1; + off += 4; + + v3 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v3 = rotateLeft(v3, 13); + v3 *= PRIME1; + off += 4; + + v4 += ByteBufferUtils.readIntLE(buf, off) * PRIME2; + v4 = rotateLeft(v4, 13); + v4 *= PRIME1; + off += 4; + } while (off <= limit); + + h32 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + } else { + h32 = seed + PRIME5; + } + + h32 += len; + + while (off <= end - 4) { + h32 += ByteBufferUtils.readIntLE(buf, off) * PRIME3; + h32 = rotateLeft(h32, 17) * PRIME4; + off += 4; + } + + while (off < end) { + h32 += (ByteBufferUtils.readByte(buf, off) & 0xFF) * PRIME5; + h32 = rotateLeft(h32, 11) * PRIME1; + ++off; + } + + h32 ^= h32 >>> 15; + h32 *= PRIME2; + h32 ^= h32 >>> 13; + h32 *= PRIME3; + h32 ^= h32 >>> 16; + + return h32; + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64.java new file mode 100644 index 000000000..2861e3009 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64.java @@ -0,0 +1,71 @@ +package com.fr.third.net.jpountz.xxhash; + +import java.nio.ByteBuffer; + +/* + * 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. + */ + +/** + * A 64-bits hash. + *

+ * Instances of this class are thread-safe. + */ +public abstract class XXHash64 { + + /** + * Computes the 64-bits hash of buf[off:off+len] using seed + * seed. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + * @param seed the seed to use + * @return the hash value + */ + public abstract long hash(byte[] buf, int off, int len, long seed); + + /** + * Computes the hash of the given slice of the {@link ByteBuffer}. + * {@link ByteBuffer#position() position} and {@link ByteBuffer#limit() limit} + * are not modified. + * + * @param buf the input data + * @param off the start offset in buf + * @param len the number of bytes to hash + * @param seed the seed to use + * @return the hash value + */ + public abstract long hash(ByteBuffer buf, int off, int len, long seed); + + /** + * Computes the hash of the given {@link ByteBuffer}. The + * {@link ByteBuffer#position() position} is moved in order to reflect bytes + * which have been read. + * + * @param buf the input data + * @param seed the seed to use + * @return the hash value + */ + public final long hash(ByteBuffer buf, long seed) { + final long hash = hash(buf, buf.position(), buf.remaining(), seed); + buf.position(buf.limit()); + return hash; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JNI.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JNI.java new file mode 100644 index 000000000..0a44018ae --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JNI.java @@ -0,0 +1,52 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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 com.fr.third.net.jpountz.util.ByteBufferUtils; +import com.fr.third.net.jpountz.util.SafeUtils; + +import static com.fr.third.net.jpountz.util.ByteBufferUtils.checkRange; +import static com.fr.third.net.jpountz.util.SafeUtils.checkRange; + +import java.nio.ByteBuffer; + +final class XXHash64JNI extends XXHash64 { + + public static final XXHash64 INSTANCE = new XXHash64JNI(); + private static XXHash64 SAFE_INSTANCE; + + @Override + public long hash(byte[] buf, int off, int len, long seed) { + SafeUtils.checkRange(buf, off, len); + return XXHashJNI.XXH64(buf, off, len, seed); + } + + @Override + public long hash(ByteBuffer buf, int off, int len, long seed) { + if (buf.isDirect()) { + ByteBufferUtils.checkRange(buf, off, len); + return XXHashJNI.XXH64BB(buf, off, len, seed); + } else if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } else { + XXHash64 safeInstance = SAFE_INSTANCE; + if (safeInstance == null) { + safeInstance = SAFE_INSTANCE = XXHashFactory.safeInstance().hash64(); + } + return safeInstance.hash(buf, off, len, seed); + } + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaSafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaSafe.java new file mode 100644 index 000000000..e91aec1d4 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaSafe.java @@ -0,0 +1,192 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static java.lang.Long.rotateLeft; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.SafeUtils; +import com.fr.third.net.jpountz.util.ByteBufferUtils; + +/** + * {@link XXHash64} implementation. + */ +final class XXHash64JavaSafe extends XXHash64 { + + public static final XXHash64 INSTANCE = new XXHash64JavaSafe(); + + @Override + public long hash(byte[] buf, int off, int len, long seed) { + + SafeUtils.checkRange(buf, off, len); + + final int end = off + len; + long h64; + + if (len >= 32) { + final int limit = end - 32; + long v1 = seed + PRIME64_1 + PRIME64_2; + long v2 = seed + PRIME64_2; + long v3 = seed + 0; + long v4 = seed - PRIME64_1; + do { + v1 += SafeUtils.readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += SafeUtils.readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += SafeUtils.readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += SafeUtils.readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } while (off <= limit); + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64 * PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += len; + + while (off <= end - 8) { + long k1 = SafeUtils.readLongLE(buf, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= end - 4) { + h64 ^= (SafeUtils.readIntLE(buf, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < end) { + h64 ^= (SafeUtils.readByte(buf, off) & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + @Override + public long hash(ByteBuffer buf, int off, int len, long seed) { + + if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } + ByteBufferUtils.checkRange(buf, off, len); + buf = ByteBufferUtils.inLittleEndianOrder(buf); + + final int end = off + len; + long h64; + + if (len >= 32) { + final int limit = end - 32; + long v1 = seed + PRIME64_1 + PRIME64_2; + long v2 = seed + PRIME64_2; + long v3 = seed + 0; + long v4 = seed - PRIME64_1; + do { + v1 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } while (off <= limit); + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64 * PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += len; + + while (off <= end - 8) { + long k1 = ByteBufferUtils.readLongLE(buf, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= end - 4) { + h64 ^= (ByteBufferUtils.readIntLE(buf, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < end) { + h64 ^= (ByteBufferUtils.readByte(buf, off) & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaUnsafe.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaUnsafe.java new file mode 100644 index 000000000..faac26bc9 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHash64JavaUnsafe.java @@ -0,0 +1,192 @@ +// Auto-generated: DO NOT EDIT + +package com.fr.third.net.jpountz.xxhash; + +import static com.fr.third.net.jpountz.xxhash.XXHashConstants.*; +import static java.lang.Long.rotateLeft; + +import java.nio.ByteBuffer; + +import com.fr.third.net.jpountz.util.UnsafeUtils; +import com.fr.third.net.jpountz.util.ByteBufferUtils; + +/** + * {@link XXHash64} implementation. + */ +final class XXHash64JavaUnsafe extends XXHash64 { + + public static final XXHash64 INSTANCE = new XXHash64JavaUnsafe(); + + @Override + public long hash(byte[] buf, int off, int len, long seed) { + + UnsafeUtils.checkRange(buf, off, len); + + final int end = off + len; + long h64; + + if (len >= 32) { + final int limit = end - 32; + long v1 = seed + PRIME64_1 + PRIME64_2; + long v2 = seed + PRIME64_2; + long v3 = seed + 0; + long v4 = seed - PRIME64_1; + do { + v1 += UnsafeUtils.readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += UnsafeUtils.readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += UnsafeUtils.readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += UnsafeUtils.readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } while (off <= limit); + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64 * PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += len; + + while (off <= end - 8) { + long k1 = UnsafeUtils.readLongLE(buf, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= end - 4) { + h64 ^= (UnsafeUtils.readIntLE(buf, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < end) { + h64 ^= (UnsafeUtils.readByte(buf, off) & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + @Override + public long hash(ByteBuffer buf, int off, int len, long seed) { + + if (buf.hasArray()) { + return hash(buf.array(), off + buf.arrayOffset(), len, seed); + } + ByteBufferUtils.checkRange(buf, off, len); + buf = ByteBufferUtils.inLittleEndianOrder(buf); + + final int end = off + len; + long h64; + + if (len >= 32) { + final int limit = end - 32; + long v1 = seed + PRIME64_1 + PRIME64_2; + long v2 = seed + PRIME64_2; + long v3 = seed + 0; + long v4 = seed - PRIME64_1; + do { + v1 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v1 = rotateLeft(v1, 31); + v1 *= PRIME64_1; + off += 8; + + v2 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v2 = rotateLeft(v2, 31); + v2 *= PRIME64_1; + off += 8; + + v3 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v3 = rotateLeft(v3, 31); + v3 *= PRIME64_1; + off += 8; + + v4 += ByteBufferUtils.readLongLE(buf, off) * PRIME64_2; + v4 = rotateLeft(v4, 31); + v4 *= PRIME64_1; + off += 8; + } while (off <= limit); + + h64 = rotateLeft(v1, 1) + rotateLeft(v2, 7) + rotateLeft(v3, 12) + rotateLeft(v4, 18); + + v1 *= PRIME64_2; v1 = rotateLeft(v1, 31); v1 *= PRIME64_1; h64 ^= v1; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v2 *= PRIME64_2; v2 = rotateLeft(v2, 31); v2 *= PRIME64_1; h64 ^= v2; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v3 *= PRIME64_2; v3 = rotateLeft(v3, 31); v3 *= PRIME64_1; h64 ^= v3; + h64 = h64 * PRIME64_1 + PRIME64_4; + + v4 *= PRIME64_2; v4 = rotateLeft(v4, 31); v4 *= PRIME64_1; h64 ^= v4; + h64 = h64 * PRIME64_1 + PRIME64_4; + } else { + h64 = seed + PRIME64_5; + } + + h64 += len; + + while (off <= end - 8) { + long k1 = ByteBufferUtils.readLongLE(buf, off); + k1 *= PRIME64_2; k1 = rotateLeft(k1, 31); k1 *= PRIME64_1; h64 ^= k1; + h64 = rotateLeft(h64, 27) * PRIME64_1 + PRIME64_4; + off += 8; + } + + if (off <= end - 4) { + h64 ^= (ByteBufferUtils.readIntLE(buf, off) & 0xFFFFFFFFL) * PRIME64_1; + h64 = rotateLeft(h64, 23) * PRIME64_2 + PRIME64_3; + off += 4; + } + + while (off < end) { + h64 ^= (ByteBufferUtils.readByte(buf, off) & 0xFF) * PRIME64_5; + h64 = rotateLeft(h64, 11) * PRIME64_1; + ++off; + } + + h64 ^= h64 >>> 33; + h64 *= PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= PRIME64_3; + h64 ^= h64 >>> 32; + + return h64; + } + + +} + diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashConstants.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashConstants.java new file mode 100644 index 000000000..79aa59b8a --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashConstants.java @@ -0,0 +1,31 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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. + */ + +enum XXHashConstants { + ; + + static final int PRIME1 = -1640531535; + static final int PRIME2 = -2048144777; + static final int PRIME3 = -1028477379; + static final int PRIME4 = 668265263; + static final int PRIME5 = 374761393; + + static final long PRIME64_1 = -7046029288634856825L; //11400714785074694791 + static final long PRIME64_2 = -4417276706812531889L; //14029467366897019727 + static final long PRIME64_3 = 1609587929392839161L; + static final long PRIME64_4 = -8796714831421723037L; //9650029242287828579 + static final long PRIME64_5 = 2870177450012600261L; +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashFactory.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashFactory.java new file mode 100644 index 000000000..6157dbace --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashFactory.java @@ -0,0 +1,257 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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.lang.reflect.Field; +import java.util.Random; + +import com.fr.third.net.jpountz.util.Native; +import com.fr.third.net.jpountz.util.Utils; + +/** + * Entry point to get {@link XXHash32} and {@link StreamingXXHash32} instances. + *

+ * This class has 3 instances

    + *
  • a {@link #nativeInstance() native} instance which is a JNI binding to + * the original LZ4 C implementation. + *
  • a {@link #safeInstance() safe Java} instance which is a pure Java port + * of the original C library,
  • + *
  • an {@link #unsafeInstance() unsafe Java} instance which is a Java port + * using the unofficial {@link sun.misc.Unsafe} API. + *
+ *

+ * Only the {@link #safeInstance() safe instance} is guaranteed to work on your + * JVM, as a consequence it is advised to use the {@link #fastestInstance()} or + * {@link #fastestJavaInstance()} to pull a {@link XXHashFactory} instance. + *

+ * All methods from this class are very costly, so you should get an instance + * once, and then reuse it whenever possible. This is typically done by storing + * a {@link XXHashFactory} instance in a static field. + */ +public final class XXHashFactory { + + private static XXHashFactory instance(String impl) { + try { + return new XXHashFactory(impl); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private static XXHashFactory NATIVE_INSTANCE, + JAVA_UNSAFE_INSTANCE, + JAVA_SAFE_INSTANCE; + + /** + * Returns a {@link XXHashFactory} that returns {@link XXHash32} instances that + * are native bindings to the original C API. + *

+ * Please note that this instance has some traps you should be aware of:

    + *
  1. Upon loading this instance, files will be written to the temporary + * directory of the system. Although these files are supposed to be deleted + * when the JVM exits, they might remain on systems that don't support + * removal of files being used such as Windows. + *
  2. The instance can only be loaded once per JVM. This can be a problem + * if your application uses multiple class loaders (such as most servlet + * containers): this instance will only be available to the children of the + * class loader which has loaded it. As a consequence, it is advised to + * either not use this instance in webapps or to put this library in the lib + * directory of your servlet container so that it is loaded by the system + * class loader. + *
+ * + * @return a {@link XXHashFactory} that returns {@link XXHash32} instances that + * are native bindings to the original C API. + */ + public static synchronized XXHashFactory nativeInstance() { + if (NATIVE_INSTANCE == null) { + NATIVE_INSTANCE = instance("JNI"); + } + return NATIVE_INSTANCE; + } + + /** + * Returns a {@link XXHashFactory} that returns {@link XXHash32} instances that + * are written with Java's official API. + * + * @return a {@link XXHashFactory} that returns {@link XXHash32} instances that + * are written with Java's official API. + */ + public static synchronized XXHashFactory safeInstance() { + if (JAVA_SAFE_INSTANCE == null) { + JAVA_SAFE_INSTANCE = instance("JavaSafe"); + } + return JAVA_SAFE_INSTANCE; + } + + /** + * Returns a {@link XXHashFactory} that returns {@link XXHash32} instances that + * may use {@link sun.misc.Unsafe} to speed up hashing. + * + * @return a {@link XXHashFactory} that returns {@link XXHash32} instances that + * may use {@link sun.misc.Unsafe} to speed up hashing. + */ + public static synchronized XXHashFactory unsafeInstance() { + if (JAVA_UNSAFE_INSTANCE == null) { + JAVA_UNSAFE_INSTANCE = instance("JavaUnsafe"); + } + return JAVA_UNSAFE_INSTANCE; + } + + /** + * Returns the fastest available {@link XXHashFactory} instance which does not + * rely on JNI bindings. It first tries to load the + * {@link #unsafeInstance() unsafe instance}, and then the + * {@link #safeInstance() safe Java instance} if the JVM doesn't have a + * working {@link sun.misc.Unsafe}. + * + * @return the fastest available {@link XXHashFactory} instance which does not + * rely on JNI bindings. + */ + public static XXHashFactory fastestJavaInstance() { + if (Utils.isUnalignedAccessAllowed()) { + try { + return unsafeInstance(); + } catch (Throwable t) { + return safeInstance(); + } + } else { + return safeInstance(); + } + } + + /** + * Returns the fastest available {@link XXHashFactory} instance. If the class + * loader is the system class loader and if the + * {@link #nativeInstance() native instance} loads successfully, then the + * {@link #nativeInstance() native instance} is returned, otherwise the + * {@link #fastestJavaInstance() fastest Java instance} is returned. + *

+ * Please read {@link #nativeInstance() javadocs of nativeInstance()} before + * using this method. + * + * @return the fastest available {@link XXHashFactory} instance. + */ + public static XXHashFactory fastestInstance() { + if (Native.isLoaded() + || Native.class.getClassLoader() == ClassLoader.getSystemClassLoader()) { + try { + return nativeInstance(); + } catch (Throwable t) { + return fastestJavaInstance(); + } + } else { + return fastestJavaInstance(); + } + } + + @SuppressWarnings("unchecked") + private static T classInstance(String cls) throws NoSuchFieldException, SecurityException, ClassNotFoundException, IllegalArgumentException, IllegalAccessException { + ClassLoader loader = XXHashFactory.class.getClassLoader(); + loader = loader == null ? ClassLoader.getSystemClassLoader() : loader; + final Class c = loader.loadClass(cls); + Field f = c.getField("INSTANCE"); + return (T) f.get(null); + } + + private final String impl; + private final XXHash32 hash32; + private final XXHash64 hash64; + private final StreamingXXHash32.Factory streamingHash32Factory; + private final StreamingXXHash64.Factory streamingHash64Factory; + + private XXHashFactory(String impl) throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { + this.impl = impl; + hash32 = classInstance("XXHash32" + impl); + streamingHash32Factory = classInstance("StreamingXXHash32" + impl + "$Factory"); + hash64 = classInstance("XXHash64" + impl); + streamingHash64Factory = classInstance("StreamingXXHash64" + impl + "$Factory"); + + // make sure it can run + final byte[] bytes = new byte[100]; + final Random random = new Random(); + random.nextBytes(bytes); + final int seed = random.nextInt(); + + final int h1 = hash32.hash(bytes, 0, bytes.length, seed); + final StreamingXXHash32 streamingHash32 = newStreamingHash32(seed); + streamingHash32.update(bytes, 0, bytes.length); + final int h2 = streamingHash32.getValue(); + final long h3 = hash64.hash(bytes, 0, bytes.length, seed); + final StreamingXXHash64 streamingHash64 = newStreamingHash64(seed); + streamingHash64.update(bytes, 0, bytes.length); + final long h4 = streamingHash64.getValue(); + if (h1 != h2) { + throw new AssertionError(); + } + if (h3 != h4) { + throw new AssertionError(); + } + } + + /** + * Returns a {@link XXHash32} instance. + * + * @return a {@link XXHash32} instance. + */ + public XXHash32 hash32() { + return hash32; + } + + /** + * Returns a {@link XXHash64} instance. + * + * @return a {@link XXHash64} instance. + */ + public XXHash64 hash64() { + return hash64; + } + + /** + * Return a new {@link StreamingXXHash32} instance. + * + * @param seed the seed to use + * @return a {@link StreamingXXHash32} instance + */ + public StreamingXXHash32 newStreamingHash32(int seed) { + return streamingHash32Factory.newStreamingHash(seed); + } + + /** + * Return a new {@link StreamingXXHash64} instance. + * + * @param seed the seed to use + * @return a {@link StreamingXXHash64} instance + */ + public StreamingXXHash64 newStreamingHash64(long seed) { + return streamingHash64Factory.newStreamingHash(seed); + } + + /** + * Prints the fastest instance. + * + * @param args no argument required + */ + public static void main(String[] args) { + System.out.println("Fastest instance is " + fastestInstance()); + System.out.println("Fastest Java instance is " + fastestJavaInstance()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + impl; + } + +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashJNI.java b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashJNI.java new file mode 100644 index 000000000..bad36cedf --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/XXHashJNI.java @@ -0,0 +1,43 @@ +package com.fr.third.net.jpountz.xxhash; + +/* + * 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 com.fr.third.net.jpountz.util.Native; + +enum XXHashJNI { + ; + + static { + Native.load(); + init(); + } + + private static native void init(); + static native int XXH32(byte[] input, int offset, int len, int seed); + static native int XXH32BB(ByteBuffer input, int offset, int len, int seed); + static native long XXH32_init(int seed); + static native void XXH32_update(long state, byte[] input, int offset, int len); + static native int XXH32_digest(long state); + static native void XXH32_free(long state); + + static native long XXH64(byte[] input, int offset, int len, long seed); + static native long XXH64BB(ByteBuffer input, int offset, int len, long seed); + static native long XXH64_init(long seed); + static native void XXH64_update(long state, byte[] input, int offset, int len); + static native long XXH64_digest(long state); + static native void XXH64_free(long state); +} diff --git a/fine-lz4/src/com/fr/third/net/jpountz/xxhash/package.html b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/package.html new file mode 100644 index 000000000..f595d25a8 --- /dev/null +++ b/fine-lz4/src/com/fr/third/net/jpountz/xxhash/package.html @@ -0,0 +1,65 @@ + + + + + + + +

xxhash hashing. This package supports both block hashing via +{@link net.jpountz.xxhash.XXHash32} and streaming hashing via +{@link net.jpountz.xxhash.StreamingXXHash32}. Have a look at +{@link net.jpountz.xxhash.XXHashFactory} to know how to get instances of these +interfaces.

+ +

Streaming hashing is a little slower but doesn't require to load the whole +stream into memory.

+ +

Sample block usage:

+ +
+    XXHashFactory factory = XXHashFactory.fastestInstance();
+
+    byte[] data = "12345345234572".getBytes("UTF-8");
+
+    XXHash32 hash32 = factory.hash32();
+    int seed = 0x9747b28c; // used to initialize the hash value, use whatever
+                           // value you want, but always the same
+    int hash = hash32.hash(data, 0, data.length, seed);
+
+ +

Sample streaming usage:

+ +
+    XXHashFactory factory = XXHashFactory.fastestInstance();
+
+    byte[] data = "12345345234572".getBytes("UTF-8");
+    ByteArrayInputStream in = new ByteArrayInputStream(data);
+
+    int seed = 0x9747b28c; // used to initialize the hash value, use whatever
+                           // value you want, but always the same
+    StreamingXXHash32 hash32 = factory.newStreamingHash32(seed);
+    byte[] buf = new byte[8]; // for real-world usage, use a larger buffer, like 8192 bytes
+    for (;;) {
+      int read = in.read(buf);
+      if (read == -1) {
+        break;
+      }
+      hash32.update(buf, 0, read);
+    }
+    int hash = hash32.getValue();
+
+ + +